Compare commits

..

2 Commits

Author SHA1 Message Date
Sarah Hoffmann
edd77e3184 prepare for 3.2.1 release 2020-05-02 23:02:56 +02:00
Sarah Hoffmann
f549379e31 properly escape class parameter
The class parameter was used as is, allowing for potential
SQL injection via the API.

Thanks to @bladeswords for finding this.
2020-05-02 23:01:27 +02:00
247 changed files with 51008 additions and 14047 deletions

View File

@@ -1,22 +1,16 @@
---
os: linux
dist: bionic
sudo: required
dist: trusty
language: python
python:
- "3.6"
addons:
postgresql: "9.6"
apt:
packages:
postgresql-server-dev-9.6
postgresql-client-9.6
git:
depth: 3
env:
- TEST_SUITE=tests
- TEST_SUITE=monaco
before_install:
- phpenv global 7.1
install:
- vagrant/install-on-travis-ci.sh
before_script:
@@ -25,15 +19,14 @@ script:
- cd $TRAVIS_BUILD_DIR/
- if [[ $TEST_SUITE == "tests" ]]; then phpcs --report-width=120 . ; fi
- cd $TRAVIS_BUILD_DIR/test/php
- if [[ $TEST_SUITE == "tests" ]]; then /usr/bin/phpunit ./ ; fi
- if [[ $TEST_SUITE == "tests" ]]; then phpunit ./ ; fi
- cd $TRAVIS_BUILD_DIR/test/bdd
- # behave --format=progress3 api
- if [[ $TEST_SUITE == "tests" ]]; then behave -DREMOVE_TEMPLATE=1 --format=progress3 db ; fi
- if [[ $TEST_SUITE == "tests" ]]; then behave --format=progress3 db ; fi
- if [[ $TEST_SUITE == "tests" ]]; then behave --format=progress3 osm2pgsql ; fi
- cd $TRAVIS_BUILD_DIR/build
- if [[ $TEST_SUITE == "monaco" ]]; then wget --no-verbose --output-document=../data/monaco.osm.pbf http://download.geofabrik.de/europe/monaco-latest.osm.pbf; fi
- if [[ $TEST_SUITE == "monaco" ]]; then /usr/bin/env php ./utils/setup.php --osm-file ../data/monaco.osm.pbf --osm2pgsql-cache 1000 --all 2>&1 | grep -v 'ETA (seconds)'; fi
- if [[ $TEST_SUITE == "monaco" ]]; then /usr/bin/env php ./utils/specialphrases.php --wiki-import | psql -d test_api_nominatim >/dev/null; fi
- if [[ $TEST_SUITE == "monaco" ]]; then /usr/bin/env php ./utils/check_import_finished.php; fi
notifications:
email: false

View File

@@ -19,8 +19,8 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
project(nominatim)
set(NOMINATIM_VERSION_MAJOR 3)
set(NOMINATIM_VERSION_MINOR 5)
set(NOMINATIM_VERSION_PATCH 0)
set(NOMINATIM_VERSION_MINOR 2)
set(NOMINATIM_VERSION_PATCH 1)
set(NOMINATIM_VERSION "${NOMINATIM_VERSION_MAJOR}.${NOMINATIM_VERSION_MINOR}.${NOMINATIM_VERSION_PATCH}")
@@ -28,57 +28,56 @@ add_definitions(-DNOMINATIM_VERSION="${NOMINATIM_VERSION}")
#-----------------------------------------------------------------------------
# Configuration
#
# Find external dependencies
#
#-----------------------------------------------------------------------------
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 on CACHE BOOL "Build PostgreSQL module")
set(BUILD_TESTS on CACHE BOOL "Build test suite")
set(BUILD_DOCS on CACHE BOOL "Build documentation")
set(BUILD_OSM2PGSQL on CACHE BOOL "Build osm2pgsql (expert only)")
set(BUILD_TESTS off CACHE BOOL "Build test suite" FORCE)
set(WITH_LUA off CACHE BOOL "Build with lua support" FORCE)
#-----------------------------------------------------------------------------
# osm2pgsql (imports/updates only)
#-----------------------------------------------------------------------------
if (NOT EXISTS "${CMAKE_SOURCE_DIR}/osm2pgsql/CMakeLists.txt")
message(FATAL_ERROR "The osm2pgsql directory is empty.\
Did you forget to check out Nominatim recursively?\
\nTry updating submodules with: git submodule update --init")
endif()
add_subdirectory(osm2pgsql)
if (BUILD_IMPORTER AND BUILD_OSM2PGSQL)
if (NOT EXISTS "${CMAKE_SOURCE_DIR}/osm2pgsql/CMakeLists.txt")
message(FATAL_ERROR "The osm2pgsql directory is empty.\
Did you forget to check out Nominatim recursively?\
\nTry updating submodules with: git submodule update --init")
endif()
set(BUILD_TESTS_SAVED "${BUILD_TESTS}")
set(BUILD_TESTS off)
set(WITH_LUA off CACHE BOOL "")
add_subdirectory(osm2pgsql)
set(BUILD_TESTS ${BUILD_TESTS_SAVED})
find_package(Threads REQUIRED)
unset(PostgreSQL_TYPE_INCLUDE_DIR CACHE)
set(PostgreSQL_TYPE_INCLUDE_DIR "/usr/include/")
find_package(PostgreSQL REQUIRED)
include_directories(${PostgreSQL_INCLUDE_DIRS})
link_directories(${PostgreSQL_LIBRARY_DIRS})
find_program(PYOSMIUM pyosmium-get-changes)
if (NOT EXISTS "${PYOSMIUM}")
set(PYOSMIUM_PATH "")
message(WARNING "pyosmium-get-changes not found (required for updates)")
else()
set(PYOSMIUM_PATH "${PYOSMIUM}")
message(STATUS "Using pyosmium-get-changes at ${PYOSMIUM_PATH}")
endif()
#-----------------------------------------------------------------------------
# python and pyosmium (imports/updates only)
#-----------------------------------------------------------------------------
find_program(PG_CONFIG pg_config)
execute_process(COMMAND ${PG_CONFIG} --pgxs
OUTPUT_VARIABLE PGXS
OUTPUT_STRIP_TRAILING_WHITESPACE)
if (BUILD_IMPORTER)
find_package(PythonInterp 3)
find_program(PYOSMIUM pyosmium-get-changes)
if (NOT EXISTS "${PYOSMIUM}")
set(PYOSMIUM_PATH "")
message(WARNING "pyosmium-get-changes not found (required for updates)")
else()
set(PYOSMIUM_PATH "${PYOSMIUM}")
message(STATUS "Using pyosmium-get-changes at ${PYOSMIUM_PATH}")
endif()
if (NOT EXISTS "${PGXS}")
message(FATAL_ERROR "Postgresql server package not found.")
endif()
#-----------------------------------------------------------------------------
# PHP
#-----------------------------------------------------------------------------
find_package(ZLIB REQUIRED)
find_package(BZip2 REQUIRED)
find_package(LibXml2 REQUIRED)
include_directories(${LIBXML2_INCLUDE_DIR})
# Setting PHP binary variable as to command line (prevailing) or auto detect
if (NOT PHP_BIN)
find_program (PHP_BIN php)
endif()
@@ -89,103 +88,75 @@ endif()
message (STATUS "Using PHP binary " ${PHP_BIN})
#-----------------------------------------------------------------------------
# import scripts and utilities (importer only)
#
# Setup settings and paths
#
#-----------------------------------------------------------------------------
if (BUILD_IMPORTER)
set(CUSTOMSCRIPTS
utils/check_import_finished.php
utils/country_languages.php
utils/importWikipedia.php
utils/export.php
utils/query.php
utils/setup.php
utils/specialphrases.php
utils/update.php
utils/warm.php
)
foreach (script_source ${CUSTOMSCRIPTS})
configure_file(${PROJECT_SOURCE_DIR}/cmake/script.tmpl
${PROJECT_BINARY_DIR}/${script_source})
endforeach()
endif()
#-----------------------------------------------------------------------------
# webserver scripts (API only)
#-----------------------------------------------------------------------------
if (BUILD_API)
set(WEBSITESCRIPTS
website/deletable.php
website/details.php
website/hierarchy.php
website/lookup.php
website/polygons.php
website/reverse.php
website/search.php
website/status.php
set(CUSTOMFILES
settings/phrase_settings.php
website/deletable.php
website/details.php
website/hierarchy.php
website/lookup.php
website/polygons.php
website/reverse.php
website/search.php
website/status.php
utils/blocks.php
utils/country_languages.php
utils/imports.php
utils/importWikipedia.php
utils/export.php
utils/query.php
utils/server_compare.php
utils/setup.php
utils/specialphrases.php
utils/update.php
utils/warm.php
)
foreach (script_source ${WEBSITESCRIPTS})
configure_file(${PROJECT_SOURCE_DIR}/cmake/website.tmpl
${PROJECT_BINARY_DIR}/${script_source})
endforeach()
foreach (cfile ${CUSTOMFILES})
configure_file(${PROJECT_SOURCE_DIR}/${cfile} ${PROJECT_BINARY_DIR}/${cfile})
endforeach()
set(WEBPATHS css images js)
configure_file(${PROJECT_SOURCE_DIR}/settings/defaults.php ${PROJECT_BINARY_DIR}/settings/settings.php)
set(WEBPATHS css images js)
foreach (wp ${WEBPATHS})
execute_process(
COMMAND ln -sf ${PROJECT_SOURCE_DIR}/website/${wp} ${PROJECT_BINARY_DIR}/website/
)
endforeach()
foreach (wp ${WEBPATHS})
execute_process(
COMMAND ln -sf ${PROJECT_SOURCE_DIR}/website/${wp} ${PROJECT_BINARY_DIR}/website/
)
endforeach()
endif()
#-----------------------------------------------------------------------------
# default settings
#-----------------------------------------------------------------------------
configure_file(${PROJECT_SOURCE_DIR}/settings/defaults.php
${PROJECT_BINARY_DIR}/settings/settings.php)
#-----------------------------------------------------------------------------
#
# Tests
#
#-----------------------------------------------------------------------------
if (BUILD_TESTS)
include(CTest)
include(CTest)
set(TEST_BDD db osm2pgsql api)
set(TEST_BDD db osm2pgsql api)
foreach (test ${TEST_BDD})
add_test(NAME bdd_${test}
COMMAND behave ${test}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test/bdd)
set_tests_properties(bdd_${test}
PROPERTIES ENVIRONMENT "NOMINATIM_DIR=${PROJECT_BINARY_DIR}")
endforeach()
foreach (test ${TEST_BDD})
add_test(NAME bdd_${test}
COMMAND lettuce features/${test}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests)
set_tests_properties(bdd_${test}
PROPERTIES ENVIRONMENT "NOMINATIM_DIR=${PROJECT_BINARY_DIR}")
endforeach()
add_test(NAME php
COMMAND phpunit ./
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test/php)
add_test(NAME phpcs
COMMAND phpcs --report-width=120 --colors lib website utils
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
endif()
add_test(NAME php
COMMAND phpunit ./
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests-php)
#-----------------------------------------------------------------------------
# Postgres module
#-----------------------------------------------------------------------------
if (BUILD_MODULE)
add_subdirectory(module)
endif()
add_subdirectory(module)
add_subdirectory(nominatim)
add_subdirectory(docs)
#-----------------------------------------------------------------------------
# Documentation
#-----------------------------------------------------------------------------
if (BUILD_DOCS)
add_subdirectory(docs)
endif()

View File

@@ -15,9 +15,9 @@ Please make sure to add the following information:
* the result you are getting
* the expected result, preferably a link to the OSM object you want to find,
otherwise an address that is as precise as possible
To get the link to the OSM object, you can try the following:
To get the link to the OSM object, you can try the following:
* go to https://openstreetmap.org
* zoom to the area of the map where you expect the result and
zoom in as much as possible
@@ -26,7 +26,7 @@ To get the link to the OSM object, you can try the following:
* find the object of interest in the list that appears on the left side
* click on the object and report the URL back that the browser shows
### When Reporting Bugs...
### When Reporting Problems with your Installation...
Please add the following information to your issue:
@@ -38,9 +38,6 @@ Please add the following information to your issue:
if you run from the git repo, the output of `git rev-parse HEAD`)
* (if applicable) exact command line of the command that was causing the issue
Bug reports that do not include extensive information about your system,
about the problem and about what you have been trying to debug the problem
will be closed.
## Workflow for Pull Requests

View File

@@ -1,84 +1,5 @@
3.5.0
* structured select on HTML search page
* new PHP Nominatim\Shell class to wrap shell escaping
* remove polygon parameter from all API calls
* improve handling of postcode areas
* reorganise place linking algorithm, now using wikidata tag as well
* remove linkees from search_name and larger_area tables
* introduce country-specific address ranks
* reorganise rank address computation
* cleanup of partition function
* improve parenting for large POIs
* add support for Postgresql 12 and Postgis 3
* add earlier cleanup when --drop is given, to reduce meory usage
* remove use of place_id in URLs
* replace C nominatim indexer with a simpler Python implementation
* split up the huge sql/functions.sql file
* move osm2pgsql tests to osm2pgsql
* add new extratags style which imports all tags from OSM
* add new script for checking the import after completion
* update osm2pgsql, reducing memory usage
* use new wikipedia importance and add processing of wikidata tags
* add search form for details page
* use ExtraDataPath for country_grid table
* remove short_name from list of names to be displayed
* split up CMakeFile, so that all parts can be built separately
* update installation instructions for CentOS and Ubuntu
* add script for importing/updating multiple country extracts
* various documentation improvements
3.4.2
* fix security bug in /details endpoint where user input was not
properly sanitized
3.4.1
* update osm2pgsql to fix hans during updates and lost address numbers
during updates
3.4.0
* increase required version for PostgreSQL(9.3), PostGIS(2.2) and PHP(7.0)
* better error reporting for out-of-memory errors
* exclude postcode ranges separated by colon from centre point calculation
* update osm2pgsql, better handling of imports without flatnode file
* switch to more efficient algorithm for word set computation
* use only boundries for country and state parts of addresses
* improve updates of addresses with housenumbers and interpolations
* remove country from place_addressline table and use country_code instead
* optimise indexes on search_name partition tables
* improve searching of attached streets for large objects like airports
* drop support for python 2
* new scripts for importing Wikidata for importance
* create and drop indexes concurrently to not clash with auto vacuum
* various documentation improvements
3.3.0
* zoom 17 in reverse now zooms in on minor streets
* fix use of postcode relations in address
* support for housenumber 0 on interpolations
* replace database abstraction DB with PDO and switch to using exceptions
* exclude line features at rank 30 from reverse geocoding
* remove self-reference and country from place_addressline
* make json output more readable (less escaping)
* update conversion scripts for postcodes
* scripts in utils/ are no longer executable (always use scripts in build dir)
* remove Natural Earth country fallback (OSM is complete enough)
* make rank assignments configurable
* allow accept languages with underscore
* new reverse-only import mode (without search index table)
* rely on boundaries only for states and countries
* update osm2pgsql, now using a configurable style
* provide multiple import styles
* improve search when house number and postcodes are dropped
* overhaul of setup code
* add support for PHPUnit 6
* update test database
* various documentation improvements
3.2.1
* security fix: fix possible SQL injection via details API
3.2.0

View File

@@ -1,4 +1,4 @@
[![Build Status](https://travis-ci.org/osm-search/Nominatim.svg?branch=master)](https://travis-ci.org/osm-search/Nominatim)
[![Build Status](https://travis-ci.org/openstreetmap/Nominatim.svg?branch=master)](https://travis-ci.org/openstreetmap/Nominatim)
Nominatim
=========
@@ -19,16 +19,8 @@ https://nominatim.org/release-docs/develop/ .
Installation
============
**Nominatim is a complex piece of software and runs in a complex environment.
Installing and running Nominatim is something for experienced system
administrators only who can do some trouble-shooting themselves. We are sorry,
but we can not provide installation support. We are all doing this in our free
time and there is just so much of that time to go around. Do not open issues in
our bug tracker if you need help. You can ask questions on the mailing list
(see below) or on [help.openstreetmap.org](https://help.openstreetmap.org/).**
The latest stable release can be downloaded from https://nominatim.org.
There you can also find [installation instructions for the release](https://nominatim.org/release-docs/latest/admin/Installation), as well as an extensive [Troubleshooting/FAQ section](https://nominatim.org/release-docs/latest/admin/Faq/).
There you can also find [installation instructions for the release](https://nominatim.org/release-docs/latest/admin/Installation).
Detailed installation instructions for the development version can be
found at [nominatim.org](https://nominatim.org/release-docs/develop/admin/Installation)
@@ -55,17 +47,11 @@ License
The source code is available under a GPLv2 license.
Contact and Bug reports
======================
Contributing
============
Contributions are welcome. For details see [contribution guide](CONTRIBUTING.md).
Both bug reports and pull requests are welcome.
Mailing list
============
For questions you can join the geocoding mailing list, see
For questions you can join the geocoding mailinglist, see
https://lists.openstreetmap.org/listinfo/geocoding
Bugs may be reported on the github project site:
https://github.com/openstreetmap/Nominatim

View File

@@ -141,7 +141,7 @@ No. Long running Nominatim installations will differ once new import features (o
bug fixes) get added since those usually only get applied to new/changed data.
Also this document skips the optional Wikipedia data import which affects ranking
of search results. See [Nominatim installation](https://nominatim.org/release-docs/latest/admin/Installation) for details.
of search results. See [Nominatim installation](http://nominatim.org/release-docs/latest/Installation) for details.
##### Why Ubuntu? Can I test CentOS/Fedora/CoreOS/FreeBSD?
@@ -171,7 +171,7 @@ If the Postgres installation is behind a firewall, you can try
inside the virtual machine. It will map the port to `localhost:9999` and then
you edit `settings/local.php` with
@define('CONST_Database_DSN', 'pgsql:host=localhost;port=9999;user=postgres;dbname=nominatim_it');
@define('CONST_Database_DSN', 'pgsql://postgres@localhost:9999/nominatim_it');
To access postgres directly remember to specify the hostname, e.g. `psql --host localhost --port 9999 nominatim_it`

30
Vagrantfile vendored
View File

@@ -15,15 +15,6 @@ Vagrant.configure("2") do |config|
end
config.vm.define "ubuntu", primary: true do |sub|
sub.vm.box = "bento/ubuntu-20.04"
sub.vm.provision :shell do |s|
s.path = "vagrant/Install-on-Ubuntu-20.sh"
s.privileged = false
s.args = [checkout]
end
end
config.vm.define "ubuntu18", primary: true do |sub|
sub.vm.box = "bento/ubuntu-18.04"
sub.vm.provision :shell do |s|
s.path = "vagrant/Install-on-Ubuntu-18.sh"
@@ -32,15 +23,6 @@ Vagrant.configure("2") do |config|
end
end
config.vm.define "ubuntu18nginx" do |sub|
sub.vm.box = "bento/ubuntu-18.04"
sub.vm.provision :shell do |s|
s.path = "vagrant/Install-on-Ubuntu-18-nginx.sh"
s.privileged = false
s.args = [checkout]
end
end
config.vm.define "ubuntu16" do |sub|
sub.vm.box = "bento/ubuntu-16.04"
sub.vm.provision :shell do |s|
@@ -70,18 +52,6 @@ Vagrant.configure("2") do |config|
sub.vm.synced_folder ".", "/vagrant", disabled: true
end
config.vm.define "centos8" do |sub|
sub.vm.box = "generic/centos8"
sub.vm.provision :shell do |s|
s.path = "vagrant/Install-on-Centos-8.sh"
s.privileged = false
s.args = "yes"
end
sub.vm.synced_folder ".", "/home/vagrant/Nominatim", disabled: true
sub.vm.synced_folder ".", "/vagrant", disabled: true
end
config.vm.provider "virtualbox" do |vb|
vb.gui = false
vb.memory = 2048

View File

@@ -1,4 +0,0 @@
#!@PHP_BIN@ -Cq
<?php
require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
require_once(CONST_BasePath.'/@script_source@');

View File

@@ -1,3 +0,0 @@
<?php
require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
require_once(CONST_BasePath.'/@script_source@');

View File

@@ -1,77 +0,0 @@
# Fallback Country Boundaries
Each place is assigned a `country_code` and partition. Partitions derive from `country_code`.
Nominatim imports two pre-generated files
* `data/country_name.sql` (country code, name, default language, partition)
* `data/country_osm_grid.sql` (country code, geometry)
before creating places in the database. This helps with fast lookups and missing data (e.g. if the data the user wants to import doesn't contain any country places).
The number of countries in the world can change (South Sudan created 2011, Germany reunification), so can their boundaries. This document explain how the pre-generated files can be updated.
## Country code
Each place is assigned a two letter country_code based on its location, e.g. `gb` for Great Britain. Or `NULL` if no suitable country is found (usually it's in open water then).
In `sql/functions.sql: get_country_code(geometry)` the place's center is checked against
1. country places already imported from the user's data file. Places are imported by rank low-to-high. Lowest rank 2 is countries so most places should be matched. Still the data file might be incomplete.
2. if unmatched: OSM grid boundaries
3. if still unmatched: OSM grid boundaries, but allow a small distance
## Partitions
Each place is assigned partition, which is a number 0..250. 0 is fallback/other.
During place indexing (`sql/functions.sql: placex_insert()`) a place is assigned the partition based on its country code (`sql/functions.sql: get_partition(country_code)`). It checks in the `country_name` table.
Most countries have their own partition, some share a partition. Thus partition counts vary greatly.
Several database tables are split by partition to allow queries to run against less indices and improve caching.
* `location_area_large_<partition>`
* `search_name_<partition>`
* `location_road_<partition>`
## Data files
### data/country_name.sql
Export from existing database table plus manual changes. `country_default_language_code` most taken from [https://wiki.openstreetmap.org/wiki/Nominatim/Country_Codes](), see `utils/country_languages.php`.
### data/country_osm_grid.sql
`country_grid.sql` merges territories by country. Then uses `function.sql: quad_split_geometry` to split each country into multiple [Quadtree](https://en.wikipedia.org/wiki/Quadtree) polygons for faster point-in-polygon lookups.
To visualize one country as geojson feature collection, e.g. for loading into [geojson.io](http://geojson.io/):
```
-- http://www.postgresonline.com/journal/archives/267-Creating-GeoJSON-Feature-Collections-with-JSON-and-PostGIS-functions.html
SELECT row_to_json(fc)
FROM (
SELECT 'FeatureCollection' As type, array_to_json(array_agg(f)) As features
FROM (
SELECT 'Feature' As type,
ST_AsGeoJSON(lg.geometry)::json As geometry,
row_to_json((country_code, area)) As properties
FROM country_osm_grid As lg where country_code='mx'
) As f
) As fc;
```
`cat /tmp/query.sql | psql -At nominatim > /tmp/mexico.quad.geojson`
![mexico](mexico.quad.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

View File

@@ -1,56 +0,0 @@
# GB Postcodes
The server [importing instructions](https://www.nominatim.org/release-docs/latest/admin/Import-and-Update/) allow optionally download [`gb_postcode_data.sql.gz`](https://www.nominatim.org/data/gb_postcode_data.sql.gz). This document explains how the file got created.
## GB vs UK
GB (Great Britain) is more correct as the Ordnance Survey dataset doesn't contain postcodes from Northern Ireland.
## Importing separately after the initial import
If you forgot to download the file, or have a new version, you can import it separately:
1. Import the downloaded `gb_postcode_data.sql.gz` file.
2. Run the SQL query `SELECT count(getorcreate_postcode_id(postcode)) FROM gb_postcode;`. This will update the search index.
3. Run `utils/setup.php --calculate-postcodes` from the build directory. This will copy data form the `gb_postcode` table to the `location_postcodes` table.
## Converting Code-Point Open data
1. Download from [Code-Point® Open](https://www.ordnancesurvey.co.uk/business-and-government/products/code-point-open.html). It requires an email address where a download link will be send to.
2. `unzip codepo_gb.zip`
Unpacked you'll see a directory of CSV files.
$ more codepo_gb/Data/CSV/n.csv
"N1 0AA",10,530626,183961,"E92000001","E19000003","E18000007","","E09000019","E05000368"
"N1 0AB",10,530559,183978,"E92000001","E19000003","E18000007","","E09000019","E05000368"
The coordinates are "Northings" and "Eastings" in [OSGB 1936](http://epsg.io/1314) projection. They can be projected to WGS84 like this
SELECT ST_AsText(ST_Transform(ST_SetSRID('POINT(530626 183961)'::geometry,27700), 4326));
POINT(-0.117872733220225 51.5394424719303)
[-0.117872733220225 51.5394424719303 on OSM map](https://www.openstreetmap.org/?mlon=-0.117872733220225&mlat=51.5394424719303&zoom=16)
3. Create database, import CSV files, add geometry column, dump into file
DBNAME=create_gb_postcode_file
createdb $DBNAME
echo 'CREATE EXTENSION postgis' | psql $DBNAME
cat data/gb_postcode_table.sql | psql $DBNAME
cat codepo_gb/Data/CSV/*.csv | ./data-sources/gb-postcodes/convert_codepoint.php | psql $DBNAME
cat codepo_gb/Doc/licence.txt | iconv -f iso-8859-1 -t utf-8 | dos2unix | sed 's/^/-- /g' > gb_postcode_data.sql
pg_dump -a -t gb_postcode $DBNAME | grep -v '^--' >> gb_postcode_data.sql
gzip -9 -f gb_postcode_data.sql
ls -lah gb_postcode_data.*
# dropdb $DBNAME

View File

@@ -1,37 +0,0 @@
#!/usr/bin/env php
<?php
echo <<< EOT
ALTER TABLE gb_postcode ADD COLUMN easting bigint;
ALTER TABLE gb_postcode ADD COLUMN northing bigint;
TRUNCATE gb_postcode;
COPY gb_postcode (id, postcode, easting, northing) FROM stdin;
EOT;
$iCounter = 0;
while ($sLine = fgets(STDIN)) {
$aColumns = str_getcsv($sLine);
// insert space before the third last position
// https://stackoverflow.com/a/9144834
$postcode = $aColumns[0];
$postcode = preg_replace('/\s*(...)$/', ' $1', $postcode);
echo join("\t", array($iCounter, $postcode, $aColumns[2], $aColumns[3]))."\n";
$iCounter = $iCounter + 1;
}
echo <<< EOT
\.
UPDATE gb_postcode SET geometry=ST_Transform(ST_SetSRID(CONCAT('POINT(', easting, ' ', northing, ')')::geometry, 27700), 4326);
ALTER TABLE gb_postcode DROP COLUMN easting;
ALTER TABLE gb_postcode DROP COLUMN northing;
EOT;

View File

@@ -1,26 +0,0 @@
# US TIGER address data
Convert [TIGER](https://www.census.gov/geographies/mapping-files/time-series/geo/tiger-line-file.html)/Line dataset of the US Census Bureau to SQL files which can be imported by Nominatim. The created tables in the Nominatim database are separate from OpenStreetMap tables and get queried at search time separately.
The dataset gets updated once per year. Downloading is prone to be slow (can take a full day) and converting them can take hours as well.
Replace '2019' with the current year throughout.
1. Install the GDAL library and python bindings and the unzip tool
# Ubuntu:
sudo apt-get install python3-gdal unzip
2. Get the TIGER 2019 data. You will need the EDGES files
(3,233 zip files, 11GB total).
wget -r ftp://ftp2.census.gov/geo/tiger/TIGER2019/EDGES/
3. Convert the data into SQL statements. Adjust the file paths in the scripts as needed
cd data-sources/us-tiger
./convert.sh <input-path> <output-path>
4. Maybe: package the created files
tar -czf tiger2019-nominatim-preprocessed.tar.gz tiger

View File

@@ -1,48 +0,0 @@
#!/bin/bash
INPATH=$1
OUTPATH=$2
if [[ ! -d "$INPATH" ]]; then
echo "input path does not exist"
exit 1
fi
if [[ ! -d "$OUTPATH" ]]; then
echo "output path does not exist"
exit 1
fi
INREGEX='_([0-9]{5})_edges.zip'
WORKPATH="$OUTPATH/tmp-workdir/"
mkdir -p "$WORKPATH"
INFILES=($INPATH/*.zip)
echo "Found ${#INFILES[*]} files."
for F in ${INFILES[*]}; do
# echo $F
if [[ "$F" =~ $INREGEX ]]; then
COUNTYID=${BASH_REMATCH[1]}
SHAPEFILE="$WORKPATH/$(basename $F '.zip').shp"
SQLFILE="$OUTPATH/$COUNTYID.sql"
unzip -o -q -d "$WORKPATH" "$F"
if [[ ! -e "$SHAPEFILE" ]]; then
echo "Unzip failed. $SHAPEFILE not found."
exit 1
fi
./tiger_address_convert.py "$SHAPEFILE" "$SQLFILE"
rm $WORKPATH/*
fi
done
OUTFILES=($OUTPATH/*.sql)
echo "Wrote ${#OUTFILES[*]} files."
rmdir $WORKPATH

View File

@@ -1,58 +0,0 @@
## Add Wikipedia and Wikidata to Nominatim
OSM contributors frequently tag items with links to Wikipedia and Wikidata. Nominatim can use the page ranking of Wikipedia pages to help indicate the relative importance of osm features. This is done by calculating an importance score between 0 and 1 based on the number of inlinks to an article for a location. If two places have the same name and one is more important than the other, the wikipedia score often points to the correct place.
These scripts extract and prepare both Wikipedia page rank and Wikidata links for use in Nominatim.
#### Create a new postgres DB for Processing
Due to the size of initial and intermediate tables, processing can be done in an external database:
```
CREATE DATABASE wikiprocessingdb;
```
---
Wikipedia
---
Processing these data requires a large amount of disk space (~1TB) and considerable time (>24 hours).
#### Import & Process Wikipedia tables
This step downloads and converts [Wikipedia](https://dumps.wikimedia.org/) page data SQL dumps to postgreSQL files which can be imported and processed with pagelink information from Wikipedia language sites to calculate importance scores.
- The script will processes data from whatever set of Wikipedia languages are specified in the initial languages array
- Note that processing the top 40 Wikipedia languages can take over a day, and will add nearly 1TB to the processing database. The final output tables will be approximately 11GB and 2GB in size
To download, convert, and import the data, then process summary statistics and compute importance scores, run:
```
./import_wikipedia.sh
```
---
Wikidata
---
This script downloads and processes Wikidata to enrich the previously created Wikipedia tables for use in Nominatim.
#### Import & Process Wikidata
This step downloads and converts [Wikidata](https://dumps.wikimedia.org/wikidatawiki/) page data SQL dumps to postgreSQL files which can be processed and imported into Nominatim database. Also utilizes Wikidata Query Service API to discover and include place types.
- Script presumes that the user has already processed Wikipedia tables as specified above
- Script requires wikidata_place_types.txt and wikidata_place_type_levles.csv
- script requires the [jq json parser](https://stedolan.github.io/jq/)
- Script processes data from whatever set of Wikipedia languages are specified in the initial languages array
- Script queries Wikidata Query Service API and imports all instances of place types listed in wikidata_place_types.txt
- Script updates wikipedia_articles table with extracted wikidata
By including Wikidata in the wikipedia_articles table, new connections can be made on the fly from the Nominatim placex table to wikipedia_article importance scores.
To download, convert, and import the data, then process required items, run:
```
./import_wikidata.sh
```

View File

@@ -1,274 +0,0 @@
#!/bin/bash
psqlcmd() {
psql --quiet wikiprocessingdb
}
mysql2pgsqlcmd() {
./mysql2pgsql.perl /dev/stdin /dev/stdout
}
download() {
echo "Downloading $1"
wget --quiet --no-clobber --tries 3 "$1"
}
# languages to process (refer to List of Wikipedias here: https://en.wikipedia.org/wiki/List_of_Wikipedias)
# requires Bash 4.0
readarray -t LANGUAGES < languages.txt
echo "====================================================================="
echo "Download wikidata dump tables"
echo "====================================================================="
# 114M wikidatawiki-latest-geo_tags.sql.gz
# 1.7G wikidatawiki-latest-page.sql.gz
# 1.2G wikidatawiki-latest-wb_items_per_site.sql.gz
download https://dumps.wikimedia.org/wikidatawiki/latest/wikidatawiki-latest-geo_tags.sql.gz
download https://dumps.wikimedia.org/wikidatawiki/latest/wikidatawiki-latest-page.sql.gz
download https://dumps.wikimedia.org/wikidatawiki/latest/wikidatawiki-latest-wb_items_per_site.sql.gz
echo "====================================================================="
echo "Import wikidata dump tables"
echo "====================================================================="
echo "Importing wikidatawiki-latest-geo_tags"
gzip -dc wikidatawiki-latest-geo_tags.sql.gz | mysql2pgsqlcmd | psqlcmd
echo "Importing wikidatawiki-latest-page"
gzip -dc wikidatawiki-latest-page.sql.gz | mysql2pgsqlcmd | psqlcmd
echo "Importing wikidatawiki-latest-wb_items_per_site"
gzip -dc wikidatawiki-latest-wb_items_per_site.sql.gz | mysql2pgsqlcmd | psqlcmd
echo "====================================================================="
echo "Get wikidata places from wikidata query API"
echo "====================================================================="
echo "Number of place types:"
wc -l wikidata_place_types.txt
while read F ; do
echo "Querying for place type $F..."
wget --quiet "https://query.wikidata.org/bigdata/namespace/wdq/sparql?format=json&query=SELECT ?item WHERE{?item wdt:P31*/wdt:P279*wd:$F;}" -O $F.json
jq -r '.results | .[] | .[] | [.item.value] | @csv' $F.json >> $F.txt
awk -v qid=$F '{print $0 ","qid}' $F.txt | sed -e 's!"http://www.wikidata.org/entity/!!' | sed 's/"//g' >> $F.csv
cat $F.csv >> wikidata_place_dump.csv
rm $F.json $F.txt $F.csv
done < wikidata_place_types.txt
echo "====================================================================="
echo "Import wikidata places"
echo "====================================================================="
echo "CREATE TABLE wikidata_place_dump (
item text,
instance_of text
);" | psqlcmd
echo "COPY wikidata_place_dump (item, instance_of)
FROM '/srv/nominatim/Nominatim/data-sources/wikipedia-wikidata/wikidata_place_dump.csv'
DELIMITER ','
CSV
;" | psqlcmd
echo "CREATE TABLE wikidata_place_type_levels (
place_type text,
level integer
);" | psqlcmd
echo "COPY wikidata_place_type_levels (place_type, level)
FROM '/srv/nominatim/Nominatim/data-sources/wikipedia-wikidata/wikidata_place_type_levels.csv'
DELIMITER ','
CSV
HEADER
;" | psqlcmd
echo "====================================================================="
echo "Create derived tables"
echo "====================================================================="
echo "CREATE TABLE geo_earth_primary AS
SELECT gt_page_id,
gt_lat,
gt_lon
FROM geo_tags
WHERE gt_globe = 'earth'
AND gt_primary = 1
AND NOT( gt_lat < -90
OR gt_lat > 90
OR gt_lon < -180
OR gt_lon > 180
OR gt_lat=0
OR gt_lon=0)
;" | psqlcmd
echo "CREATE TABLE geo_earth_wikidata AS
SELECT DISTINCT geo_earth_primary.gt_page_id,
geo_earth_primary.gt_lat,
geo_earth_primary.gt_lon,
page.page_title,
page.page_namespace
FROM geo_earth_primary
LEFT OUTER JOIN page
ON (geo_earth_primary.gt_page_id = page.page_id)
ORDER BY geo_earth_primary.gt_page_id
;" | psqlcmd
echo "ALTER TABLE wikidata_place_dump
ADD COLUMN ont_level integer,
ADD COLUMN lat numeric(11,8),
ADD COLUMN lon numeric(11,8)
;" | psqlcmd
echo "UPDATE wikidata_place_dump
SET ont_level = wikidata_place_type_levels.level
FROM wikidata_place_type_levels
WHERE wikidata_place_dump.instance_of = wikidata_place_type_levels.place_type
;" | psqlcmd
echo "CREATE TABLE wikidata_places
AS
SELECT DISTINCT ON (item) item,
instance_of,
MAX(ont_level) AS ont_level,
lat,
lon
FROM wikidata_place_dump
GROUP BY item,
instance_of,
ont_level,
lat,
lon
ORDER BY item
;" | psqlcmd
echo "UPDATE wikidata_places
SET lat = geo_earth_wikidata.gt_lat,
lon = geo_earth_wikidata.gt_lon
FROM geo_earth_wikidata
WHERE wikidata_places.item = geo_earth_wikidata.page_title
;" | psqlcmd
echo "====================================================================="
echo "Process language pages"
echo "====================================================================="
echo "CREATE TABLE wikidata_pages (
item text,
instance_of text,
lat numeric(11,8),
lon numeric(11,8),
ips_site_page text,
language text
);" | psqlcmd
for i in "${LANGUAGES[@]}"
do
echo "CREATE TABLE wikidata_${i}_pages AS
SELECT wikidata_places.item,
wikidata_places.instance_of,
wikidata_places.lat,
wikidata_places.lon,
wb_items_per_site.ips_site_page
FROM wikidata_places
LEFT JOIN wb_items_per_site
ON (CAST (( LTRIM(wikidata_places.item, 'Q')) AS INTEGER) = wb_items_per_site.ips_item_id)
WHERE ips_site_id = '${i}wiki'
AND LEFT(wikidata_places.item,1) = 'Q'
ORDER BY wikidata_places.item
;" | psqlcmd
echo "ALTER TABLE wikidata_${i}_pages
ADD COLUMN language text
;" | psqlcmd
echo "UPDATE wikidata_${i}_pages
SET language = '${i}'
;" | psqlcmd
echo "INSERT INTO wikidata_pages
SELECT item,
instance_of,
lat,
lon,
ips_site_page,
language
FROM wikidata_${i}_pages
;" | psqlcmd
done
echo "ALTER TABLE wikidata_pages
ADD COLUMN wp_page_title text
;" | psqlcmd
echo "UPDATE wikidata_pages
SET wp_page_title = REPLACE(ips_site_page, ' ', '_')
;" | psqlcmd
echo "ALTER TABLE wikidata_pages
DROP COLUMN ips_site_page
;" | psqlcmd
echo "====================================================================="
echo "Add wikidata to wikipedia_article table"
echo "====================================================================="
echo "UPDATE wikipedia_article
SET lat = wikidata_pages.lat,
lon = wikidata_pages.lon,
wd_page_title = wikidata_pages.item,
instance_of = wikidata_pages.instance_of
FROM wikidata_pages
WHERE wikipedia_article.language = wikidata_pages.language
AND wikipedia_article.title = wikidata_pages.wp_page_title
;" | psqlcmd
echo "CREATE TABLE wikipedia_article_slim
AS
SELECT * FROM wikipedia_article
WHERE wikidata_id IS NOT NULL
;" | psqlcmd
echo "ALTER TABLE wikipedia_article
RENAME TO wikipedia_article_full
;" | psqlcmd
echo "ALTER TABLE wikipedia_article_slim
RENAME TO wikipedia_article
;" | psqlcmd
echo "====================================================================="
echo "Dropping intermediate tables"
echo "====================================================================="
echo "DROP TABLE wikidata_place_dump;" | psqlcmd
echo "DROP TABLE geo_earth_primary;" | psqlcmd
for i in "${LANGUAGES[@]}"
do
echo "DROP TABLE wikidata_${i}_pages;" | psqlcmd
done

View File

@@ -1,297 +0,0 @@
#!/bin/bash
psqlcmd() {
psql --quiet wikiprocessingdb |& \
grep -v 'does not exist, skipping' |& \
grep -v 'violates check constraint' |& \
grep -vi 'Failing row contains'
}
mysql2pgsqlcmd() {
./mysql2pgsql.perl --nodrop /dev/stdin /dev/stdout
}
download() {
echo "Downloading $1"
wget --quiet --no-clobber --tries=3 "$1"
}
# languages to process (refer to List of Wikipedias here: https://en.wikipedia.org/wiki/List_of_Wikipedias)
# requires Bash 4.0
readarray -t LANGUAGES < languages.txt
echo "====================================================================="
echo "Create wikipedia calculation tables"
echo "====================================================================="
echo "CREATE TABLE linkcounts (
language text,
title text,
count integer,
sumcount integer,
lat double precision,
lon double precision
);" | psqlcmd
echo "CREATE TABLE wikipedia_article (
language text NOT NULL,
title text NOT NULL,
langcount integer,
othercount integer,
totalcount integer,
lat double precision,
lon double precision,
importance double precision,
title_en text,
osm_type character(1),
osm_id bigint
);" | psqlcmd
echo "CREATE TABLE wikipedia_redirect (
language text,
from_title text,
to_title text
);" | psqlcmd
echo "====================================================================="
echo "Download individual wikipedia language tables"
echo "====================================================================="
for i in "${LANGUAGES[@]}"
do
echo "Language: $i"
# english is the largest
# 1.7G enwiki-latest-page.sql.gz
# 6.2G enwiki-latest-pagelinks.sql.gz
# 355M enwiki-latest-langlinks.sql.gz
# 128M enwiki-latest-redirect.sql.gz
# example of smaller languge turkish
# 53M trwiki-latest-page.sql.gz
# 176M trwiki-latest-pagelinks.sql.gz
# 106M trwiki-latest-langlinks.sql.gz
# 3.2M trwiki-latest-redirect.sql.gz
download https://dumps.wikimedia.org/${i}wiki/latest/${i}wiki-latest-page.sql.gz
download https://dumps.wikimedia.org/${i}wiki/latest/${i}wiki-latest-pagelinks.sql.gz
download https://dumps.wikimedia.org/${i}wiki/latest/${i}wiki-latest-langlinks.sql.gz
download https://dumps.wikimedia.org/${i}wiki/latest/${i}wiki-latest-redirect.sql.gz
done
echo "====================================================================="
echo "Import individual wikipedia language tables"
echo "====================================================================="
for i in "${LANGUAGES[@]}"
do
echo "Language: $i"
# We pre-create the table schema. This allows us to
# 1. Skip index creation. Most queries we do are full table scans
# 2. Add constrain to only import namespace=0 (wikipedia articles)
# Both cuts down data size considerably (50%+)
echo "Importing ${i}wiki-latest-pagelinks"
echo "DROP TABLE IF EXISTS ${i}pagelinks;" | psqlcmd
echo "CREATE TABLE ${i}pagelinks (
pl_from int NOT NULL DEFAULT '0',
pl_namespace int NOT NULL DEFAULT '0',
pl_title text NOT NULL DEFAULT '',
pl_from_namespace int NOT NULL DEFAULT '0'
);" | psqlcmd
time \
gzip -dc ${i}wiki-latest-pagelinks.sql.gz | \
sed "s/\`pagelinks\`/\`${i}pagelinks\`/g" | \
mysql2pgsqlcmd | \
grep -v '^CREATE INDEX ' | \
psqlcmd
echo "Importing ${i}wiki-latest-page"
# autoincrement serial8 4byte
echo "DROP TABLE IF EXISTS ${i}page;" | psqlcmd
echo "CREATE TABLE ${i}page (
page_id int NOT NULL,
page_namespace int NOT NULL DEFAULT '0',
page_title text NOT NULL DEFAULT '',
page_restrictions text NOT NULL,
page_is_redirect smallint NOT NULL DEFAULT '0',
page_is_new smallint NOT NULL DEFAULT '0',
page_random double precision NOT NULL DEFAULT '0',
page_touched text NOT NULL DEFAULT '',
page_links_updated text DEFAULT NULL,
page_latest int NOT NULL DEFAULT '0',
page_len int NOT NULL DEFAULT '0',
page_content_model text DEFAULT NULL,
page_lang text DEFAULT NULL
);" | psqlcmd
time \
gzip -dc ${i}wiki-latest-page.sql.gz | \
sed "s/\`page\`/\`${i}page\`/g" | \
mysql2pgsqlcmd | \
grep -v '^CREATE INDEX ' | \
psqlcmd
echo "Importing ${i}wiki-latest-langlinks"
echo "DROP TABLE IF EXISTS ${i}langlinks;" | psqlcmd
echo "CREATE TABLE ${i}langlinks (
ll_from int NOT NULL DEFAULT '0',
ll_lang text NOT NULL DEFAULT '',
ll_title text NOT NULL DEFAULT ''
);" | psqlcmd
time \
gzip -dc ${i}wiki-latest-langlinks.sql.gz | \
sed "s/\`langlinks\`/\`${i}langlinks\`/g" | \
mysql2pgsqlcmd | \
grep -v '^CREATE INDEX ' | \
psqlcmd
echo "Importing ${i}wiki-latest-redirect"
echo "DROP TABLE IF EXISTS ${i}redirect;" | psqlcmd
echo "CREATE TABLE ${i}redirect (
rd_from int NOT NULL DEFAULT '0',
rd_namespace int NOT NULL DEFAULT '0',
rd_title text NOT NULL DEFAULT '',
rd_interwiki text DEFAULT NULL,
rd_fragment text DEFAULT NULL
);" | psqlcmd
time \
gzip -dc ${i}wiki-latest-redirect.sql.gz | \
sed "s/\`redirect\`/\`${i}redirect\`/g" | \
mysql2pgsqlcmd | \
grep -v '^CREATE INDEX ' | \
psqlcmd
done
echo "====================================================================="
echo "Process language tables and associated pagelink counts"
echo "====================================================================="
for i in "${LANGUAGES[@]}"
do
echo "Language: $i"
echo "CREATE TABLE ${i}pagelinkcount
AS
SELECT pl_title AS title,
COUNT(*) AS count,
0::bigint as othercount
FROM ${i}pagelinks
WHERE pl_namespace = 0
GROUP BY pl_title
;" | psqlcmd
echo "INSERT INTO linkcounts
SELECT '${i}',
pl_title,
COUNT(*)
FROM ${i}pagelinks
WHERE pl_namespace = 0
GROUP BY pl_title
;" | psqlcmd
echo "INSERT INTO wikipedia_redirect
SELECT '${i}',
page_title,
rd_title
FROM ${i}redirect
JOIN ${i}page ON (rd_from = page_id)
WHERE page_namespace = 0
AND rd_namespace = 0
;" | psqlcmd
done
for i in "${LANGUAGES[@]}"
do
for j in "${LANGUAGES[@]}"
do
echo "UPDATE ${i}pagelinkcount
SET othercount = ${i}pagelinkcount.othercount + x.count
FROM (
SELECT page_title AS title,
count
FROM ${i}langlinks
JOIN ${i}page ON (ll_from = page_id)
JOIN ${j}pagelinkcount ON (ll_lang = '${j}' AND ll_title = title)
) AS x
WHERE x.title = ${i}pagelinkcount.title
;" | psqlcmd
done
echo "INSERT INTO wikipedia_article
SELECT '${i}',
title,
count,
othercount,
count + othercount
FROM ${i}pagelinkcount
;" | psqlcmd
done
echo "====================================================================="
echo "Calculate importance score for each wikipedia page"
echo "====================================================================="
echo "UPDATE wikipedia_article
SET importance = LOG(totalcount)/LOG((SELECT MAX(totalcount) FROM wikipedia_article))
;" | psqlcmd
echo "====================================================================="
echo "Clean up intermediate tables to conserve space"
echo "====================================================================="
for i in "${LANGUAGES[@]}"
do
echo "DROP TABLE ${i}pagelinks;" | psqlcmd
echo "DROP TABLE ${i}page;" | psqlcmd
echo "DROP TABLE ${i}langlinks;" | psqlcmd
echo "DROP TABLE ${i}redirect;" | psqlcmd
echo "DROP TABLE ${i}pagelinkcount;" | psqlcmd
done
echo "all done."

View File

@@ -1,39 +0,0 @@
ar
bg
ca
cs
da
de
en
es
eo
eu
fa
fr
ko
hi
hr
id
it
he
lt
hu
ms
nl
ja
no
pl
pt
kk
ro
ru
sk
sl
sr
fi
sv
tr
uk
vi
war
zh

View File

@@ -1,199 +0,0 @@
place_type,level
Q9842,4
Q9430,3
Q928830,4
Q9259,1
Q91028,5
Q8514,2
Q8502,2
Q83405,3
Q82794,2
Q820477,1
Q811979,1
Q8072,2
Q79007,2
Q786014,3
Q75848,2
Q75520,2
Q728937,4
Q7275,2
Q719456,3
Q7075,3
Q697295,4
Q6852233,2
Q682943,3
Q665487,5
Q655686,3
Q643589,5
Q641226,2
Q631305,2
Q6256,2
Q6023295,2
Q5773747,5
Q56061,1
Q55659167,4
Q55488,4
Q55465477,3
Q54050,2
Q532,3
Q53060,2
Q52177058,4
Q515716,5
Q5153984,4
Q515,3
Q5144960,5
Q5119,4
Q5119,4
Q5107,2
Q5084,4
Q5031071,4
Q5003624,2
Q4989906,1
Q4976993,3
Q486972,1
Q486972,2
Q483110,3
Q4830453,4
Q47521,3
Q473972,1
Q46831,2
Q46614560,5
Q44782,3
Q44613,4
Q44539,4
Q44494,2
Q44377,2
Q4421,2
Q43501,2
Q4286337,3
Q42523,3
Q41176,2
Q40357,3
Q4022,4
Q40080,2
Q39816,2
Q39715,3
Q39614,1
Q3957,3
Q3947,4
Q3914,3
Q38723,2
Q38720,3
Q3623867,5
Q35666,2
Q355304,3
Q35509,2
Q35112127,3
Q34985575,4
Q34876,5
Q34763,2
Q34627,4
Q3455524,3
Q34442,4
Q33837,2
Q33506,3
Q32815,4
Q3257686,2
Q3240715,2
Q3191695,5
Q3153117,2
Q30198,2
Q30139652,3
Q294422,3
Q2870166,3
Q27686,3
Q274153,3
Q271669,1
Q2659904,2
Q24529780,2
Q24354,3
Q2354973,4
Q23442,2
Q23413,3
Q23397,3
Q2327515,4
Q2311958,5
Q22927291,6
Q22698,1
Q2175765,4
Q205495,4
Q204832,3
Q2042028,2
Q202216,6
Q1970725,3
Q194203,5
Q194195,2
Q190429,2
Q185187,3
Q185113,2
Q183366,2
Q1799794,1
Q1788454,4
Q1785071,3
Q1777138,3
Q177634,2
Q177380,2
Q174814,4
Q174782,2
Q17350442,2
Q17343829,3
Q17334923,0
Q17018380,3
Q16970,4
Q16917,3
Q16831714,4
Q165,3
Q160742,4
Q159719,3
Q159334,4
Q15640612,5
Q15324,2
Q15284,5
Q15243209,6
Q152081,1
Q15195406,4
Q1500350,5
Q149621,5
Q14757767,4
Q14350,3
Q1410668,3
Q1394476,3
Q1377575,2
Q1353183,3
Q134447,4
Q133215,3
Q133056,2
Q13221722,3
Q13220204,2
Q1311958,4
Q1303167,3
Q130003,3
Q12518,2
Q12516,3
Q1248784,3
Q123705,3
Q12323,3
Q12284,4
Q12280,4
Q121359,2
Q1210950,2
Q11755880,3
Q11707,3
Q11315,3
Q11303,3
Q1115575,4
Q1107656,1
Q10864048,1
Q1076486,2
Q105731,3
Q105190,3
Q1048525,3
Q102496,5
Q28872924,1
Q15617994,1
Q159313,2
Q24398318,3
Q327333,2
Q43229,1
Q860861,1
Q4989906,1
1 place_type level
2 Q9842 4
3 Q9430 3
4 Q928830 4
5 Q9259 1
6 Q91028 5
7 Q8514 2
8 Q8502 2
9 Q83405 3
10 Q82794 2
11 Q820477 1
12 Q811979 1
13 Q8072 2
14 Q79007 2
15 Q786014 3
16 Q75848 2
17 Q75520 2
18 Q728937 4
19 Q7275 2
20 Q719456 3
21 Q7075 3
22 Q697295 4
23 Q6852233 2
24 Q682943 3
25 Q665487 5
26 Q655686 3
27 Q643589 5
28 Q641226 2
29 Q631305 2
30 Q6256 2
31 Q6023295 2
32 Q5773747 5
33 Q56061 1
34 Q55659167 4
35 Q55488 4
36 Q55465477 3
37 Q54050 2
38 Q532 3
39 Q53060 2
40 Q52177058 4
41 Q515716 5
42 Q5153984 4
43 Q515 3
44 Q5144960 5
45 Q5119 4
46 Q5119 4
47 Q5107 2
48 Q5084 4
49 Q5031071 4
50 Q5003624 2
51 Q4989906 1
52 Q4976993 3
53 Q486972 1
54 Q486972 2
55 Q483110 3
56 Q4830453 4
57 Q47521 3
58 Q473972 1
59 Q46831 2
60 Q46614560 5
61 Q44782 3
62 Q44613 4
63 Q44539 4
64 Q44494 2
65 Q44377 2
66 Q4421 2
67 Q43501 2
68 Q4286337 3
69 Q42523 3
70 Q41176 2
71 Q40357 3
72 Q4022 4
73 Q40080 2
74 Q39816 2
75 Q39715 3
76 Q39614 1
77 Q3957 3
78 Q3947 4
79 Q3914 3
80 Q38723 2
81 Q38720 3
82 Q3623867 5
83 Q35666 2
84 Q355304 3
85 Q35509 2
86 Q35112127 3
87 Q34985575 4
88 Q34876 5
89 Q34763 2
90 Q34627 4
91 Q3455524 3
92 Q34442 4
93 Q33837 2
94 Q33506 3
95 Q32815 4
96 Q3257686 2
97 Q3240715 2
98 Q3191695 5
99 Q3153117 2
100 Q30198 2
101 Q30139652 3
102 Q294422 3
103 Q2870166 3
104 Q27686 3
105 Q274153 3
106 Q271669 1
107 Q2659904 2
108 Q24529780 2
109 Q24354 3
110 Q2354973 4
111 Q23442 2
112 Q23413 3
113 Q23397 3
114 Q2327515 4
115 Q2311958 5
116 Q22927291 6
117 Q22698 1
118 Q2175765 4
119 Q205495 4
120 Q204832 3
121 Q2042028 2
122 Q202216 6
123 Q1970725 3
124 Q194203 5
125 Q194195 2
126 Q190429 2
127 Q185187 3
128 Q185113 2
129 Q183366 2
130 Q1799794 1
131 Q1788454 4
132 Q1785071 3
133 Q1777138 3
134 Q177634 2
135 Q177380 2
136 Q174814 4
137 Q174782 2
138 Q17350442 2
139 Q17343829 3
140 Q17334923 0
141 Q17018380 3
142 Q16970 4
143 Q16917 3
144 Q16831714 4
145 Q165 3
146 Q160742 4
147 Q159719 3
148 Q159334 4
149 Q15640612 5
150 Q15324 2
151 Q15284 5
152 Q15243209 6
153 Q152081 1
154 Q15195406 4
155 Q1500350 5
156 Q149621 5
157 Q14757767 4
158 Q14350 3
159 Q1410668 3
160 Q1394476 3
161 Q1377575 2
162 Q1353183 3
163 Q134447 4
164 Q133215 3
165 Q133056 2
166 Q13221722 3
167 Q13220204 2
168 Q1311958 4
169 Q1303167 3
170 Q130003 3
171 Q12518 2
172 Q12516 3
173 Q1248784 3
174 Q123705 3
175 Q12323 3
176 Q12284 4
177 Q12280 4
178 Q121359 2
179 Q1210950 2
180 Q11755880 3
181 Q11707 3
182 Q11315 3
183 Q11303 3
184 Q1115575 4
185 Q1107656 1
186 Q10864048 1
187 Q1076486 2
188 Q105731 3
189 Q105190 3
190 Q1048525 3
191 Q102496 5
192 Q28872924 1
193 Q15617994 1
194 Q159313 2
195 Q24398318 3
196 Q327333 2
197 Q43229 1
198 Q860861 1
199 Q4989906 1

View File

@@ -1,195 +0,0 @@
Q9842
Q9430
Q928830
Q9259
Q91028
Q8514
Q8502
Q83405
Q82794
Q820477
Q811979
Q8072
Q79007
Q786014
Q75848
Q75520
Q728937
Q7275
Q719456
Q7075
Q697295
Q6852233
Q682943
Q665487
Q655686
Q643589
Q641226
Q631305
Q6256
Q6023295
Q5773747
Q56061
Q55659167
Q55488
Q55465477
Q54050
Q532
Q53060
Q52177058
Q515716
Q5153984
Q515
Q5144960
Q5119
Q5107
Q5084
Q5031071
Q5003624
Q4989906
Q4976993
Q486972
Q483110
Q4830453
Q47521
Q473972
Q46831
Q46614560
Q44782
Q44613
Q44539
Q44494
Q44377
Q4421
Q43501
Q4286337
Q42523
Q41176
Q40357
Q4022
Q40080
Q39816
Q39715
Q39614
Q3957
Q3947
Q3914
Q38723
Q38720
Q3623867
Q35666
Q355304
Q35509
Q35112127
Q34985575
Q34876
Q34763
Q34627
Q3455524
Q34442
Q33837
Q33506
Q32815
Q3257686
Q3240715
Q3191695
Q3153117
Q30198
Q30139652
Q294422
Q2870166
Q27686
Q274153
Q271669
Q2659904
Q24529780
Q24354
Q2354973
Q23442
Q23413
Q23397
Q2327515
Q2311958
Q22927291
Q22698
Q2175765
Q205495
Q204832
Q2042028
Q202216
Q1970725
Q194203
Q194195
Q190429
Q185187
Q185113
Q183366
Q1799794
Q1788454
Q1785071
Q1777138
Q177634
Q177380
Q174814
Q174782
Q17350442
Q17343829
Q17334923
Q17018380
Q16970
Q16917
Q16831714
Q165
Q160742
Q159719
Q159334
Q15640612
Q15324
Q15284
Q15243209
Q152081
Q15195406
Q1500350
Q149621
Q14757767
Q14350
Q1410668
Q1394476
Q1377575
Q1353183
Q134447
Q133215
Q133056
Q13221722
Q13220204
Q1311958
Q1303167
Q130003
Q12518
Q12516
Q1248784
Q123705
Q12323
Q12284
Q12280
Q121359
Q1210950
Q11755880
Q11707
Q11315
Q11303
Q1115575
Q1107656
Q10864048
Q1076486
Q105731
Q105190
Q1048525
Q102496
Q28872924
Q15617994
Q159313
Q24398318
Q327333
Q43229
Q860861

View File

@@ -1,200 +0,0 @@
## Wikidata place types and related OSM Tags
Wikidata does not have any official ontologies, however the [DBpedia project](https://wiki.dbpedia.org/) has created an [ontology](https://wiki.dbpedia.org/services-resources/ontology) that covered [place types](http://mappings.dbpedia.org/server/ontology/classes/#Place). The table below used the DBpedia place ontology as a starting point, and is provided as a cross-reference to the relevant OSM tags.
The Wikidata place types listed in the table below can be used in conjunction with the [Wikidata Query Service](https://query.wikidata.org/) to retrieve instances of those place types from the Wikidata knowledgebase.
```
SELECT ?item ?lat ?lon
WHERE {
?item wdt:P31*/wdt:P279*wd:Q9430; wdt:P625 ?pt.
?item p:P625?loc.
?loc psv:P625?cnode.
?cnode wikibase:geoLatitude?lat.
?cnode wikibase:geoLongitude?lon.
}
```
An example json return for all instances of the Wikidata item "Q9430" (Ocean) can be seen at [json](https://query.wikidata.org/bigdata/namespace/wdq/sparql?format=json&query=SELECT?item?lat?lon%20WHERE{?item%20wdt:P31*/wdt:P279*wd:Q9430;wdt:P625?pt.?item%20p:P625?loc.?loc%20psv:P625?cnode.?cnode%20wikibase:geoLatitude?lat.?cnode%20wikibase:geoLongitude?lon.})
**NOTE** the OSM tags listed are those listed in the wikidata entries, and not all the possible matches for tags within OSM.
title | concept | OSM Tag |
-----------|---------------------------------------|------------------|
[Q17334923](https://www.wikidata.org/entity/Q17334923) | Location | |
[Q811979](https://www.wikidata.org/entity/Q811979) | Architectural Structure | |
[Q194195](https://www.wikidata.org/entity/Q194195) | Amusement park |
[Q204832](https://www.wikidata.org/entity/Q204832) | Roller coaster | [attraction=roller_coaster](https://wiki.openstreetmap.org/wiki/Tag:attraction=roller_coaster) |
[Q2870166](https://www.wikidata.org/entity/Q2870166) | Water ride | |
[Q641226](https://www.wikidata.org/entity/Q641226) | Arena | [amenity=events_centre](https://wiki.openstreetmap.org/wiki/Tag:amenity=events_centre) |
[Q41176](https://www.wikidata.org/entity/Q41176) | Building | [building=yes](https://wiki.openstreetmap.org/wiki/Key:building) |
[Q1303167](https://www.wikidata.org/entity/Q1303167) | Barn | [building=barn](https://wiki.openstreetmap.org/wiki/Tag:building=barn) |
[Q655686](https://www.wikidata.org/entity/Q655686) | Commercial building | [building=commercial](https://wiki.openstreetmap.org/wiki/Tag:building=commercial) |
[Q4830453](https://www.wikidata.org/entity/Q4830453) | Business | |
[Q7075](https://www.wikidata.org/entity/Q7075) | Library | [amenity=library](https://wiki.openstreetmap.org/wiki/Tag:amenity=library) |
[Q133215](https://www.wikidata.org/entity/Q133215) | Casino | [amenity=casino](https://wiki.openstreetmap.org/wiki/Tag:amenity=casino) |
[Q23413](https://www.wikidata.org/entity/Q23413) | Castle | [historic=castle](https://wiki.openstreetmap.org/wiki/Tag:historic=castle) |
[Q83405](https://www.wikidata.org/entity/Q83405) | Factory | |
[Q53060](https://www.wikidata.org/entity/Q53060) | Gate | [barrier=gate](https://wiki.openstreetmap.org/wiki/Tag:barrier=gate) |cnode%20wikibase:geoLatitude?lat.?cnode%20wikibase:geoLongitude?lon.})
[Q11755880](https://www.wikidata.org/entity/Q11755880) | Residential Building | [building=residential](https://wiki.openstreetmap.org/wiki/Tag:building=residential) |
[Q3947](https://www.wikidata.org/entity/Q3947) | House | [building=house](https://wiki.openstreetmap.org/wiki/Tag:building=house) |
[Q35112127](https://www.wikidata.org/entity/Q35112127) | Historic Building | |
[Q5773747](https://www.wikidata.org/entity/Q5773747) | Historic house | |
[Q38723](https://www.wikidata.org/entity/Q38723) | Higher Education Institution |
[Q3914](https://www.wikidata.org/entity/Q3914) | School | [amenity=school](https://wiki.openstreetmap.org/wiki/Tag:amenity=school) |
[Q9842](https://www.wikidata.org/entity/Q9842) | Primary school | |
[Q159334](https://www.wikidata.org/entity/Q159334) | Secondary school | |
[Q16917](https://www.wikidata.org/entity/Q16917) | Hospital | [amenity=hospital](https://wiki.openstreetmap.org/wiki/Tag:amenity=hospital), [healthcare=hospital](https://wiki.openstreetmap.org/wiki/Tag:healthcare=hospital), [building=hospital](https://wiki.openstreetmap.org/wiki/Tag:building=hospital) |
[Q27686](https://www.wikidata.org/entity/Q27686) | Hotel | [tourism=hotel](https://wiki.openstreetmap.org/wiki/Tag:tourism=hotel), [building=hotel](https://wiki.openstreetmap.org/wiki/Tag:building=hotel) |
[Q33506](https://www.wikidata.org/entity/Q33506) | Museum | [tourism=museum](https://wiki.openstreetmap.org/wiki/Tag:tourism=museum) |
[Q40357](https://www.wikidata.org/entity/Q40357) | Prison | [amenity=prison](https://wiki.openstreetmap.org/wiki/Tag:amenity=prison) |
[Q24398318](https://www.wikidata.org/entity/Q24398318) | Religious Building | |
[Q160742](https://www.wikidata.org/entity/Q160742) | Abbey | |
[Q16970](https://www.wikidata.org/entity/Q16970) | Church (building) | [building=church](https://wiki.openstreetmap.org/wiki/Tag:building=church) |
[Q44613](https://www.wikidata.org/entity/Q44613) | Monastery | [amenity=monastery](https://wiki.openstreetmap.org/wiki/Tag:amenity=monastery) |
[Q32815](https://www.wikidata.org/entity/Q32815) | Mosque | [building=mosque](https://wiki.openstreetmap.org/wiki/Tag:building=mosque) |
[Q697295](https://www.wikidata.org/entity/Q697295) | Shrine | [building=shrine](https://wiki.openstreetmap.org/wiki/Tag:building=shrine) |
[Q34627](https://www.wikidata.org/entity/Q34627) | Synagogue | [building=synagogue](https://wiki.openstreetmap.org/wiki/Tag:building=synagogue) |
[Q44539](https://www.wikidata.org/entity/Q44539) | Temple | [building=temple](https://wiki.openstreetmap.org/wiki/Tag:building=temple) |
[Q11707](https://www.wikidata.org/entity/Q11707) | Restaurant | [amenity=restaurant](https://wiki.openstreetmap.org/wiki/Tag:amenity=restaurant) |
[Q11315](https://www.wikidata.org/entity/Q11315) | Shopping mall | [shop=mall](https://wiki.openstreetmap.org/wiki/Tag:shop=mall), [shop=shopping_centre](https://wiki.openstreetmap.org/wiki/Tag:shop=shopping_centre) |
[Q11303](https://www.wikidata.org/entity/Q11303) | Skyscraper | |
[Q17350442](https://www.wikidata.org/entity/Q17350442) | Venue | |
[Q41253](https://www.wikidata.org/entity/Q41253) | Movie Theater | [amenity=cinema](https://wiki.openstreetmap.org/wiki/Tag:amenity=cinema) |
[Q483110](https://www.wikidata.org/entity/Q483110) | Stadium | [leisure=stadium](https://wiki.openstreetmap.org/wiki/Tag:leisure=stadium), [building=stadium](https://wiki.openstreetmap.org/wiki/Tag:building=stadium) |
[Q24354](https://www.wikidata.org/entity/Q24354) | Theater (structure) | [amenity=theatre](https://wiki.openstreetmap.org/wiki/Tag:amenity=theatre) |
[Q121359](https://www.wikidata.org/entity/Q121359) | Infrastructure | |
[Q1248784](https://www.wikidata.org/entity/Q1248784) | Airport | |
[Q12323](https://www.wikidata.org/entity/Q12323) | Dam | [waterway=dam](https://wiki.openstreetmap.org/wiki/Tag:waterway=dam) |
[Q1353183](https://www.wikidata.org/entity/Q1353183) | Launch pad | |
[Q105190](https://www.wikidata.org/entity/Q105190) | Levee | [man_made=dyke](https://wiki.openstreetmap.org/wiki/Tag:man_made=dyke) |
[Q105731](https://www.wikidata.org/entity/Q105731) | Lock (water navigation) | [lock=yes](https://wiki.openstreetmap.org/wiki/Key:lock) |
[Q44782](https://www.wikidata.org/entity/Q44782) | Port | |
[Q159719](https://www.wikidata.org/entity/Q159719) | Power station | [power=plant](https://wiki.openstreetmap.org/wiki/Tag:power=plant) |
[Q174814](https://www.wikidata.org/entity/Q174814) | Electrical substation | |
[Q134447](https://www.wikidata.org/entity/Q134447) | Nuclear power plant | [plant:source=nuclear](https://wiki.openstreetmap.org/wiki/Tag:plant:source=nuclear) |
[Q786014](https://www.wikidata.org/entity/Q786014) | Rest area | [highway=rest_area](https://wiki.openstreetmap.org/wiki/Tag:highway=rest_area), [highway=services](https://wiki.openstreetmap.org/wiki/Tag:highway=services) |
[Q12280](https://www.wikidata.org/entity/Q12280) | Bridge | [bridge=* ](https://wiki.openstreetmap.org/wiki/Key:bridge), [man_made=bridge](https://wiki.openstreetmap.org/wiki/Tag:man_made=bridge) |
[Q728937](https://www.wikidata.org/entity/Q728937) | Railroad Line | [railway=rail](https://wiki.openstreetmap.org/wiki/Tag:railway=rail) |
[Q1311958](https://www.wikidata.org/entity/Q1311958) | Railway Tunnel | |
[Q34442](https://www.wikidata.org/entity/Q34442) | Road | [highway=* ](https://wiki.openstreetmap.org/wiki/Key:highway), [route=road](https://wiki.openstreetmap.org/wiki/Tag:route=road) |
[Q1788454](https://www.wikidata.org/entity/Q1788454) | Road junction | |
[Q44377](https://www.wikidata.org/entity/Q44377) | Tunnel | [tunnel=* ](https://wiki.openstreetmap.org/wiki/Key:tunnel) |
[Q5031071](https://www.wikidata.org/entity/Q5031071) | Canal tunnel | |
[Q719456](https://www.wikidata.org/entity/Q719456) | Station | [public_transport=station](https://wiki.openstreetmap.org/wiki/Tag:public_transport=station) |
[Q205495](https://www.wikidata.org/entity/Q205495) | Filling station | [amenity=fuel](https://wiki.openstreetmap.org/wiki/Tag:amenity=fuel) |
[Q928830](https://www.wikidata.org/entity/Q928830) | Metro station | [station=subway](https://wiki.openstreetmap.org/wiki/Tag:station=subway) |
[Q55488](https://www.wikidata.org/entity/Q55488) | Train station | [railway=station](https://wiki.openstreetmap.org/wiki/Tag:railway=station) |
[Q2175765](https://www.wikidata.org/entity/Q2175765) | Tram stop | [railway=tram_stop](https://wiki.openstreetmap.org/wiki/Tag:railway=tram_stop), [public_transport=stop_position](https://wiki.openstreetmap.org/wiki/Tag:public_transport=stop_position) |
[Q6852233](https://www.wikidata.org/entity/Q6852233) | Military building | |
[Q44494](https://www.wikidata.org/entity/Q44494) | Mill (grinding) | |
[Q185187](https://www.wikidata.org/entity/Q185187) | Watermill | [man_made=watermill](https://wiki.openstreetmap.org/wiki/Tag:man_made=watermill) |
[Q38720](https://www.wikidata.org/entity/Q38720) | Windmill | [man_made=windmill](https://wiki.openstreetmap.org/wiki/Tag:man_made=windmill) |
[Q4989906](https://www.wikidata.org/entity/Q4989906) | Monument | [historic=monument](https://wiki.openstreetmap.org/wiki/Tag:historic=monument) |
[Q5003624](https://www.wikidata.org/entity/Q5003624) | Memorial | [historic=memorial](https://wiki.openstreetmap.org/wiki/Tag:historic=memorial) |
[Q271669](https://www.wikidata.org/entity/Q271669) | Landform | |
[Q190429](https://www.wikidata.org/entity/Q190429) | Depression (geology) | |
[Q17018380](https://www.wikidata.org/entity/Q17018380) | Bight (geography) | |
[Q54050](https://www.wikidata.org/entity/Q54050) | Hill | |
[Q1210950](https://www.wikidata.org/entity/Q1210950) | Channel (geography) | |
[Q23442](https://www.wikidata.org/entity/Q23442) | Island | [place=island](https://wiki.openstreetmap.org/wiki/Tag:place=island) |
[Q42523](https://www.wikidata.org/entity/Q42523) | Atoll | |
[Q34763](https://www.wikidata.org/entity/Q34763) | Peninsula | |
[Q355304](https://www.wikidata.org/entity/Q355304) | Watercourse | |
[Q30198](https://www.wikidata.org/entity/Q30198) | Marsh | [wetland=marsh](https://wiki.openstreetmap.org/wiki/Tag:wetland=marsh) |
[Q75520](https://www.wikidata.org/entity/Q75520) | Plateau | |
[Q2042028](https://www.wikidata.org/entity/Q2042028) | Ravine | |
[Q631305](https://www.wikidata.org/entity/Q631305) | Rock formation | |
[Q12516](https://www.wikidata.org/entity/Q12516) | Pyramid | |
[Q1076486](https://www.wikidata.org/entity/Q1076486) | Sports venue | |
[Q682943](https://www.wikidata.org/entity/Q682943) | Cricket field | [sport=cricket](https://wiki.openstreetmap.org/wiki/Tag:sport=cricket) |
[Q1048525](https://www.wikidata.org/entity/Q1048525) | Golf course | [leisure=golf_course](https://wiki.openstreetmap.org/wiki/Tag:leisure=golf_course) |
[Q1777138](https://www.wikidata.org/entity/Q1777138) | Race track | [highway=raceway](https://wiki.openstreetmap.org/wiki/Tag:highway=raceway) |
[Q130003](https://www.wikidata.org/entity/Q130003) | Ski resort | |
[Q174782](https://www.wikidata.org/entity/Q174782) | Town square | [place=square](https://wiki.openstreetmap.org/wiki/Tag:place=square) |
[Q12518](https://www.wikidata.org/entity/Q12518) | Tower | [building=tower](https://wiki.openstreetmap.org/wiki/Tag:building=tower), [man_made=tower](https://wiki.openstreetmap.org/wiki/Tag:man_made=tower) |
[Q39715](https://www.wikidata.org/entity/Q39715) | Lighthouse | [man_made=lighthouse](https://wiki.openstreetmap.org/wiki/Tag:man_made=lighthouse) |
[Q274153](https://www.wikidata.org/entity/Q274153) | Water tower | [building=water_tower](https://wiki.openstreetmap.org/wiki/Tag:building=water_tower), [man_made=water_tower](https://wiki.openstreetmap.org/wiki/Tag:man_made=water_tower) |
[Q43501](https://www.wikidata.org/entity/Q43501) | Zoo | [tourism=zoo](https://wiki.openstreetmap.org/wiki/Tag:tourism=zoo) |
[Q39614](https://www.wikidata.org/entity/Q39614) | Cemetery | [amenity=grave_yard](https://wiki.openstreetmap.org/wiki/Tag:amenity=grave_yard), [landuse=cemetery](https://wiki.openstreetmap.org/wiki/Tag:landuse=cemetery) |
[Q152081](https://www.wikidata.org/entity/Q152081) | Concentration camp | |
[Q1107656](https://www.wikidata.org/entity/Q1107656) | Garden | [leisure=garden](https://wiki.openstreetmap.org/wiki/Tag:leisure=garden) |
[Q820477](https://www.wikidata.org/entity/Q820477) | Mine | |
[Q33837](https://www.wikidata.org/entity/Q33837) | Archipelago | [place=archipelago](https://wiki.openstreetmap.org/wiki/Tag:place=archipelago) |
[Q40080](https://www.wikidata.org/entity/Q40080) | Beach | [natural=beach](https://wiki.openstreetmap.org/wiki/Tag:natural=beach) |
[Q15324](https://www.wikidata.org/entity/Q15324) | Body of water | [natural=water](https://wiki.openstreetmap.org/wiki/Tag:natural=water) |
[Q23397](https://www.wikidata.org/entity/Q23397) | Lake | [water=lake](https://wiki.openstreetmap.org/wiki/Tag:water=lake) |
[Q9430](https://www.wikidata.org/entity/Q9430) | Ocean | |
[Q165](https://www.wikidata.org/entity/Q165) | Sea | |
[Q47521](https://www.wikidata.org/entity/Q47521) | Stream | |
[Q12284](https://www.wikidata.org/entity/Q12284) | Canal | [waterway=canal](https://wiki.openstreetmap.org/wiki/Tag:waterway=canal) |
[Q4022](https://www.wikidata.org/entity/Q4022) | River | [waterway=river](https://wiki.openstreetmap.org/wiki/Tag:waterway=river), [type=waterway](https://wiki.openstreetmap.org/wiki/Relation:waterway) |
[Q185113](https://www.wikidata.org/entity/Q185113) | Cape | [natural=cape](https://wiki.openstreetmap.org/wiki/Tag:natural=cape) |
[Q35509](https://www.wikidata.org/entity/Q35509) | Cave | [natural=cave_entrance](https://wiki.openstreetmap.org/wiki/Tag:natural=cave_entrance) |
[Q8514](https://www.wikidata.org/entity/Q8514) | Desert | |
[Q4421](https://www.wikidata.org/entity/Q4421) | Forest | [natural=wood](https://wiki.openstreetmap.org/wiki/Tag:natural=wood) |
[Q35666](https://www.wikidata.org/entity/Q35666) | Glacier | [natural=glacier](https://wiki.openstreetmap.org/wiki/Tag:natural=glacier) |
[Q177380](https://www.wikidata.org/entity/Q177380) | Hot spring | |
[Q8502](https://www.wikidata.org/entity/Q8502) | Mountain | [natural=peak](https://wiki.openstreetmap.org/wiki/Tag:natural=peak) |
[Q133056](https://www.wikidata.org/entity/Q133056) | Mountain pass | |
[Q46831](https://www.wikidata.org/entity/Q46831) | Mountain range | |
[Q39816](https://www.wikidata.org/entity/Q39816) | Valley | [natural=valley](https://wiki.openstreetmap.org/wiki/Tag:natural=valley) |
[Q8072](https://www.wikidata.org/entity/Q8072) | Volcano | [natural=volcano](https://wiki.openstreetmap.org/wiki/Tag:natural=volcano) |
[Q43229](https://www.wikidata.org/entity/Q43229) | Organization | |
[Q327333](https://www.wikidata.org/entity/Q327333) | Government agency | [office=government](https://wiki.openstreetmap.org/wiki/Tag:office=government)|
[Q22698](https://www.wikidata.org/entity/Q22698) | Park | [leisure=park](https://wiki.openstreetmap.org/wiki/Tag:leisure=park) |
[Q159313](https://www.wikidata.org/entity/Q159313) | Urban agglomeration | |
[Q177634](https://www.wikidata.org/entity/Q177634) | Community | |
[Q5107](https://www.wikidata.org/entity/Q5107) | Continent | [place=continent](https://wiki.openstreetmap.org/wiki/Tag:place=continent) |
[Q6256](https://www.wikidata.org/entity/Q6256) | Country | [place=country](https://wiki.openstreetmap.org/wiki/Tag:place=country) |
[Q75848](https://www.wikidata.org/entity/Q75848) | Gated community | |
[Q3153117](https://www.wikidata.org/entity/Q3153117) | Intercommunality | |
[Q82794](https://www.wikidata.org/entity/Q82794) | Region | |
[Q56061](https://www.wikidata.org/entity/Q56061) | Administrative division | [boundary=administrative](https://wiki.openstreetmap.org/wiki/Tag:boundary=administrative) |
[Q665487](https://www.wikidata.org/entity/Q665487) | Diocese | |
[Q4976993](https://www.wikidata.org/entity/Q4976993) | Parish | [boundary=civil_parish](https://wiki.openstreetmap.org/wiki/Tag:boundary=civil_parish) |
[Q194203](https://www.wikidata.org/entity/Q194203) | Arrondissements of France | |
[Q91028](https://www.wikidata.org/entity/Q91028) | Arrondissements of Belgium | |
[Q3623867](https://www.wikidata.org/entity/Q3623867) | Arrondissements of Benin | |
[Q2311958](https://www.wikidata.org/entity/Q2311958) | Canton (country subdivision) | [political_division=canton](https://wiki.openstreetmap.org/wiki/FR:Cantons_in_France) |
[Q643589](https://www.wikidata.org/entity/Q643589) | Department | |
[Q202216](https://www.wikidata.org/entity/Q202216) | Overseas department and region | |
[Q149621](https://www.wikidata.org/entity/Q149621) | District | [place=district](https://wiki.openstreetmap.org/wiki/Tag:place=district) |
[Q15243209](https://www.wikidata.org/wiki/Q15243209) | Historic district | |
[Q5144960](https://www.wikidata.org/entity/Q5144960) | Microregion | |
[Q15284](https://www.wikidata.org/entity/Q15284) | Municipality | |
[Q515716](https://www.wikidata.org/entity/Q515716) | Prefecture | |
[Q34876](https://www.wikidata.org/entity/Q34876) | Province | |
[Q3191695](https://www.wikidata.org/entity/Q3191695) | Regency (Indonesia) | |
[Q1970725](https://www.wikidata.org/entity/Q1970725) | Natural region | |
[Q486972](https://www.wikidata.org/entity/Q486972) | Human settlement | |
[Q515](https://www.wikidata.org/entity/Q515) | City | [place=city](https://wiki.openstreetmap.org/wiki/Tag:place=city) |
[Q5119](https://www.wikidata.org/entity/Q5119) | Capital city | [capital=yes](https://wiki.openstreetmap.org/wiki/Key:capital) |
[Q4286337](https://www.wikidata.org/entity/Q4286337) | City district | |
[Q1394476](https://www.wikidata.org/entity/Q1394476) | Civil township | |
[Q1115575](https://www.wikidata.org/entity/Q1115575) | Civil parish | [designation=civil_parish](https://wiki.openstreetmap.org/wiki/Tag:designation=civil_parish) |
[Q5153984](https://www.wikidata.org/entity/Q5153984) | Commune-level subdivisions | |
[Q123705](https://www.wikidata.org/entity/Q123705) | Neighbourhood | [place=neighbourhood](https://wiki.openstreetmap.org/wiki/Tag:place=neighbourhood) |
[Q1500350](https://www.wikidata.org/entity/Q1500350) | Townships of China | |
[Q17343829](https://www.wikidata.org/entity/Q17343829) | Unincorporated Community | |
[Q3957](https://www.wikidata.org/entity/Q3957) | Town | [place=town](https://wiki.openstreetmap.org/wiki/Tag:place=town) |
[Q532](https://www.wikidata.org/entity/Q532) | Village | [place=village](https://wiki.openstreetmap.org/wiki/Tag:place=village) |
[Q5084](https://www.wikidata.org/entity/Q5084) | Hamlet | [place=hamlet](https://wiki.openstreetmap.org/wiki/Tag:place=hamlet) |
[Q7275](https://www.wikidata.org/entity/Q7275) | State | |
[Q79007](https://www.wikidata.org/entity/Q79007) | Street | |
[Q473972](https://www.wikidata.org/entity/Q473972) | Protected area | [boundary=protected_area](https://wiki.openstreetmap.org/wiki/Tag:boundary=protected_area) |
[Q1377575](https://www.wikidata.org/entity/Q1377575) | Wildlife refuge | |
[Q1410668](https://www.wikidata.org/entity/Q1410668) | National Wildlife Refuge | [protection_title=National Wildlife Refuge](ownership=national), [ownership=national](https://wiki.openstreetmap.org/wiki/Tag:ownership=national)|
[Q9259](https://www.wikidata.org/entity/Q9259) | World Heritage Site | |
---
### Future Work
The Wikidata improvements to Nominatim can be further enhanced by:
- continuing to add new Wikidata links to OSM objects
- increasing the number of place types accounted for in the wikipedia_articles table
- working to use place types in the wikipedia_article matching process

File diff suppressed because one or more lines are too long

38126
data/us_postcode.sql Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
SET statement_timeout = 0;
SET client_encoding = 'UTF8';
SET check_function_bodies = false;
SET client_min_messages = warning;
SET search_path = public, pg_catalog;
SET default_tablespace = '';
SET default_with_oids = false;
CREATE TABLE us_postcode (
postcode text,
x double precision,
y double precision
);

View File

@@ -5,45 +5,16 @@
configure_file(mkdocs.yml ../mkdocs.yml)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/appendix)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/data-sources)
set (DOC_SOURCES
admin
develop
api
index.md
extra.css
styles.css
data-sources/overview.md
)
foreach (src ${DOC_SOURCES})
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/${src} ${CMAKE_CURRENT_BINARY_DIR}/${src}
)
endforeach()
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/us-tiger/README.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/US-Tiger.md
)
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/gb-postcodes/README.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/GB-Postcodes.md
)
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/country-grid/README.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/Country-Grid.md
)
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/country-grid/mexico.quad.png ${CMAKE_CURRENT_BINARY_DIR}/data-sources/mexico.quad.png
)
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/wikipedia-wikidata/README.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/Wikipedia-Wikidata.md
)
ADD_CUSTOM_TARGET(doc
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/admin ${CMAKE_CURRENT_BINARY_DIR}/admin
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/develop ${CMAKE_CURRENT_BINARY_DIR}/develop
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/api ${CMAKE_CURRENT_BINARY_DIR}/api
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/index.md ${CMAKE_CURRENT_BINARY_DIR}/index.md
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/extra.css ${CMAKE_CURRENT_BINARY_DIR}/extra.css
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Centos-7.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Centos-7.md
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Centos-8.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Centos-8.md
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Ubuntu-16.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Ubuntu-16.md
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Ubuntu-18.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Ubuntu-18.md
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Ubuntu-20.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Ubuntu-20.md
COMMAND mkdocs build -d ${CMAKE_CURRENT_BINARY_DIR}/../site-html -f ${CMAKE_CURRENT_BINARY_DIR}/../mkdocs.yml
)

View File

@@ -1,171 +0,0 @@
# Advanced installations
This page contains instructions for setting up multiple countries in
your Nominatim database. It is assumed that you have already successfully
installed the Nominatim software itself, if not return to the
[installation page](Installation.md).
## Importing multiple regions
To import multiple regions in your database, you need to configure and run `utils/import_multiple_regions.sh` file. This script will set up the update directory which has the following structure:
```bash
update
   ├── europe
   │   ├── andorra
   │   │   └── sequence.state
   │   └── monaco
   │   └── sequence.state
   └── tmp
├── combined.osm.pbf
└── europe
├── andorra-latest.osm.pbf
└── monaco-latest.osm.pbf
```
The `sequence.state` files will contain the sequence ID, which will be used by pyosmium to get updates. The tmp folder is used for import dump.
### Configuring multiple regions
The file `import_multiple_regions.sh` needs to be edited as per your requirement:
1. List of countries. eg:
COUNTRIES="europe/monaco europe/andorra"
2. Path to Build directory. eg:
NOMINATIMBUILD="/srv/nominatim/build"
3. Path to Update directory. eg:
UPDATEDIR="/srv/nominatim/update"
4. Replication URL. eg:
BASEURL="https://download.geofabrik.de"
DOWNCOUNTRYPOSTFIX="-latest.osm.pbf"
!!! tip
If your database already exists and you want to add more countries, replace the setting up part
`${SETUPFILE} --osm-file ${UPDATEDIR}/tmp/combined.osm.pbf --all 2>&1`
with `${UPDATEFILE} --import-file ${UPDATEDIR}/tmp/combined.osm.pbf 2>&1`.
### Setting up multiple regions
Run the following command from your Nominatim directory after configuring the file.
bash ./utils/import_multiple_regions.sh
!!! danger "Important"
This file uses osmium-tool. It must be installed before executing the import script.
Installation instructions can be found [here](https://osmcode.org/osmium-tool/manual.html#installation).
### Updating multiple regions
To import multiple regions in your database, you need to configure and run ```utils/update_database.sh```.
This uses the update directory set up while setting up the DB.
### Configuring multiple regions
The file `update_database.sh` needs to be edited as per your requirement:
1. List of countries. eg:
COUNTRIES="europe/monaco europe/andorra"
2. Path to Build directory. eg:
NOMINATIMBUILD="/srv/nominatim/build"
3. Path to Update directory. eg:
UPDATEDIR="/srv/nominatim/update"
4. Replication URL. eg:
BASEURL="https://download.geofabrik.de"
DOWNCOUNTRYPOSTFIX="-updates"
5. Followup can be set according to your installation. eg: For Photon,
FOLLOWUP="curl http://localhost:2322/nominatim-update"
will handle the indexing.
### Updating the database
Run the following command from your Nominatim directory after configuring the file.
bash ./utils/update_database.sh
This will get diffs from the replication server, import diffs and index the database. The default replication server in the script([Geofabrik](https://download.geofabrik.de)) provides daily updates.
## Importing Nominatim to 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.
### 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 nomrmalization library in `build/module/nominatim.so`. Copy the file to
the database server at a location where it is readable and executable by the
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 `settings/local.php` file:
```php
@define('CONST_Database_Module_Path', '<directory on the database server where nominatim.so resides>');
```
Now change the `CONST_Database_DSN` to point to your remote server and continue
to follow the [standard instructions for importing](/admin/Import).

View File

@@ -22,38 +22,21 @@ then you can resume with the following command:
If the reported rank is 26 or higher, you can also safely add `--index-noanalyse`.
### PostgreSQL crashed "invalid page in block"
Usually serious problem, can be a hardware issue, not all data written to disc
for example. Check PostgreSQL log file and search PostgreSQL issues/mailing
list for hints.
If it happened during index creation you can try rerunning the step with
```sh
./utils/setup.php --create-search-indices --ignore-errors
```
Otherwise it's best to start the full setup from the beginning.
### PHP "open_basedir restriction in effect" warnings
PHP Warning: file_get_contents(): open_basedir restriction in effect.
`PHP Warning: file_get_contents(): open_basedir restriction in effect.`
You need to adjust the
[open_basedir](https://www.php.net/manual/en/ini.core.php#ini.open-basedir)
setting in your PHP configuration (`php.ini` file). By default this setting may
look like this:
You need to adjust the [open_basedir](http://www.php.net/manual/en/ini.core.php#ini.open-basedir) setting
in your PHP configuration (`php.ini file`). By default this setting may look like this:
open_basedir = /srv/http/:/home/:/tmp/:/usr/share/pear/
Either add reported directories to the list or disable this setting temporarily
by adding ";" at the beginning of the line. Don't forget to enable this setting
again once you are done with the PHP command line operations.
Either add reported directories to the list or disable this setting temporarily by
dding ";" at the beginning of the line. Don't forget to enable this setting again
once you are done with the PHP command line operations.
### PHP timezeone warnings
### PHP timzeone warnings
The Apache log may contain lots of PHP warnings like this:
`PHP Warning: date_default_timezone_set() function.`
@@ -61,9 +44,9 @@ The Apache log may contain lots of PHP warnings like this:
You should set the default time zone as instructed in the warning in
your `php.ini` file. Find the entry about timezone and set it to
something like this:
; Defines the default timezone used by the date functions
; https://php.net/date.timezone
; http://php.net/date.timezone
date.timezone = 'America/Denver'
Or
@@ -83,41 +66,11 @@ server development libraries (`postgresql-server-dev-9.5` on Ubuntu)
and recompile (`cmake .. && make`).
### I see the error "ERROR: permission denied for language c"
`nominatim.so`, written in C, is required to be installed on the database
server. Some managed database (cloud) services like Amazon RDS do not allow
this. There is currently no work-around other than installing a database
on a non-managed machine.
### I see the error: "function transliteration(text) does not exist"
Reinstall the nominatim functions with `setup.php --create--functions`
and check for any errors, e.g. a missing `nominatim.so` file.
### I see the error: "ERROR: mmap (remap) failed"
This may be a simple out-of-memory error. Try reducing the memory used
for `--osm2pgsql-cache`. Also make sure that overcommitting memory is
allowed: `cat /proc/sys/vm/overcommit_memory` should print 0 or 1.
If you are using a flatnode file, then it may also be that the underlying
filesystem does not fully support 'mmap'. A notable candidate is virtualbox's
vboxfs.
### I see the error: "clang: Command not found" on CentOS
On CentOS 7 users reported `/opt/rh/llvm-toolset-7/root/usr/bin/clang: Command not found`.
Double-check clang is installed. Instead of `make` try running `make CLANG=true`.
### nominatim UPDATE failed: ERROR: buffer 179261 is not owned by resource owner Portal
Several users [reported this](https://github.com/openstreetmap/Nominatim/issues/1168) during the initial import of the database. It's
something PostgreSQL internal Nominatim doesn't control. And PostgreSQL forums
suggest it's threading related but definitely some kind of crash of a process.
Users reported either rebooting the server, different hardware or just trying
the import again worked.
### The website shows: "Could not get word tokens"
@@ -129,11 +82,10 @@ to get the full error message.
`could not connect to server: No such file or directory`
On CentOS v7 the PostgreSQL server is started with `systemd`. Check if
`/usr/lib/systemd/system/httpd.service` contains a line `PrivateTmp=true`. If
so then Apache cannot see the `/tmp/.s.PGSQL.5432` file. It's a good security
feature, so use the
[preferred solution](../appendix/Install-on-Centos-7/#adding-selinux-security-settings).
On CentOS v7 the PostgreSQL server is started with `systemd`.
Check if `/usr/lib/systemd/system/httpd.service` contains a line `PrivateTmp=true`.
If so then Apache cannot see the `/tmp/.s.PGSQL.5432` file. It's a good security feature,
so use the [preferred solution](../appendix/Install-on-Centos-7/#adding-selinux-security-settings).
However, you can solve this the quick and dirty way by commenting out that line and then run
@@ -143,10 +95,7 @@ However, you can solve this the quick and dirty way by commenting out that line
### Website reports "DB Error: insufficient permissions"
The user the webserver, e.g. Apache, runs under needs to have access to the
Nominatim database. You can find the user like
[this](https://serverfault.com/questions/125865/finding-out-what-user-apache-is-running-as),
for default Ubuntu operating system for example it's `www-data`.
The user the webserver, e.g. Apache, runs under needs to have access to the Nominatim database. You can find the user like [this](https://serverfault.com/questions/125865/finding-out-what-user-apache-is-running-as), for default Ubuntu operating system for example it's `www-data`.
1. Repeat the `createuser` step of the installation instructions.
@@ -169,10 +118,9 @@ Example error message
CONTEXT: PL/pgSQL function make_standard_name(text) line 5 at assignment]
```
The PostgreSQL database, i.e. user `postgres`, needs to have access to that file.
The Postgresql database, i.e. user postgres, needs to have access to that file.
The permission need to be read & executable by everybody, but not writeable
by everybody, e.g.
The permission need to be read & executable by everybody, e.g.
```
-rwxr-xr-x 1 nominatim nominatim 297984 build/module/nominatim.so
@@ -183,22 +131,22 @@ Try `chmod a+r nominatim.so; chmod a+x nominatim.so`.
When running SELinux, make sure that the
[context is set up correctly](../appendix/Install-on-Centos-7/#adding-selinux-security-settings).
When you recently updated your operating system, updated PostgreSQL to
a new version or moved files (e.g. the build directory) you should
recreate `nominatim.so`. Try
```
cd build
rm -r module/
cmake $main_Nominatim_path && make
```
### Setup.php fails with "DB Error: extension not found"
Make sure you have the PostgreSQL extensions "hstore" and "postgis" installed.
See the installation instructions for a full list of required packages.
Make sure you have the Postgres extensions hstore and postgis installed.
See the installation instruction for a full list of required packages.
### Setup.php reports "Cannot redeclare getDB()"
`Cannot redeclare getDB() (previously declared in /your/path/Nominatim/lib/db.php:4)`
The message is a bit misleading as PHP needs to load the file `DB.php` and
instead re-loads Nominatim's `db.php`. To solve this make sure you
have the [Pear module 'DB'](http://pear.php.net/package/DB/) installed.
sudo pear install DB
### I forgot to delete the flatnodes file before starting an import.
That's fine. For each import the flatnodes file get overwritten.
@@ -208,6 +156,22 @@ for more information.
## Running your own instance
### Can I import multiple countries and keep them up to date?
You should use the extracts and updates from https://download.geofabrik.de.
For the initial import, download the countries you need and merge them.
See [OSM Help](https://help.openstreetmap.org/questions/48843/merging-two-or-more-geographical-areas-to-import-two-or-more-osm-files-in-nominatim)
for examples how to do that. Use the resulting single osm file when
running `setup.php`.
For updates you need to download the change files for each country
once per day and apply them **separately** using
./utils/update.php --import-diff <filename> --index
See [this issue](https://github.com/openstreetmap/Nominatim/issues/60#issuecomment-18679446)
for a script that runs the updates using osmosis.
### Can I import negative OSM ids into Nominatim?
See [this question of Stackoverflow](https://help.openstreetmap.org/questions/64662/nominatim-flatnode-with-negative-id).

View File

@@ -0,0 +1,212 @@
# Importing and Updating the Database
The following instructions explain how to create a Nominatim database
from an OSM planet file and how to keep the database up to date. It
is assumed that you have already successfully installed the Nominatim
software itself, if not return to the [installation page](Installation.md).
## Configuration setup in settings/local.php
The Nominatim server can be customized via the file `settings/local.php`
in the build directory. Note that this is a PHP file, so it must always
start like this:
<?php
without any leading spaces.
There are lots of configuration settings you can tweak. Have a look
at `settings/default.php` for a full list. Most should have a sensible default.
#### Flatnode files
If you plan to import a large dataset (e.g. Europe, North America, planet),
you should also enable flatnode storage of node locations. With this
setting enabled, node coordinates are stored in a simple file instead
of the database. This will save you import time and disk storage.
Add to your `settings/local.php`:
@define('CONST_Osm2pgsql_Flatnode_File', '/path/to/flatnode.file');
Replace the second part with a suitable path on your system and make sure
the directory exists. There should be at least 40GB of free space.
## Downloading additional data
### Wikipedia rankings
Wikipedia can be used as an optional auxiliary data source to help indicate
the importance of osm features. Nominatim will work without this information
but it will improve the quality of the results if this is installed.
This data is available as a binary download:
cd $NOMINATIM_SOURCE_DIR/data
wget https://www.nominatim.org/data/wikipedia_article.sql.bin
wget https://www.nominatim.org/data/wikipedia_redirect.sql.bin
Combined the 2 files are around 1.5GB and add around 30GB to the install
size of nominatim. They also increase the install time by an hour or so.
*NOTE:* you'll need to download the Wikipedia rankings before performing
the initial import of the data if you want the rankings applied to the
loaded data.
### UK postcodes
Nominatim can use postcodes from an external source to improve searches that involve a UK postcode. This data can be optionally downloaded:
cd $NOMINATIM_SOURCE_DIR/data
wget https://www.nominatim.org/data/gb_postcode_data.sql.gz
## Initial import of the data
**Important:** first try the import with a small excerpt, for example from
[Geofabrik](https://download.geofabrik.de).
Download the data to import and load the data with the following command:
```sh
./utils/setup.php --osm-file <data file> --all [--osm2pgsql-cache 28000] 2>&1 | tee setup.log
```
The `--osm2pgsql-cache` parameter is optional but strongly recommended for
planet imports. It sets the node cache size for the osm2pgsql import part
(see `-C` parameter in osm2pgsql help). As a rule of thumb, this should be
about the same size as the file you are importing but never more than
2/3 of RAM available. If your machine starts swapping reduce the size.
Computing word frequency for search terms can improve the performance of
forward geocoding in particular under high load as it helps Postgres' query
planner to make the right decisions. To recompute word counts run:
```sh
./utils/update.php --recompute-word-counts
```
This will take a couple of hours for a full planet installation. You can
also defer that step to a later point in time when you realise that
performance becomes an issue. Just make sure that updates are stopped before
running this function.
If you want to be able to search for places by their type through
[special key phrases](https://wiki.openstreetmap.org/wiki/Nominatim/Special_Phrases)
you also need to enable these key phrases like this:
./utils/specialphrases.php --wiki-import > specialphrases.sql
psql -d nominatim -f specialphrases.sql
Note that this command downloads the phrases from the wiki link above.
## Installing Tiger housenumber data for the US
Nominatim is able to use the official TIGER address set to complement the
OSM house number data in the US. You can add TIGER data to your own Nominatim
instance by following these steps:
1. Install the GDAL library and python bindings and the unzip tool
* Ubuntu: `sudo apt-get install python-gdal unzip`
* CentOS: `sudo yum install gdal-python unzip`
2. Get preprocessed TIGER 2017 data and unpack it into the
data directory in your Nominatim sources:
cd Nominatim/data
wget https://nominatim.org/data/tiger2017-nominatim-preprocessed.tar.gz
tar xf tiger2017-nominatim-preprocessed.tar.gz
3. Import the data into your Nominatim database:
./utils/setup.php --import-tiger-data
4. Enable use of the Tiger data in your `settings/local.php` by adding:
@define('CONST_Use_US_Tiger_Data', true);
5. Apply the new settings:
```sh
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
```
The entire US adds about 10GB to your database.
You can also process the data from the original TIGER data to create the
SQL files, Nominatim needs for the import:
1. Get the TIGER 2017 data. You will need the EDGES files
(3,234 zip files, 11GB total).
wget -r ftp://ftp2.census.gov/geo/tiger/TIGER2017/EDGES/
2. Convert the data into SQL statements:
./utils/imports.php --parse-tiger <tiger edge data directory>
Be warned that this can take quite a long time. After this process is finished,
the same preprocessed files as above are available in `data/tiger`.
## Updates
There are many different possibilities to update your Nominatim database.
The following section describes how to keep it up-to-date with Pyosmium.
For a list of other methods see the output of `./utils/update.php --help`.
#### Installing the newest version of Pyosmium
It is recommended to install Pyosmium via pip. Run (as the same user who
will later run the updates):
```sh
pip install --user osmium
```
Nominatim needs a tool called `pyosmium-get-updates`, which comes with
Pyosmium. You need to tell Nominatim where to find it. Add the
following line to your `settings/local.php`:
@define('CONST_Pyosmium_Binary', '/home/user/.local/bin/pyosmium-get-changes');
The path above is fine if you used the `--user` parameter with pip.
Replace `user` with your user name.
#### Setting up the update process
Next the update needs to be initialised. By default Nominatim is configured
to update using the global minutely diffs.
If you want a different update source you will need to add some settings
to `settings/local.php`. For example, to use the daily country extracts
diffs for Ireland from geofabrik add the following:
// base URL of the replication service
@define('CONST_Replication_Url', 'https://download.geofabrik.de/europe/ireland-and-northern-ireland-updates');
// How often upstream publishes diffs
@define('CONST_Replication_Update_Interval', '86400');
// How long to sleep if no update found yet
@define('CONST_Replication_Recheck_Interval', '900');
To set up the update process now run the following command:
./utils/update.php --init-updates
It outputs the date where updates will start. Recheck that this date is
what you expect.
The --init-updates command needs to be rerun whenever the replication service
is changed.
#### Updating Nominatim
The following command will keep your database constantly up to date:
./utils/update.php --import-osmosis-all
(Note that even though the old name "import-osmosis-all" has been kept for compatibility reasons, Osmosis is not required to run this - it uses pyosmium behind the scenes.)
If you have imported multiple country extracts and want to keep them
up-to-date, have a look at the script in
[issue #60](https://github.com/openstreetmap/Nominatim/issues/60).

View File

@@ -1,255 +0,0 @@
# Importing the Database
The following instructions explain how to create a Nominatim database
from an OSM planet file and how to keep the database up to date. It
is assumed that you have already successfully installed the Nominatim
software itself, if not return to the [installation page](Installation.md).
## Configuration setup in settings/local.php
The Nominatim server can be customized via the file `settings/local.php`
in the build directory. Note that this is a PHP file, so it must always
start like this:
<?php
without any leading spaces.
There are lots of configuration settings you can tweak. Have a look
at `settings/default.php` for a full list. Most should have a sensible default.
#### Flatnode files
If you plan to import a large dataset (e.g. Europe, North America, planet),
you should also enable flatnode storage of node locations. With this
setting enabled, node coordinates are stored in a simple file instead
of the database. This will save you import time and disk storage.
Add to your `settings/local.php`:
@define('CONST_Osm2pgsql_Flatnode_File', '/path/to/flatnode.file');
Replace the second part with a suitable path on your system and make sure
the directory exists. There should be at least 64GB of free space.
## Downloading additional data
### Wikipedia/Wikidata rankings
Wikipedia can be used as an optional auxiliary data source to help indicate
the importance of OSM features. Nominatim will work without this information
but it will improve the quality of the results if this is installed.
This data is available as a binary download:
cd $NOMINATIM_SOURCE_DIR/data
wget https://www.nominatim.org/data/wikimedia-importance.sql.gz
The file is about 400MB and adds around 4GB to Nominatim database.
!!! tip
If you forgot to download the wikipedia rankings, you can also add
importances after the import. Download the files, then run
`./utils/setup.php --import-wikipedia-articles`
and `./utils/update.php --recompute-importance`.
### Great Britain, USA postcodes
Nominatim can use postcodes from an external source to improve searches that
involve a GB or US postcode. This data can be optionally downloaded:
cd $NOMINATIM_SOURCE_DIR/data
wget https://www.nominatim.org/data/gb_postcode_data.sql.gz
wget https://www.nominatim.org/data/us_postcode_data.sql.gz
## Choosing the Data to Import
In its default setup Nominatim is configured to import the full OSM data
set for the entire planet. Such a setup requires a powerful machine with
at least 64GB of RAM and around 800GB of SSD hard disks. Depending on your
use case there are various ways to reduce the amount of data imported. This
section discusses these methods. They can also be combined.
### Using an extract
If you only need geocoding for a smaller region, then precomputed extracts
are a good way to reduce the database size and import time.
[Geofabrik](https://download.geofabrik.de) offers extracts for most countries.
They even have daily updates which can be used with the update process described
below. There are also
[other providers for extracts](https://wiki.openstreetmap.org/wiki/Planet.osm#Downloading).
Please be aware that some extracts are not cut exactly along the country
boundaries. As a result some parts of the boundary may be missing which means
that Nominatim cannot compute the areas for some administrative areas.
### Dropping Data Required for Dynamic Updates
About half of the data in Nominatim's database is not really used for serving
the API. It is only there to allow the data to be updated from the latest
changes from OSM. For many uses these dynamic updates are not really required.
If you don't plan to apply updates, the dynamic part of the database can be
safely dropped using the following command:
```
./utils/setup.php --drop
```
Note that you still need to provide for sufficient disk space for the initial
import. So this option is particularly interesting if you plan to transfer the
database or reuse the space later.
### Reverse-only Imports
If you only want to use the Nominatim database for reverse lookups or
if you plan to use the installation only for exports to a
[photon](https://photon.komoot.de/) database, then you can set up a database
without search indexes. Add `--reverse-only` to your setup command above.
This saves about 5% of disk space.
### Filtering Imported Data
Nominatim normally sets up a full search database containing administrative
boundaries, places, streets, addresses and POI data. There are also other
import styles available which only read selected data:
* **settings/import-admin.style**
Only import administrative boundaries and places.
* **settings/import-street.style**
Like the admin style but also adds streets.
* **settings/import-address.style**
Import all data necessary to compute addresses down to house number level.
* **settings/import-full.style**
Default style that also includes points of interest.
* **settings/import-extratags.style**
Like the full style but also adds most of the OSM tags into the extratags
column.
The style can be changed with the configuration `CONST_Import_Style`.
To give you an idea of the impact of using the different styles, the table
below gives rough estimates of the final database size after import of a
2018 planet and after using the `--drop` option. It also shows the time
needed for the import on a machine with 64GB RAM, 4 CPUS and SSDs. Note that
the given sizes are just an estimate meant for comparison of style requirements.
Your planet import is likely to be larger as the OSM data grows with time.
style | Import time | DB size | after drop
----------|--------------|------------|------------
admin | 5h | 190 GB | 20 GB
street | 42h | 400 GB | 180 GB
address | 59h | 500 GB | 260 GB
full | 80h | 575 GB | 300 GB
extratags | 80h | 585 GB | 310 GB
You can also customize the styles further. For a description of the
style format see [the development section](../develop/Import.md).
## Initial import of the data
!!! danger "Important"
First try the import with a small extract, for example from
[Geofabrik](https://download.geofabrik.de).
Download the data to import and load the data with the following command
from the build directory:
```sh
./utils/setup.php --osm-file <data file> --all 2>&1 | tee setup.log
```
***Note for full planet imports:*** Even on a perfectly configured machine
the import of a full planet takes at least 2 days. Once you see messages
with `Rank .. ETA` appear, the indexing process has started. This part takes
the most time. There are 30 ranks to process. Rank 26 and 30 are the most complex.
They take each about a third of the total import time. If you have not reached
rank 26 after two days of import, it is worth revisiting your system
configuration as it may not be optimal for the import.
### Notes on memory usage
In the first step of the import Nominatim uses osm2pgsql to load the OSM data
into the PostgreSQL database. This step is very demanding in terms of RAM usage.
osm2pgsql and PostgreSQL are running in parallel at this point. PostgreSQL
blocks at least the part of RAM that has been configured with the
`shared_buffers` parameter during [PostgreSQL tuning](Installation#postgresql-tuning)
and needs some memory on top of that. osm2pgsql needs at least 2GB of RAM for
its internal data structures, potentially more when it has to process very large
relations. In addition it needs to maintain a cache for node locations. The size
of this cache can be configured with the parameter `--osm2pgsql-cache`.
When importing with a flatnode file, it is best to disable the node cache
completely and leave the memory for the flatnode file. Nominatim will do this
by default, so you do not need to configure anything in this case.
For imports without a flatnode file, set `--osm2pgsql-cache` approximately to
the size of the OSM pbf file (in MB) you are importing. Make sure you leave
enough RAM for PostgreSQL and osm2pgsql as mentioned above. If the system starts
swapping or you are getting out-of-memory errors, reduce the cache size or
even consider using a flatnode file.
### Verify import finished
Run this script to verify all required tables and indices got created successfully.
```sh
./utils/check_import_finished.php
```
## Tuning the database
Accurate word frequency information for search terms helps PostgreSQL's query
planner to make the right decisions. Recomputing them can improve the performance
of forward geocoding in particular under high load. To recompute word counts run:
```sh
./utils/update.php --recompute-word-counts
```
This will take a couple of hours for a full planet installation. You can
also defer that step to a later point in time when you realise that
performance becomes an issue. Just make sure that updates are stopped before
running this function.
If you want to be able to search for places by their type through
[special key phrases](https://wiki.openstreetmap.org/wiki/Nominatim/Special_Phrases)
you also need to enable these key phrases like this:
./utils/specialphrases.php --wiki-import > specialphrases.sql
psql -d nominatim -f specialphrases.sql
Note that this command downloads the phrases from the wiki link above. You
need internet access for the step.
## Installing Tiger housenumber data for the US
Nominatim is able to use the official [TIGER](https://www.census.gov/geographies/mapping-files/time-series/geo/tiger-line-file.html)
address set to complement the OSM house number data in the US. You can add
TIGER data to your own Nominatim instance by following these steps. The
entire US adds about 10GB to your database.
1. Get preprocessed TIGER 2019 data and unpack it into the
data directory in your Nominatim sources:
cd Nominatim/data
wget https://nominatim.org/data/tiger2019-nominatim-preprocessed.tar.gz
tar xf tiger2019-nominatim-preprocessed.tar.gz
`data-source/us-tiger/README.md` explains how the data got preprocessed.
2. Import the data into your Nominatim database:
./utils/setup.php --import-tiger-data
3. Enable use of the Tiger data in your `settings/local.php` by adding:
@define('CONST_Use_US_Tiger_Data', true);
4. Apply the new settings:
```sh
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
```

View File

@@ -4,9 +4,8 @@ This page contains generic installation instructions for Nominatim and its
prerequisites. There are also step-by-step instructions available for
the following operating systems:
* [Ubuntu 20.04](../appendix/Install-on-Ubuntu-20.md)
* [Ubuntu 18.04](../appendix/Install-on-Ubuntu-18.md)
* [CentOS 8](../appendix/Install-on-Centos-8.md)
* [Ubuntu 16.04](../appendix/Install-on-Ubuntu-16.md)
* [CentOS 7.2](../appendix/Install-on-Centos-7.md)
These OS-specific instructions can also be found in executable form
@@ -26,47 +25,45 @@ and can't offer support.
For compiling:
* [cmake](https://cmake.org/)
* [expat](https://libexpat.github.io/)
* [proj](https://proj.org/)
* [bzip2](http://www.bzip.org/)
* [zlib](https://www.zlib.net/)
* [Boost libraries](https://www.boost.org/), including system and filesystem
* PostgreSQL client libraries
* a recent C++ compiler (gcc 5+ or Clang 3.8+)
* [libxml2](http://xmlsoft.org/)
* a recent C++ compiler
Nominatim comes with its own version of osm2pgsql. See the
osm2pgsql README for additional dependencies required for compiling osm2pgsql.
For running tests:
* [behave](http://pythonhosted.org/behave/)
* [Psycopg2](http://initd.org/psycopg)
* [nose](https://nose.readthedocs.io)
* [phpunit](https://phpunit.de)
For running Nominatim:
* [PostgreSQL](https://www.postgresql.org) (9.3+)
* [PostGIS](https://postgis.net) (2.2+)
* [Python 3](https://www.python.org/)
* [Psycopg2](https://www.psycopg.org)
* [PHP](https://php.net) (7.0 or later)
* [PostgreSQL](http://www.postgresql.org) (9.1 or later)
* [PostGIS](http://postgis.refractions.net) (2.0 or later)
* [PHP](http://php.net) (5.4 or later)
* PHP-pgsql
* PHP-intl (bundled with PHP)
* [PEAR::DB](http://pear.php.net/package/DB)
* a webserver (apache or nginx are recommended)
For running continuous updates:
* [pyosmium](https://osmcode.org/pyosmium/) (with Python 3)
For running tests:
* [behave](https://behave.readthedocs.io)
* [nose](https://nose.readthedocs.io)
* [phpunit](https://phpunit.de) >= 7.3
* [pyosmium](http://osmcode.org/pyosmium/)
### Hardware
A minimum of 2GB of RAM is required or installation will fail. For a full
planet import 64GB of RAM or more are strongly recommended. Do not report
out of memory problems if you have less than 64GB RAM.
planet import 32GB of RAM or more strongly are recommended.
For a full planet install you will need at least 800GB of hard disk space
For a full planet install you will need at least 700GB of hard disk space
(take into account that the OSM database is growing fast). SSD disks
will help considerably to speed up import and queries.
Even on a well configured machine the import of a full planet takes
at least 2 days. Without SSDs 7-8 days are more realistic.
On a 6-core machine with 32GB RAM and SSDs the import of a full planet takes
a bit more than 2 days. Without SSDs 7-8 days are more realistic.
## Setup of the server
@@ -76,30 +73,17 @@ You might want to tune your PostgreSQL installation so that the later steps
make best use of your hardware. You should tune the following parameters in
your `postgresql.conf` file.
shared_buffers = 2GB
maintenance_work_mem = (10GB)
autovacuum_work_mem = 2GB
work_mem = (50MB)
effective_cache_size = (24GB)
shared_buffers (2GB)
maintenance_work_mem (10GB)
work_mem (50MB)
effective_cache_size (24GB)
synchronous_commit = off
checkpoint_segments = 100 # only for postgresql <= 9.4
max_wal_size = 1GB # postgresql > 9.4
checkpoint_timeout = 10min
checkpoint_completion_target = 0.9
The numbers in brackets behind some parameters seem to work fine for
64GB RAM machine. Adjust to your setup. A higher number for `max_wal_size`
means that PostgreSQL needs to run checkpoints less often but it does require
the additional space on your disk.
Autovacuum must not be switched off because it ensures that the
tables are frequently analysed. If your machine has very little memory,
you might consider setting:
autovacuum_max_workers = 1
and even reduce `autovacuum_work_mem` further. This will reduce the amount
of memory that autovacuum takes away from the import process.
32GB RAM machine. Adjust to your setup.
For the initial import, you should also set:
@@ -107,8 +91,8 @@ For the initial import, you should also set:
full_page_writes = off
Don't forget to reenable them after the initial import or you risk database
corruption.
corruption. Autovacuum must not be switched off because it ensures that the
tables are frequently analysed.
### Webserver setup
@@ -121,15 +105,13 @@ from there.
Make sure your Apache configuration contains the required permissions for the
directory and create an alias:
``` apache
<Directory "/srv/nominatim/build/website">
Options FollowSymLinks MultiViews
AddType text/html .php
DirectoryIndex search.php
Require all granted
</Directory>
Alias /nominatim /srv/nominatim/build/website
```
<Directory "/srv/nominatim/build/website">
Options FollowSymLinks MultiViews
AddType text/html .php
DirectoryIndex search.php
Require all granted
</Directory>
Alias /nominatim /srv/nominatim/build/website
`/srv/nominatim/build` should be replaced with the location of your
build directory.
@@ -157,35 +139,20 @@ follows:
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/build/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/php7.3-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
root /srv/nominatim/build/website;
index search.php index.html;
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index search.php;
include fastcgi.conf;
}
fastcgi_pass unix:/var/run/php7.3-fpm.sock;
fastcgi_index search.php;
include fastcgi.conf;
}
```
Restart the nginx and php5-fpm services and the website should now be available
at `http://localhost/`.
Now continue with [importing the database](Import.md).
Now continue with [importing the database](Import-and-Update.md).

View File

@@ -3,99 +3,8 @@
This page describes database migrations necessary to update existing databases
to newer versions of Nominatim.
SQL statements should be executed from the PostgreSQL commandline. Execute
`psql nominatim` to enter command line mode.
## 3.4.0 -> 3.5.0
### New Wikipedia/Wikidata importance tables
The `wikipedia_*` tables have a new format that also includes references to
Wikidata. You need to update the computation functions and the tables as
follows:
* download the new Wikipedia tables as described in the import section
* reimport the tables: `./utils/setup.php --import-wikipedia-articles`
* update the functions: `./utils/setup.php --create-functions --enable-diff-updates`
* create a new lookup index:
```
CREATE INDEX idx_placex_wikidata on placex
USING BTREE ((extratags -> 'wikidata'))
WHERE extratags ? 'wikidata' and class = 'place' and osm_type = 'N' and rank_search < 26
```
* compute importance: `./utils/update.php --recompute-importance`
The last step takes about 10 hours on the full planet.
Remove one function (it will be recreated in the next step):
```sql
DROP FUNCTION create_country(hstore,character varying);
```
Finally, update all SQL functions:
```sh
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
```
## 3.3.0 -> 3.4.0
### Reorganisation of location_area_country table
The table `location_area_country` has been optimized. You need to switch to the
new format when you run updates. While updates are disabled, run the following
SQL commands:
```sql
CREATE TABLE location_area_country_new AS
SELECT place_id, country_code, geometry FROM location_area_country;
DROP TABLE location_area_country;
ALTER TABLE location_area_country_new RENAME TO location_area_country;
CREATE INDEX idx_location_area_country_geometry ON location_area_country USING GIST (geometry);
CREATE INDEX idx_location_area_country_place_id ON location_area_country USING BTREE (place_id);
```
Finally, update all SQL functions:
```sh
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
```
## 3.2.0 -> 3.3.0
### New database connection string (DSN) format
Previously database connection setting (`CONST_Database_DSN` in `settings/*.php`) had the format
* (simple) `pgsql://@/nominatim`
* (complex) `pgsql://johndoe:secret@machine1.domain.com:1234/db1`
The new format is
* (simple) `pgsql:dbname=nominatim`
* (complex) `pgsql:dbname=db1;host=machine1.domain.com;port=1234;user=johndoe;password=secret`
### Natural Earth country boundaries no longer needed as fallback
```
DROP TABLE country_naturalearthdata;
```
Finally, update all SQL functions:
```sh
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
```
### Configurable Address Levels
The new configurable address levels require a new table. Create it with the
following command:
```sh
./utils/update.php --update-address-levels
```
SQL statements should be executed from the postgres commandline. Execute
`psql nominiatim` to enter command line mode.
## 3.1.0 -> 3.2.0
@@ -106,17 +15,17 @@ SQL statements to create the indexes:
```
CREATE INDEX idx_placex_geometry_reverse_lookupPoint
ON placex USING gist (geometry)
ON placex USING gist (geometry) {ts:search-index}
WHERE (name is not null or housenumber is not null or rank_address between 26 and 27)
AND class not in ('railway','tunnel','bridge','man_made')
AND rank_address >= 26 AND indexed_status = 0 AND linked_place_id is null;
CREATE INDEX idx_placex_geometry_reverse_lookupPolygon
ON placex USING gist (geometry)
ON placex USING gist (geometry) {ts:search-index}
WHERE St_GeometryType(geometry) in ('ST_Polygon', 'ST_MultiPolygon')
AND rank_address between 4 and 25 AND type != 'postcode'
AND name is not null AND indexed_status = 0 AND linked_place_id is null;
CREATE INDEX idx_placex_geometry_reverse_placeNode
ON placex USING gist (geometry)
ON placex USING gist (geometry) {ts:search-index}
WHERE osm_type = 'N' AND rank_search between 5 and 25
AND class = 'place' AND type != 'postcode'
AND name is not null AND indexed_status = 0 AND linked_place_id is null;
@@ -130,18 +39,12 @@ GRANT SELECT ON table country_osm_grid to "www-user";
Replace the `www-user` with the user name of your website server if necessary.
You can now drop the unused indexes:
Finally, you can drop the now unused indexes:
```
DROP INDEX idx_placex_reverse_geometry;
```
Finally, update all SQL functions:
```sh
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
```
## 3.0.0 -> 3.1.0
### Postcode Table

View File

@@ -1,67 +0,0 @@
# Updating the Database
There are many different ways to update your Nominatim database.
The following section describes how to keep it up-to-date with Pyosmium.
For a list of other methods see the output of `./utils/update.php --help`.
!!! warning
If you have configured a flatnode file for the import, then you
need to keep this flatnode file around for updates as well.
#### Installing the newest version of Pyosmium
It is recommended to install Pyosmium via pip. Make sure to use python3.
Run (as the same user who will later run the updates):
```sh
pip3 install --user osmium
```
Nominatim needs a tool called `pyosmium-get-changes` which comes with
Pyosmium. You need to tell Nominatim where to find it. Add the
following line to your `settings/local.php`:
@define('CONST_Pyosmium_Binary', '/home/user/.local/bin/pyosmium-get-changes');
The path above is fine if you used the `--user` parameter with pip.
Replace `user` with your user name.
#### Setting up the update process
Next the update needs to be initialised. By default Nominatim is configured
to update using the global minutely diffs.
If you want a different update source you will need to add some settings
to `settings/local.php`. For example, to use the daily country extracts
diffs for Ireland from Geofabrik add the following:
// base URL of the replication service
@define('CONST_Replication_Url', 'https://download.geofabrik.de/europe/ireland-and-northern-ireland-updates');
// How often upstream publishes diffs
@define('CONST_Replication_Update_Interval', '86400');
// How long to sleep if no update found yet
@define('CONST_Replication_Recheck_Interval', '900');
To set up the update process now run the following command:
./utils/update.php --init-updates
It outputs the date where updates will start. Recheck that this date is
what you expect.
The `--init-updates` command needs to be rerun whenever the replication service
is changed.
#### Updating Nominatim
The following command will keep your database constantly up to date:
./utils/update.php --import-osmosis-all
(Note that even though the old name "import-osmosis-all" has been kept for
compatibility reasons, Osmosis is not required to run this - it uses pyosmium
behind the scenes.)
If you have imported multiple country extracts and want to keep them
up-to-date, [Advanced installations section](Advanced-Installations.md) contains instructions
to set up and update multiple country extracts.

View File

@@ -24,7 +24,7 @@ but the `class` parameter is left out, then one of the places will be chosen
at random and displayed.
```
https://nominatim.openstreetmap.org/details?place_id=<value>
https://nominatim.openstreetmap.org/details?placeid=<value>
```
Placeids are assigned sequentially during Nominatim data import. The id for a place is different between Nominatim installation (servers) and changes when data gets reimported. Therefore it can't be used as permanent id and shouldn't be used in bug reports.
@@ -40,7 +40,7 @@ See [Place Output Formats](Output.md) for details on each format. (Default: html
* `json_callback=<string>`
Wrap JSON output in a callback function (JSONP) i.e. `<string>(<json>)`.
Wrap json output in a callback function (JSONP) i.e. `<string>(<json>)`.
Only has an effect for JSON output formats.
* `pretty=[0|1]`

View File

@@ -7,11 +7,11 @@
Nominatim computes the address from two sources in the OpenStreetMap data:
from administrative boundaries and from place nodes. Boundaries are the more
useful source. They precisely describe an area. So it is very clear for
Nominatim if a point belongs to an area or not. Place nodes are more complicated.
These are only points without any precise extent. So Nominatim has to take a
guess and assume that an address belongs to the closest place node it can find.
Nominatim if a point belongs to an area of not. Place nodes are more complicated.
These are only points without any precise extend. So Nominatim has to take a
guess and assume that an address belongs to the closest place nose it can find.
In an ideal world, Nominatim would not need the place nodes but there are
many places on earth where there are no precise boundaries available for
many places on earth where there are not precise boundaries available for
all parts that make up an address. This is in particular true for the more
local address parts, like villages and suburbs. Therefore it is not possible
to completely dismiss place nodes. And sometimes they sneak in where they
@@ -21,7 +21,7 @@ As a OpenStreetMap mapper, you can improve the situation in two ways: if you
see a place node for which already an administrative area exists, then you
should _link_ the two by adding the node with a 'label' role to the boundary
relation. If there is no administrative area, you can add the approximate
extent of the place and tag it place=<something> as well.
extend of the place and tag it place=<something> as well.
#### 2. When doing reverse search, the address details have parts that don't contain the point I was looking up.
@@ -30,7 +30,7 @@ Reverse does not give you the address of the point you asked for. Reverse
returns the closest object to the point you asked for and then returns the
address of that object. Now, if you are close to a border, then the closest
object may be across that border. When Nominatim then returns the address,
it contains the county/state/country across the border.
contains the county/state/country across the border.
#### 3. I get different counties/states/countries when I change the zoom parameter in the reverse query. How is that possible?
@@ -41,21 +41,3 @@ border while the closest street is on the other. As the address details contain
the address of the closest object found, you might sometimes get one result,
sometimes the other for the closest point.
#### 4. Can you return the continent?
Nominatim assigns each map feature one country. Those outside any administrative
boundaries are assigned a special no-country. Continents or other super-national
administrations (e.g. European Union, NATO, Custom unions) are not supported,
see also [Administrative Boundary](https://wiki.openstreetmap.org/wiki/Tag:boundary%3Dadministrative#Super-national_administrations).
#### 5. Can you return the timezone?
See this separate OpenStreetMap-based project [Timezone Boundary Builder](https://github.com/evansiroky/timezone-boundary-builder).
#### 6. I want to download a list of streets/restaurants of a city/region
The [Overpass API](https://wiki.openstreetmap.org/wiki/Overpass_API) is more
suited for these kinds of queries.
That said if you installed your own Nominatim instance you can use the
`/utils/export.php` PHP script as basis to return such lists.

View File

@@ -19,13 +19,13 @@ Additional optional parameters are explained below.
### Output format
* `format=[xml|json|jsonv2|geojson|geocodejson]`
* `format=[html|xml|json|jsonv2|geojson|geocodejson]`
See [Place Output Formats](Output.md) for details on each format. (Default: xml)
* `json_callback=<string>`
Wrap JSON output in a callback function (JSONP) i.e. `<string>(<json>)`.
Wrap json output in a callback function (JSONP) i.e. `<string>(<json>)`.
Only has an effect for JSON output formats.
### Output details

View File

@@ -1,6 +1,6 @@
# Place Output
The [/reverse](Reverse.md), [/search](Search.md) and [/lookup](Lookup.md)
The [\reverse](Reverse.md), [\search](Search.md) and [\lookup](Lookup.md)
API calls produce very similar output which is explained in this section.
There is one section for each format which is selectable via the `format`
parameter.
@@ -46,16 +46,15 @@ a single place (for reverse) of the following format:
The possible fields are:
* `place_id` - reference to the Nominatim internal database ID ([see notes](#place_id-is-not-a-persistent-id))
* `place_id` - reference to the Nominatim internal database ID
* `osm_type`, `osm_id` - reference to the OSM object
* `boundingbox` - area of corner coordinates ([see notes](#boundingbox))
* `boundingbox` - area of corner coordinates
* `lat`, `lon` - latitude and longitude of the centroid of the object
* `display_name` - full comma-separated address
* `class`, `type` - key and value of the main OSM tag
* `importance` - computed importance rank
* `icon` - link to class icon (if available)
* `address` - dictionary of address details (only with `addressdetails=1`,
[see notes](#addressdetails))
* `address` - dictionary of address details (only with `addressdetails=1`)
* `extratags` - dictionary with additional useful tags like website or maxspeed
(only with `extratags=1`)
* `namedetails` - dictionary with full list of available names including ref etc.
@@ -71,21 +70,20 @@ This is the same as the JSON format with two changes:
### GeoJSON
This format follows the [RFC7946](https://geojson.org). Every feature includes
This format follows the [RFC7946](http://geojson.org). Every feature includes
a bounding box (`bbox`).
The feature list has the following fields:
* `place_id` - reference to the Nominatim internal database ID ([see notes](#place_id-is-not-a-persistent-id))
* `place_id` - reference to the Nominatim internal database ID
* `osm_type`, `osm_id` - reference to the OSM object
* `category`, `type` - key and value of the main OSM tag
* `display_name` - full comma-separated address
* `place_rank` - class search rank
* `importance` - computed importance rank
* `icon` - link to class icon (if available)
* `address` - dictionary of address details (only with `addressdetails=1`,
[see notes](#addressdetails))
* `extratags` - dictionary with additional useful tags like `website` or `maxspeed`
* `address` - dictionary of address details (only with `addressdetails=1`)
* `extratags` - dictionary with additional useful tags like website or maxspeed
(only with `extratags=1`)
* `namedetails` - dictionary with full list of available names including ref etc.
@@ -102,9 +100,12 @@ The following feature attributes are implemented:
* `type` - value of the main tag of the object (e.g. residential, restaurant, ...)
* `label` - full comma-separated address
* `name` - localised name of the place
* `housenumber`, `street`, `locality`, `district`, `postcode`, `city`,
`county`, `state`, `country` -
* `housenumber`, `street`, `locality`, `postcode`, `city`,
`district`, `county`, `state`, `country` -
provided when it can be determined from the address
(see [this issue](https://github.com/openstreetmap/Nominatim/issues/1080) for
current limitations on the correctness of the address) and `addressdetails=1`
was given
* `admin` - list of localised names of administrative boundaries (only with `addressdetails=1`)
Use `polygon_geojson` to output the full geometry of the object instead
@@ -119,7 +120,7 @@ formats depending on the API call.
```
<reversegeocode timestamp="Sat, 11 Aug 18 11:53:21 +0000"
attribution="Data © OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright"
attribution="Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright"
querystring="lat=48.400381&lon=11.745876&zoom=5&format=xml">
<result place_id="179509537" osm_type="relation" osm_id="2145268" ref="BY"
lat="48.9467562" lon="11.4038717"
@@ -147,13 +148,13 @@ attribution to OSM and the original querystring.
The place information can be found in the `result` element. The attributes of that element contain:
* `place_id` - reference to the Nominatim internal database ID ([see notes](#place_id-is-not-a-persistent-id))
* `place_id` - reference to the Nominatim internal database ID
* `osm_type`, `osm_id` - reference to the OSM object
* `ref` - content of `ref` tag if it exists
* `lat`, `lon` - latitude and longitude of the centroid of the object
* `boundingbox` - comma-separated list of corner coordinates ([see notes](#boundingbox))
* `boundingbox` - comma-separated list of corner coordinates
The full address of the result can be found in the content of the
The full address address of the result can be found in the content of the
`result` element as a comma-separated list.
Additional information requested with `addressdetails=1`, `extratags=1` and
@@ -163,12 +164,12 @@ 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"
attribution="Data © OpenStreetMap contributors, ODbL 1.0. http://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">
<place place_id="100149" osm_type="node" osm_id="107775" place_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"
display_name="London, Greater London, England, SW1A 2DU, United Kingdom"
class="place" type="city" importance="0.9654895765402"
icon="https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png">
<extratags>
@@ -202,11 +203,11 @@ generic information about the query:
The place information can be found in the `place` elements, of which there may
be more than one. The attributes of that element contain:
* `place_id` - reference to the Nominatim internal database ID ([see notes](#place_id-is-not-a-persistent-id))
* `place_id` - reference to the Nominatim internal database ID
* `osm_type`, `osm_id` - reference to the OSM object
* `ref` - content of `ref` tag if it exists
* `lat`, `lon` - latitude and longitude of the centroid of the object
* `boundingbox` - comma-separated list of corner coordinates ([see notes](#boundingbox))
* `boundingbox` - comma-separated list of corner coordinates
* `place_rank` - class search rank
* `display_name` - full comma-separated address
* `class`, `type` - key and value of the main OSM tag
@@ -219,56 +220,3 @@ as subelements with the type of the address part.
Additional information requested with `extratags=1` and `namedetails=1` can
be found in extra elements as sub-element of each place.
## Notes on field values
### place_id is not a persistent id
The `place_id` is created when a Nominatim database gets installed. A
single place will have a different value on another server or even when
the same data gets re-imported. It's thus not useful to treat it as
permanent for later use.
The combination `osm_type`+`osm_id` is slighly better but remember in
OpenStreetMap mappers can delete, split, recreate places (and those
get a new `osm_id`), there is no link between those old and new ids.
Places can also change their meaning without changing their `osm_id`,
e.g. when a restaurant is retagged as supermarket. For a more in-depth
discussion see [Permanent ID](https://wiki.openstreetmap.org/wiki/Permanent_ID).
Nominatim merges some places (e.g. center node of a city with the boundary
relation) so `osm_type`+`osm_id`+`class_name` would be more unique.
### boundingbox
Comma separated list of min latitude, max latitude, min longitude, max longitude.
The whole planet would be `-90,90,-180,180`.
Can we used to pan and center the map on the result, for example with leafletjs
mapping library
`map.fitBounds([[bbox[0],bbox[2]],[bbox[1],bbox[3]]], {padding: [20, 20], maxzoom: 16});`
Bounds crossing the antimeridian have a min latitude -180 and max latitude 180,
essentially covering the planet (See [issue 184](https://github.com/openstreetmap/Nominatim/issues/184)).
### addressdetails
Address details in the xml and json formats return a list of names together
with a designation label. Per default the following labels may appear:
* continent
* country, country_code
* region, state, state_district, county
* municipality, city, town, village
* city_district, district, borough, suburb, subdivision
* hamlet, croft, isolated_dwelling
* neighbourhood, allotments, quarter
* city_block, residental, farm, farmyard, industrial, commercial, retail
* road
* house_number, house_name
* emergency, historic, military, natural, landuse, place, railway,
man_made, aerialway, boundary, amenity, aeroway, club, craft, leisure,
office, mountain_pass, shop, tourism, bridge, tunnel, waterway
They roughly correspond to the classification of the OpenStreetMap data
according to either the `place` tag or the main key of the object.

View File

@@ -7,7 +7,7 @@ Its API has the following endpoints for querying the data:
* __[/search](Search.md)__ - search OSM objects by name or type
* __[/reverse](Reverse.md)__ - search OSM object by their location
* __[/lookup](Lookup.md)__ - look up address details for OSM objects by their ID
* __[/status](Status.md)__ - query the status of the server
* __/status__ - query the status of the server
* __/deletable__ - list objects that have been deleted in OSM but are held
back in Nominatim in case the deletion was accidental
* __/polygons__ - list of broken polygons detected by Nominatim

View File

@@ -22,7 +22,7 @@ There are two ways how the requested location can be specified:
A specific OSM node(N), way(W) or relation(R) to return an address for.
In both cases exactly one object is returned. The two input parameters cannot
In both cases exactly one object is returned. The two input paramters cannot
be used at the same time. Both accept the additional optional parameters listed
below.
@@ -34,7 +34,7 @@ See [Place Output Formats](Output.md) for details on each format. (Default: html
* `json_callback=<string>`
Wrap JSON output in a callback function ([JSONP](https://en.wikipedia.org/wiki/JSONP)) i.e. `<string>(<json>)`.
Wrap json output in a callback function ([JSONP](https://en.wikipedia.org/wiki/JSONP)) i.e. `<string>(<json>)`.
Only has an effect for JSON output formats.
### Output details
@@ -80,8 +80,7 @@ In terms of address details the zoom levels are as follows:
8 | county
10 | city
14 | suburb
16 | major streets
17 | major and minor streets
16 | street
18 | building
@@ -135,7 +134,7 @@ This overrides the specified machine readable format. (Default: 0)
<postcode>B72</postcode>
<country>United Kingdom</country>
<country_code>gb</country_code>
</addressparts>
</addressparts>
</reversegeocode>
```
@@ -146,7 +145,7 @@ This overrides the specified machine readable format. (Default: 0)
```json
{
"place_id":"134140761",
"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https:\/\/www.openstreetmap.org\/copyright",
"licence":"Data © OpenStreetMap contributors, ODbL 1.0. http:\/\/www.openstreetmap.org\/copyright",
"osm_type":"way",
"osm_id":"280940520",
"lat":"-34.4391708",

View File

@@ -1,6 +1,6 @@
# Search queries
The search API allows you to look up a location from a textual description.
The search API allows to look up a location from a textual description.
Nominatim supports structured as well as free-form search queries.
The search query may also contain
@@ -46,7 +46,7 @@ In this form, the query may be given through two different sets of parameters:
Structured requests are faster but are less robust against alternative
OSM tagging schemas. **Do not combine with** `q=<query>` **parameter**.
All three query forms accept the additional parameters listed below.
All three query forms accept the additional paramters listed below.
### Output format
@@ -56,7 +56,7 @@ See [Place Output Formats](Output.md) for details on each format. (Default: html
* `json_callback=<string>`
Wrap JSON output in a callback function ([JSONP](https://en.wikipedia.org/wiki/JSONP)) i.e. `<string>(<json>)`.
Wrap json output in a callback function ([JSONP](https://en.wikipedia.org/wiki/JSONP)) i.e. `<string>(<json>)`.
Only has an effect for JSON output formats.
### Output details
@@ -92,12 +92,8 @@ comma-separated list of language codes.
* `countrycodes=<countrycode>[,<countrycode>][,<countrycode>]...`
Limit search results to one or more countries. `<countrycode>` must be the
[ISO 3166-1alpha2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code,
e.g. `gb` for the United Kingdom, `de` for Germany.
ISO 3166-1alpha2 code, e.g. `gb` for the United Kingdom, `de` for Germany.
Each place in Nominatim is assigned to one country code based
on `admin_level=2` tags, in rare cases to none (for example in
international waters outside any country).
* `exclude_place_ids=<place_id,[place_id],[place_id]`
@@ -116,8 +112,7 @@ Limit the number of returned results. (Default: 10, Maximum: 50)
* `viewbox=<x1>,<y1>,<x2>,<y2>`
The preferred area to find search results. Any two corner points of the box
are accepted in any order as long as they span a real box. `x` is longitude,
`y` is latitude.
are accepted in any order as long as they span a real box.
* `bounded=[0|1]`
@@ -172,27 +167,21 @@ This overrides the specified machine readable format. (Default: 0)
## Examples
##### XML with kml polygon
##### XML with polygon points
* [https://nominatim.openstreetmap.org/search?q=135+pilkington+avenue,+birmingham&format=xml&polygon_geojson=1&addressdetails=1](https://nominatim.openstreetmap.org/search?q=135+pilkington+avenue,+birmingham&format=xml&polygon_geojson=1&addressdetails=1)
* [https://nominatim.openstreetmap.org/search?q=135+pilkington+avenue,+birmingham&format=xml&polygon=1&addressdetails=1](https://nominatim.openstreetmap.org/search?q=135+pilkington+avenue,+birmingham&format=xml&polygon=1&addressdetails=1)
* [https://nominatim.openstreetmap.org/search/gb/birmingham/pilkington%20avenue/135?format=xml&polygon=1&addressdetails=1](https://nominatim.openstreetmap.org/search/gb/birmingham/pilkington%20avenue/135?format=xml&polygon=1&addressdetails=1)
* [https://nominatim.openstreetmap.org/search/135%20pilkington%20avenue,%20birmingham?format=xml&polygon=1&addressdetails=1](https://nominatim.openstreetmap.org/search/135%20pilkington%20avenue,%20birmingham?format=xml&polygon=1&addressdetails=1)
```xml
<searchresults timestamp="Sat, 07 Nov 09 14:42:10 +0000" querystring="135 pilkington, avenue birmingham" polygon="true">
<place
place_id="1620612" osm_type="node" osm_id="452010817"
boundingbox="52.548641204834,52.5488433837891,-1.81612110137939,-1.81592094898224"
lat="52.5487429714954" lon="-1.81602098644987"
display_name="135, Pilkington Avenue, Wylde Green, City of Birmingham, West Midlands (county), B72, United Kingdom"
<place
place_id="1620612" osm_type="node" osm_id="452010817"
boundingbox="52.548641204834,52.5488433837891,-1.81612110137939,-1.81592094898224"
polygonpoints="[['-1.81592098644987','52.5487429714954'],['-1.81592290792183','52.5487234624632'],...]"
lat="52.5487429714954" lon="-1.81602098644987"
display_name="135, Pilkington Avenue, Wylde Green, City of Birmingham, West Midlands (county), B72, United Kingdom"
class="place" type="house">
<geokml>
<Polygon>
<outerBoundaryIs>
<LinearRing>
<coordinates>-1.816513,52.548756599999997 -1.816434,52.548747300000002 -1.816429,52.5487629 -1.8163717,52.548756099999999 -1.8163464,52.548834599999999 -1.8164599,52.548848100000001 -1.8164685,52.5488213 -1.8164913,52.548824000000003 -1.816513,52.548756599999997</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</geokml>
<house_number>135</house_number>
<road>Pilkington Avenue</road>
<village>Wylde Green</village>
@@ -248,7 +237,7 @@ This overrides the specified machine readable format. (Default: 0)
##### JSON with address details
[https://nominatim.openstreetmap.org/?addressdetails=1&q=bakery+in+berlin+wedding&format=json&limit=1](https://nominatim.openstreetmap.org/?addressdetails=1&q=bakery+in+berlin+wedding&format=json&limit=1)
[https://nominatim.openstreetmap.org/?format=json&addressdetails=1&q=bakery+in+berlin+wedding&format=json&limit=1](https://nominatim.openstreetmap.org/?format=json&addressdetails=1&q=bakery+in+berlin+wedding&format=json&limit=1)
```json
{

View File

@@ -1,60 +0,0 @@
# Status
Useful for checking if the service and database is running. The JSON output also shows
when the database was last updated.
## Parameters
* `format=[text|json]` (defaults to 'text')
## Output
#### Text format
```
https://nominatim.openstreetmap.org/status.php
```
will return HTTP status code 200 and print `OK`.
On error it will return HTTP status code 500 and print a message, e.g.
`ERROR: Database connection failed`.
#### JSON format
```
https://nominatim.openstreetmap.org/status.php?format=json
```
will return HTTP code 200 and a structure
```json
{
"status": 0,
"message": "OK",
"data_updated": "2020-05-04T14:47:00+00:00"
}
```
On error will also return HTTP status code 200 and a structure with error
code and message, e.g.
```json
{
"status": 700,
"message": "Database connection failed"
}
```
Possible status codes are
| | message | notes |
|-----|----------------------|---------------------------------------------------|
| 700 | "No database" | connection failed |
| 701 | "Module failed" | database could not load nominatim.so |
| 702 | "Module call failed" | nominatim.so loaded but calling a function failed |
| 703 | "Query failed" | test query against a database table failed |
| 704 | "No value" | test query worked but returned no results |

View File

@@ -1,4 +0,0 @@
# Additional Data Sources
This guide explains how data sources other than OpenStreetMap mentioned in
the install instructions got obtained and converted.

View File

@@ -1,39 +0,0 @@
# Documentation Pages
The [Nominatim documentation](https://nominatim.org/release-docs/develop/) is built using the [MkDocs](https://www.mkdocs.org/) static site generation framework. The master branch is automatically deployed every night on under [https://nominatim.org/release-docs/develop/](https://nominatim.org/release-docs/develop/)
To preview local changes, first install MkDocs
```
pip3 install --user mkdocs
```
If `mkdocs` can't be found after the installation, the $PATH might have not
been set correctly yet. Try opening a new terminal session.
Then go to the build directory and run
```
make doc
INFO - Cleaning site directory
INFO - Building documentation to directory: /home/vagrant/build/site-html
```
This runs `mkdocs build` plus extra transformation of some files and adds
symlinks (see `CMakeLists.txt` for the exact steps).
Now you can start webserver for local testing
```
build> mkdocs serve
[server:296] Serving on http://127.0.0.1:8000
[handlers:62] Start watching changes
```
If you develop inside a Vagrant virtual machine:
* add port forwarding to your Vagrantfile,
e.g. `config.vm.network "forwarded_port", guest: 8000, host: 8000`
* use `mkdocs serve --dev-addr 0.0.0.0:8000` because the default localhost
IP does not get forwarded.

View File

@@ -1,170 +0,0 @@
# OSM Data Import
OSM data is initially imported using osm2pgsql. Nominatim uses its own data
output style 'gazetteer', which differs from the output style created for
map rendering.
## Database Layout
The gazetteer style produces a single table `place` with the following rows:
* `osm_type` - kind of OSM object (**N** - node, **W** - way, **R** - relation)
* `osm_id` - original OSM ID
* `class` - key of principal tag defining the object type
* `type` - value of principal tag defining the object type
* `name` - collection of tags that contain a name or reference
* `admin_level` - numerical value of the tagged administrative level
* `address` - collection of tags defining the address of an object
* `extratags` - collection of additional interesting tags that are not
directly relevant for searching
* `geometry` - geometry of the object (in WGS84)
A single OSM object may appear multiple times in this table when it is tagged
with multiple tags that may constitute a principal tag. Take for example a
motorway bridge. In OSM, this would be a way which is tagged with
`highway=motorway` and `bridge=yes`. This way would appear in the `place` table
once with `class` of `highway` and once with a `class` of `bridge`. Thus the
*unique key* for `place` is (`osm_type`, `osm_id`, `class`).
## Configuring the Import
How tags are interpreted and assigned to the different `place` columns can be
configured via the import style configuration file (`CONST_Import_style`). This
is a JSON file which contains a list of rules which are matched against every
tag of every object and then assign the tag its specific role.
### Configuration Rules
A single rule looks like this:
```json
{
"keys" : ["key1", "key2", ...],
"values" : {
"value1" : "prop",
"value2" : "prop1,prop2"
}
}
```
A rule first defines a list of keys to apply the rule to. This is always a list
of strings. The string may have four forms. An empty string matches against
any key. A string that ends in an asterisk `*` is a prefix match and accordingly
matches against any key that starts with the given string (minus the `*`). A
suffix match can be defined similarly with a string that starts with a `*`. Any
other string constitutes an exact match.
The second part of the rules defines a list of values and the properties that
apply to a successful match. Value strings may be either empty, which
means that they match any value, or describe an exact match. Prefix
or suffix matching of values is not possible.
For a rule to match, it has to find a valid combination of keys and values. The
resulting property is that of the matched values.
The rules in a configuration file are processed sequentially and the first
match for each tag wins.
A rule where key and value are the empty string is special. This defines the
fallback when none of the rules match. The fallback is always used as a last
resort when nothing else matches, no matter where the rule appears in the file.
Defining multiple fallback rules is not allowed. What happens in this case,
is undefined.
### Tag Properties
One or more of the following properties may be given for each tag:
* `main`
A principal tag. A new row will be added for the object with key and value
as `class` and `type`.
* `with_name`
When the tag is a principal tag (`main` property set): only really add a new
row, if there is any name tag found (a reference tag is not sufficient, see
below).
* `with_name_key`
When the tag is a principal tag (`main` property set): only really add a new
row, if there is also a name tag that matches the key of the principal tag.
For example, if the main tag is `bridge=yes`, then it will only be added as
an extra row, if there is a tag `bridge:name[:XXX]` for the same object.
If this property is set, all other names that are not domain-specific are
ignored.
* `fallback`
When the tag is a principal tag (`main` property set): only really add a new
row, when no other principal tags for this object have been found. Only one
fallback tag can win for an object.
* `operator`
When the tag is a principal tag (`main` property set): also include the
`operator` tag in the list of names. This is a special construct for an
out-dated tagging practise in OSM. Fuel stations and chain restaurants
in particular used to have the name of the chain tagged as `operator`.
These days the chain can be more commonly found in the `brand` tag but
there is still enough old data around to warrant this special case.
* `name`
Add tag to the list of names.
* `ref`
Add tag to the list of names as a reference. At the moment this only means
that the object is not considered to be named for `with_name`.
* `address`
Add tag to the list of address tags. If the tag starts with `addr:` or
`is_in:`, then this prefix is cut off before adding it to the list.
* `postcode`
Add the value as a postcode to the address tags. If multiple tags are
candidate for postcodes, one wins out and the others are dropped.
* `country`
Add the value as a country code to the address tags. The value must be a
two letter country code, otherwise it is ignored. If there are multiple
tags that match, then one wins out and the others are dropped.
* `house`
If no principle tags can be found for the object, still add the object with
`class`=`place` and `type`=`house`. Use this for address nodes that have no
other function.
* `interpolation`
Add this object as an address interpolation (appears as `class`=`place` and
`type`=`houses` in the database).
* `extra`
Add tag to the list of extra tags.
* `skip`
Skip the tag completely. Useful when a custom default fallback is defined
or to define exceptions to rules.
A rule can define as many of these properties for one match as it likes. For
example, if the property is `"main,extra"` then the tag will open a new row
but also have the tag appear in the list of extra tags.
There are a number of pre-defined styles in the `settings/` directory. It is
advisable to start from one of these styles when defining your own.
### Changing the Style of Existing Databases
There is normally no issue changing the style of a database that is already
imported and now kept up-to-date with change files. Just be aware that any
change in the style applies to updates only. If you want to change the data
that is already in the database, then a reimport is necessary.

View File

@@ -1,45 +0,0 @@
# Postcodes in Nominatim
The blog post
[Nominatim and Postcodes](https://www.openstreetmap.org/user/lonvia/diary/43143)
describes the handling implemented since Nominatim 3.1.
Postcode centroids (aka 'calculated postcodes') are generated by looking at all
postcodes of a country, grouping them and calculating the geometric centroid.
There is currently no logic to deal with extreme outliers (typos or other
mistakes in OSM data). There is also no check if a postcodes adheres to a
country's format, e.g. if Swiss postcodes are 4 digits.
## Regular updating calculated postcodes
The script to rerun the calculation is
`build/utils/update.php --calculate-postcodes`
and runs once per night on nominatim.openstreetmap.org.
## Finding places that share a specific postcode
In the Nominatim database run
```sql
SELECT address->'postcode' as pc,
osm_type, osm_id, class, type,
st_x(centroid) as lon, st_y(centroid) as lat
FROM placex
WHERE country_code='fr'
AND upper(trim (both ' ' from address->'postcode')) = '33210';
```
Alternatively on [Overpass](https://overpass-turbo.eu/) run the following query
```
[out:json][timeout:250];
area["name"="France"]->.boundaryarea;
(
nwr(area.boundaryarea)["addr:postcode"="33210"];
);
out body;
>;
out skel qt;
```

View File

@@ -1,90 +0,0 @@
# Place Ranking in Nominatim
Nominatim uses two metrics to rank a place: search rank and address rank.
Both can be assigned a value between 0 and 30. They serve slightly
different purposes, which are explained in this chapter.
## Search rank
The search rank describes the extent and importance of a place. It is used
when ranking search result. Simply put, if there are two results for a
search query which are otherwise equal, then the result with the _lower_
search rank will be appear higher in the result list.
Search ranks are not so important these days because many well-known
places use the Wikipedia importance ranking instead.
## Address rank
The address rank describes where a place shows up in an address hierarchy.
Usually only administrative boundaries and place nodes and areas are
eligible to be part of an address. All other objects have an address rank
of 0.
Note that the search rank of a place plays a role in the address computation
as well. When collecting the places that should make up the address parts
then only places are taken into account that have a lower address rank than
the search rank of the base object.
## Rank configuration
Search and address ranks are assigned to a place when it is first imported
into the database. There are a few hard-coded rules for the assignment:
* postcodes follow special rules according to their length
* boundaries that are not areas and railway=rail are dropped completely
* the following are always search rank 30 and address rank 0:
* highway nodes
* landuse that is not an area
Other than that, the ranks can be freely assigned via the JSON file
defined with `CONST_Address_Level_Config` according to their type and
the country they are in.
The address level configuration must consist of an array of configuration
entries, each containing a tag definition and an optional country array:
```
[ {
"tags" : {
"place" : {
"county" : 12,
"city" : 16,
},
"landuse" : {
"residential" : 22,
"" : 30
}
}
},
{
"countries" : [ "ca", "us" ],
"tags" : {
"boundary" : {
"administrative8" : 18,
"administrative9" : 20
},
"landuse" : {
"residential" : [22, 0]
}
}
}
]
```
The `countries` field contains a list of countries (as ISO 3166-1 alpha 2 code)
for which the definition applies. When the field is omitted, then the
definition is used as a fallback, when nothing more specific for a given
country exists.
`tags` contains the ranks for key/value pairs. The ranks can be either a
single number, in which case they are the search and address rank, or an array
of search and address rank (in that order). The value may be left empty.
Then the rank is used when no more specific value is found for the given
key.
Countries and key/value combination may appear in multiple definitions. Just
make sure that each combination of counrty/key/value appears only once per
file. Otherwise the import will fail with a UNIQUE INDEX constraint violation
on import.

View File

@@ -1,46 +0,0 @@
# Setup Test Environment
To test changes and contribute to Nominatim you should be able to run
the test suite(s). For many usecases it's enough to create a Vagrant
virtual machine (see `VAGRANT.md`), import one small country into the
database.
## Prerequisites
Nominatim supports a range of PHP versions and PHPUnit versions also
move fast. We try to test against the newest stable PHP release and
PHPUnit version even though we expect many Nominatim users will install
older version on their production servers.
#### Ubuntu 20
sudo apt-get install -y phpunit php-codesniffer php-cgi
pip3 install --user behave nose
#### Ubuntu 18
pip3 install --user behave nose
sudo apt-get install -y composer php-cgi php-cli php-mbstring php-xml zip unzip
composer global require "squizlabs/php_codesniffer=*"
sudo ln -s ~/.config/composer/vendor/bin/phpcs /usr/bin/
composer global require "phpunit/phpunit=8.*"
sudo ln -s ~/.config/composer/vendor/bin/phpunit /usr/bin/
#### CentOS 7 or 8
sudo dnf install -y php-dom php-mbstring
pip3 install --user behave nose
composer global require "squizlabs/php_codesniffer=*"
sudo ln -s ~/.config/composer/vendor/bin/phpcs /usr/bin/
composer global require "phpunit/phpunit=^7"
sudo ln -s ~/.config/composer/vendor/bin/phpunit /usr/bin/
## Run tests, code linter, code coverage
See `README.md` in `test` subdirectory.

View File

@@ -1,6 +1,6 @@
# Basic Architecture
Nominatim provides geocoding based on OpenStreetMap data. It uses a PostgreSQL
Nominatim provides geocoding based on OpenStreetMap data. It uses a Postgresql
database as a backend for storing the data.
There are three basic parts to Nominatim's architecture: the data import,
@@ -15,10 +15,10 @@ the import can be found in the database table `place`.
The __address computation__ or __indexing__ stage takes the data from `place`
and adds additional information needed for geocoding. It ranks the places by
importance, links objects that belong together and computes addresses and
the search index. Most of this work is done in PL/pgSQL via database triggers
the search index. Most of this work is done in Pl/pqSQL via database triggers
and can be found in the file `sql/functions.sql`.
The __search frontend__ implements the actual API. It takes search
and reverse geocoding queries from the user, looks up the data and
The __search frontend__ implements the actual API. It takes queries for
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.

View File

@@ -1,7 +1,7 @@
site_name: Nominatim 3.5.2
site_name: Nominatim Documentation
theme: readthedocs
docs_dir: ${CMAKE_CURRENT_BINARY_DIR}
site_url: https://nominatim.org
site_url: http://nominatim.org
repo_url: https://github.com/openstreetmap/Nominatim
pages:
- 'Introduction' : 'index.md'
@@ -11,37 +11,22 @@ pages:
- 'Reverse': 'api/Reverse.md'
- 'Address Lookup': 'api/Lookup.md'
- 'Details' : 'api/Details.md'
- 'Status' : 'api/Status.md'
- 'Place Output Formats': 'api/Output.md'
- 'FAQ': 'api/Faq.md'
- 'Administration Guide':
- 'Basic Installation': 'admin/Installation.md'
- 'Importing' : 'admin/Import.md'
- 'Updating' : 'admin/Update.md'
- 'Advanced Installations' : 'admin/Advanced-Installations.md'
- 'Importing and Updating' : 'admin/Import-and-Update.md'
- 'Migration from older Versions' : 'admin/Migration.md'
- 'Troubleshooting' : 'admin/Faq.md'
- 'Developers Guide':
- 'Overview' : 'develop/overview.md'
- 'OSM Data Import' : 'develop/Import.md'
- 'Place Ranking' : 'develop/Ranking.md'
- 'Postcodes' : 'develop/Postcodes.md'
- 'Setup Test Environment' : 'develop/Setup.md'
- 'Documentation' : 'develop/Documentation.md'
- 'External Data Sources':
- 'Overview' : 'data-sources/overview.md'
- 'US Census (Tiger)': 'data-sources/US-Tiger.md'
- 'GB Postcodes': 'data-sources/GB-Postcodes.md'
- 'Country Grid': 'data-sources/Country-Grid.md'
- 'Wikipedia & Wikidata': 'data-sources/Wikipedia-Wikidata.md'
- 'Appendix':
- 'Installation on CentOS 7' : 'appendix/Install-on-Centos-7.md'
- 'Installation on CentOS 8' : 'appendix/Install-on-Centos-8.md'
- 'Installation on Ubuntu 16' : 'appendix/Install-on-Ubuntu-16.md'
- 'Installation on Ubuntu 18' : 'appendix/Install-on-Ubuntu-18.md'
- 'Installation on Ubuntu 20' : 'appendix/Install-on-Ubuntu-20.md'
markdown_extensions:
- codehilite
- admonition
- codehilite:
use_pygments: False
- toc:
permalink:
extra_css: [extra.css, styles.css]
extra_css: [extra.css]

View File

@@ -1,69 +0,0 @@
.codehilite .hll { background-color: #ffffcc }
.codehilite { background: #f0f0f0; }
.codehilite .c { color: #60a0b0; font-style: italic } /* Comment */
.codehilite .err { /* border: 1px solid #FF0000 */ } /* Error */
.codehilite .k { color: #007020; font-weight: bold } /* Keyword */
.codehilite .o { color: #666666 } /* Operator */
.codehilite .ch { color: #60a0b0; font-style: italic } /* Comment.Hashbang */
.codehilite .cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */
.codehilite .cp { color: #007020 } /* Comment.Preproc */
.codehilite .cpf { color: #60a0b0; font-style: italic } /* Comment.PreprocFile */
.codehilite .c1 { color: #60a0b0; font-style: italic } /* Comment.Single */
.codehilite .cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */
.codehilite .gd { color: #A00000 } /* Generic.Deleted */
.codehilite .ge { font-style: italic } /* Generic.Emph */
.codehilite .gr { color: #FF0000 } /* Generic.Error */
.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.codehilite .gi { color: #00A000 } /* Generic.Inserted */
.codehilite .go { color: #888888 } /* Generic.Output */
.codehilite .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
.codehilite .gs { font-weight: bold } /* Generic.Strong */
.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.codehilite .gt { color: #0044DD } /* Generic.Traceback */
.codehilite .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
.codehilite .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
.codehilite .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
.codehilite .kp { color: #007020 } /* Keyword.Pseudo */
.codehilite .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
.codehilite .kt { color: #902000 } /* Keyword.Type */
.codehilite .m { color: #40a070 } /* Literal.Number */
.codehilite .s { color: #4070a0 } /* Literal.String */
.codehilite .na { color: #4070a0 } /* Name.Attribute */
.codehilite .nb { color: #007020 } /* Name.Builtin */
.codehilite .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
.codehilite .no { color: #60add5 } /* Name.Constant */
.codehilite .nd { color: #555555; font-weight: bold } /* Name.Decorator */
.codehilite .ni { color: #d55537; font-weight: bold } /* Name.Entity */
.codehilite .ne { color: #007020 } /* Name.Exception */
.codehilite .nf { color: #06287e } /* Name.Function */
.codehilite .nl { color: #002070; font-weight: bold } /* Name.Label */
.codehilite .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
.codehilite .nt { color: #062873; font-weight: bold } /* Name.Tag */
.codehilite .nv { color: #bb60d5 } /* Name.Variable */
.codehilite .ow { color: #007020; font-weight: bold } /* Operator.Word */
.codehilite .w { color: #bbbbbb } /* Text.Whitespace */
.codehilite .mb { color: #40a070 } /* Literal.Number.Bin */
.codehilite .mf { color: #40a070 } /* Literal.Number.Float */
.codehilite .mh { color: #40a070 } /* Literal.Number.Hex */
.codehilite .mi { color: #40a070 } /* Literal.Number.Integer */
.codehilite .mo { color: #40a070 } /* Literal.Number.Oct */
.codehilite .sa { color: #4070a0 } /* Literal.String.Affix */
.codehilite .sb { color: #4070a0 } /* Literal.String.Backtick */
.codehilite .sc { color: #4070a0 } /* Literal.String.Char */
.codehilite .dl { color: #4070a0 } /* Literal.String.Delimiter */
.codehilite .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
.codehilite .s2 { color: #4070a0 } /* Literal.String.Double */
.codehilite .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
.codehilite .sh { color: #4070a0 } /* Literal.String.Heredoc */
.codehilite .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
.codehilite .sx { color: #c65d09 } /* Literal.String.Other */
.codehilite .sr { color: #235388 } /* Literal.String.Regex */
.codehilite .s1 { color: #4070a0 } /* Literal.String.Single */
.codehilite .ss { color: #517918 } /* Literal.String.Symbol */
.codehilite .bp { color: #007020 } /* Name.Builtin.Pseudo */
.codehilite .fm { color: #06287e } /* Name.Function.Magic */
.codehilite .vc { color: #bb60d5 } /* Name.Variable.Class */
.codehilite .vg { color: #bb60d5 } /* Name.Variable.Global */
.codehilite .vi { color: #bb60d5 } /* Name.Variable.Instance */
.codehilite .vm { color: #bb60d5 } /* Name.Variable.Magic */
.codehilite .il { color: #40a070 } /* Literal.Number.Integer.Long */

View File

@@ -9,32 +9,29 @@ require_once(CONST_BasePath.'/lib/ClassTypes.php');
*/
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));
$mLangPref = 'ARRAY['.join(',', array_map('getDBQuoted', $mLangPref)).']';
}
if (!isset($sHousenumber)) {
if (!$sHousenumber) {
$sHousenumber = -1;
}
$sSQL = 'SELECT *,';
$sSQL .= ' get_name_by_language(name,'.$mLangPref.') as localname';
$sSQL .= ' get_name_by_language(name,'.$mLangPref.') as localname';
$sSQL .= ' FROM get_addressdata('.$iPlaceID.','.$sHousenumber.')';
$sSQL .= ' ORDER BY rank_address DESC, isaddress DESC';
$sSQL .= ' ORDER BY rank_address desc,isaddress DESC';
$this->aAddressLines = $oDB->getAll($sSQL);
$this->aAddressLines = chksql($oDB->getAll($sSQL));
}
private static function isAddress($aLine)
{
return $aLine['isaddress'] || $aLine['type'] == 'country_code';
return $aLine['isaddress'] == 't' || $aLine['type'] == 'country_code';
}
public function getAddressDetails($bAll = false)
@@ -43,7 +40,7 @@ class AddressDetails
return $this->aAddressLines;
}
return array_filter($this->aAddressLines, array(__CLASS__, 'isAddress'));
return array_filter($this->aAddressLines, 'AddressDetails::isAddress');
}
public function getLocaleAddress()
@@ -52,7 +49,7 @@ class AddressDetails
$sPrevResult = '';
foreach ($this->aAddressLines as $aLine) {
if ($aLine['isaddress'] && $sPrevResult != $aLine['localname']) {
if ($aLine['isaddress'] == 't' && $sPrevResult != $aLine['localname']) {
$sPrevResult = $aLine['localname'];
$aParts[] = $sPrevResult;
}
@@ -61,98 +58,52 @@ class AddressDetails
return join(', ', $aParts);
}
public function getAddressNames($sCountry = null)
public function getAddressNames()
{
$aAddress = array();
$aFallback = array();
foreach ($this->aAddressLines as $aLine) {
if (!self::isAddress($aLine)) {
continue;
}
$sTypeLabel = ClassTypes\getLabelTag($aLine);
$bFallback = false;
$aTypeLabel = ClassTypes\getInfo($aLine);
$sName = null;
if (isset($aLine['localname']) && $aLine['localname']!=='') {
if ($aTypeLabel === false) {
$aTypeLabel = ClassTypes\getFallbackInfo($aLine);
$bFallback = true;
}
$sName = false;
if (isset($aLine['localname']) && $aLine['localname']) {
$sName = $aLine['localname'];
} elseif (isset($aLine['housenumber']) && $aLine['housenumber']!=='') {
} elseif (isset($aLine['housenumber']) && $aLine['housenumber']) {
$sName = $aLine['housenumber'];
}
if (isset($sName)) {
$sTypeLabel = strtolower(str_replace(' ', '_', $sTypeLabel));
if ($sName) {
$sTypeLabel = strtolower(isset($aTypeLabel['simplelabel']) ? $aTypeLabel['simplelabel'] : $aTypeLabel['label']);
$sTypeLabel = str_replace(' ', '_', $sTypeLabel);
if (!isset($aAddress[$sTypeLabel])
|| isset($aFallback[$sTypeLabel])
|| $aLine['class'] == 'place'
) {
$aAddress[$sTypeLabel] = $sName;
if ($bFallback) {
$aFallback[$sTypeLabel] = $bFallback;
}
}
}
}
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) {
foreach ($this->aAddressLines as $aLine) {
if (self::isAddress($aLine)
&& isset($aLine['admin_level'])
&& $aLine['admin_level'] < 15

View File

@@ -2,559 +2,373 @@
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)
function getInfo($aPlace)
{
$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 ($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'];
$aClassType = getList();
if (isset($aPlace['admin_level'])) {
$sName = $aPlace['class'].':'.$aPlace['type'].':'.$aPlace['admin_level'];
if (isset($aClassType[$sName])) {
return $aClassType[$sName];
}
}
return strtolower(str_replace(' ', '_', $sLabel));
$sName = $aPlace['class'].':'.$aPlace['type'];
if (isset($aClassType[$sName])) {
return $aClassType[$sName];
}
return false;
}
/**
* Create a label for the given place.
*
* @param array[] $aPlace Information about the place to label.
*/
function getLabel($aPlace, $sCountry = null)
function getFallbackInfo($aPlace)
{
if (isset($aPlace['place_type'])) {
return ucwords(str_replace('_', ' ', $aPlace['place_type']));
$aClassType = getList();
$sFallback = 'boundary:administrative:'.((int)($aPlace['rank_address']/2));
if (isset($aClassType[$sFallback])) {
return $aClassType[$sFallback];
}
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 array('simplelabel' => 'address'.$aPlace['rank_address']);
}
/**
* 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')
function getProperty($aPlace, $sProp, $mDefault = false)
{
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'
),
'no' => array (
3 => 'State',
4 => 'County'
),
'se' => array (
3 => 'State',
4 => 'County'
)
);
$aClassType = getList();
if (isset($aBoundaryList[$sCountry])
&& isset($aBoundaryList[$sCountry][$iAdminLevel])
) {
return $aBoundaryList[$sCountry][$iAdminLevel];
if (isset($aPlace['admin_level'])) {
$sName = $aPlace['class'].':'.$aPlace['type'].':'.$aPlace['admin_level'];
if (isset($aClassType[$sName]) && isset($aClassType[$sName][$sProp])) {
return $aClassType[$sName][$sProp];
}
}
return $aBoundaryList['default'][$iAdminLevel] ?? $sFallback;
$sName = $aPlace['class'].':'.$aPlace['type'];
if (isset($aClassType[$sName]) && isset($aClassType[$sName][$sProp])) {
return $aClassType[$sName][$sProp];
}
return $mDefault;
}
/**
* 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)
function getListWithImportance()
{
$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
static $aOrders = null;
if ($aOrders === null) {
$aOrders = getList();
$i = 1;
foreach ($aOrders as $sID => $a) {
$aOrders[$sID]['importance'] = $i++;
}
}
return $aOrders;
}
function getList()
{
return array(
'boundary:administrative:1' => array('label' => 'Continent', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
'boundary:administrative:2' => array('label' => 'Country', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
'place:country' => array('label' => 'Country', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defzoom' => 6, 'defdiameter' => 15),
'boundary:administrative:3' => array('label' => 'State', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
'boundary:administrative:4' => array('label' => 'State', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
'place:state' => array('label' => 'State', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defzoom' => 8, 'defdiameter' => 5.12),
'boundary:administrative:5' => array('label' => 'State District', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
'boundary:administrative:6' => array('label' => 'County', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
'boundary:administrative:7' => array('label' => 'County', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
'place:county' => array('label' => 'County', 'frequency' => 108, 'icon' => 'poi_boundary_administrative', 'defzoom' => 10, 'defdiameter' => 1.28),
'boundary:administrative:8' => array('label' => 'City', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
'place:city' => array('label' => 'City', 'frequency' => 66, 'icon' => 'poi_place_city', 'defzoom' => 12, 'defdiameter' => 0.32),
'boundary:administrative:9' => array('label' => 'City District', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
'boundary:administrative:10' => array('label' => 'Suburb', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
'boundary:administrative:11' => array('label' => 'Neighbourhood', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
'place:region' => array('label' => 'Region', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defzoom' => 8, 'defdiameter' => 0.04),
'place:island' => array('label' => 'Island', 'frequency' => 288, 'defzoom' => 11, 'defdiameter' => 0.64),
'boundary:administrative' => array('label' => 'Administrative', 'frequency' => 413, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
'boundary:postal_code' => array('label' => 'Postcode', 'frequency' => 413, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
'place:town' => array('label' => 'Town', 'frequency' => 1497, 'icon' => 'poi_place_town', 'defzoom' => 14, 'defdiameter' => 0.08),
'place:village' => array('label' => 'Village', 'frequency' => 11230, 'icon' => 'poi_place_village', 'defzoom' => 15, 'defdiameter' => 0.04),
'place:hamlet' => array('label' => 'Hamlet', 'frequency' => 7075, 'icon' => 'poi_place_village', 'defzoom' => 15, 'defdiameter' => 0.04),
'place:suburb' => array('label' => 'Suburb', 'frequency' => 2528, 'icon' => 'poi_place_village', 'defdiameter' => 0.04),
'place:locality' => array('label' => 'Locality', 'frequency' => 4113, 'icon' => 'poi_place_village', 'defdiameter' => 0.02),
'landuse:farm' => array('label' => 'Farm', 'frequency' => 1201, 'defdiameter' => 0.02),
'place:farm' => array('label' => 'Farm', 'frequency' => 1162, 'defdiameter' => 0.02),
'highway:motorway_junction' => array('label' => 'Motorway Junction', 'frequency' => 1126, 'simplelabel' => 'Junction'),
'highway:motorway' => array('label' => 'Motorway', 'frequency' => 4627, 'simplelabel' => 'Road'),
'highway:trunk' => array('label' => 'Trunk', 'frequency' => 23084, 'simplelabel' => 'Road'),
'highway:primary' => array('label' => 'Primary', 'frequency' => 32138, 'simplelabel' => 'Road'),
'highway:secondary' => array('label' => 'Secondary', 'frequency' => 25807, 'simplelabel' => 'Road'),
'highway:tertiary' => array('label' => 'Tertiary', 'frequency' => 29829, 'simplelabel' => 'Road'),
'highway:residential' => array('label' => 'Residential', 'frequency' => 361498, 'simplelabel' => 'Road'),
'highway:unclassified' => array('label' => 'Unclassified', 'frequency' => 66441, 'simplelabel' => 'Road'),
'highway:living_street' => array('label' => 'Living Street', 'frequency' => 710, 'simplelabel' => 'Road'),
'highway:service' => array('label' => 'Service', 'frequency' => 9963, 'simplelabel' => 'Road'),
'highway:track' => array('label' => 'Track', 'frequency' => 2565, 'simplelabel' => 'Road'),
'highway:road' => array('label' => 'Road', 'frequency' => 591, 'simplelabel' => 'Road'),
'highway:byway' => array('label' => 'Byway', 'frequency' => 346, 'simplelabel' => 'Road'),
'highway:bridleway' => array('label' => 'Bridleway', 'frequency' => 1556),
'highway:cycleway' => array('label' => 'Cycleway', 'frequency' => 2419),
'highway:pedestrian' => array('label' => 'Pedestrian', 'frequency' => 2757),
'highway:footway' => array('label' => 'Footway', 'frequency' => 15008),
'highway:steps' => array('label' => 'Steps', 'frequency' => 444, 'simplelabel' => 'Footway'),
'highway:motorway_link' => array('label' => 'Motorway Link', 'frequency' => 795, 'simplelabel' => 'Road'),
'highway:trunk_link' => array('label' => 'Trunk Link', 'frequency' => 1258, 'simplelabel' => 'Road'),
'highway:primary_link' => array('label' => 'Primary Link', 'frequency' => 313, 'simplelabel' => 'Road'),
'landuse:industrial' => array('label' => 'Industrial', 'frequency' => 1062),
'landuse:residential' => array('label' => 'Residential', 'frequency' => 886),
'landuse:retail' => array('label' => 'Retail', 'frequency' => 754),
'landuse:commercial' => array('label' => 'Commercial', 'frequency' => 657),
'place:airport' => array('label' => 'Airport', 'frequency' => 36, 'icon' => 'transport_airport2', 'defdiameter' => 0.03),
'aeroway:aerodrome' => array('label' => 'Aerodrome', 'frequency' => 36, 'icon' => 'transport_airport2', 'defdiameter' => 0.03),
'aeroway' => array('label' => 'Aeroway', 'frequency' => 36, 'icon' => 'transport_airport2', 'defdiameter' => 0.03),
'railway:station' => array('label' => 'Station', 'frequency' => 3431, 'icon' => 'transport_train_station2', 'defdiameter' => 0.01),
'amenity:place_of_worship' => array('label' => 'Place Of Worship', 'frequency' => 9049, 'icon' => 'place_of_worship_unknown3'),
'amenity:pub' => array('label' => 'Pub', 'frequency' => 18969, 'icon' => 'food_pub'),
'amenity:bar' => array('label' => 'Bar', 'frequency' => 164, 'icon' => 'food_bar'),
'amenity:university' => array('label' => 'University', 'frequency' => 607, 'icon' => 'education_university'),
'tourism:museum' => array('label' => 'Museum', 'frequency' => 543, 'icon' => 'tourist_museum'),
'amenity:arts_centre' => array('label' => 'Arts Centre', 'frequency' => 136, 'icon' => 'tourist_art_gallery2'),
'tourism:zoo' => array('label' => 'Zoo', 'frequency' => 47, 'icon' => 'tourist_zoo'),
'tourism:theme_park' => array('label' => 'Theme Park', 'frequency' => 24, 'icon' => 'poi_point_of_interest'),
'tourism:attraction' => array('label' => 'Attraction', 'frequency' => 1463, 'icon' => 'poi_point_of_interest'),
'leisure:golf_course' => array('label' => 'Golf Course', 'frequency' => 712, 'icon' => 'sport_golf'),
'historic:castle' => array('label' => 'Castle', 'frequency' => 316, 'icon' => 'tourist_castle'),
'amenity:hospital' => array('label' => 'Hospital', 'frequency' => 879, 'icon' => 'health_hospital'),
'amenity:school' => array('label' => 'School', 'frequency' => 8192, 'icon' => 'education_school'),
'amenity:theatre' => array('label' => 'Theatre', 'frequency' => 371, 'icon' => 'tourist_theatre'),
'amenity:public_building' => array('label' => 'Public Building', 'frequency' => 985),
'amenity:library' => array('label' => 'Library', 'frequency' => 794, 'icon' => 'amenity_library'),
'amenity:townhall' => array('label' => 'Townhall', 'frequency' => 242),
'amenity:community_centre' => array('label' => 'Community Centre', 'frequency' => 157),
'amenity:fire_station' => array('label' => 'Fire Station', 'frequency' => 221, 'icon' => 'amenity_firestation3'),
'amenity:police' => array('label' => 'Police', 'frequency' => 334, 'icon' => 'amenity_police2'),
'amenity:bank' => array('label' => 'Bank', 'frequency' => 1248, 'icon' => 'money_bank2'),
'amenity:post_office' => array('label' => 'Post Office', 'frequency' => 859, 'icon' => 'amenity_post_office'),
'leisure:park' => array('label' => 'Park', 'frequency' => 2378),
'amenity:park' => array('label' => 'Park', 'frequency' => 53),
'landuse:park' => array('label' => 'Park', 'frequency' => 50),
'landuse:recreation_ground' => array('label' => 'Recreation Ground', 'frequency' => 517),
'tourism:hotel' => array('label' => 'Hotel', 'frequency' => 2150, 'icon' => 'accommodation_hotel2'),
'tourism:motel' => array('label' => 'Motel', 'frequency' => 43),
'amenity:cinema' => array('label' => 'Cinema', 'frequency' => 277, 'icon' => 'tourist_cinema'),
'tourism:artwork' => array('label' => 'Artwork', 'frequency' => 171, 'icon' => 'tourist_art_gallery2'),
'historic:archaeological_site' => array('label' => 'Archaeological Site', 'frequency' => 407, 'icon' => 'tourist_archaeological2'),
'amenity:doctors' => array('label' => 'Doctors', 'frequency' => 581, 'icon' => 'health_doctors'),
'leisure:sports_centre' => array('label' => 'Sports Centre', 'frequency' => 767, 'icon' => 'sport_leisure_centre'),
'leisure:swimming_pool' => array('label' => 'Swimming Pool', 'frequency' => 24, 'icon' => 'sport_swimming_outdoor'),
'shop:supermarket' => array('label' => 'Supermarket', 'frequency' => 2673, 'icon' => 'shopping_supermarket'),
'shop:convenience' => array('label' => 'Convenience', 'frequency' => 1469, 'icon' => 'shopping_convenience'),
'amenity:restaurant' => array('label' => 'Restaurant', 'frequency' => 3179, 'icon' => 'food_restaurant'),
'amenity:fast_food' => array('label' => 'Fast Food', 'frequency' => 2289, 'icon' => 'food_fastfood'),
'amenity:cafe' => array('label' => 'Cafe', 'frequency' => 1780, 'icon' => 'food_cafe'),
'tourism:guest_house' => array('label' => 'Guest House', 'frequency' => 223, 'icon' => 'accommodation_bed_and_breakfast'),
'amenity:pharmacy' => array('label' => 'Pharmacy', 'frequency' => 733, 'icon' => 'health_pharmacy_dispensing'),
'amenity:fuel' => array('label' => 'Fuel', 'frequency' => 1308, 'icon' => 'transport_fuel'),
'natural:peak' => array('label' => 'Peak', 'frequency' => 3212, 'icon' => 'poi_peak'),
'waterway:waterfall' => array('label' => 'Waterfall', 'frequency' => 24),
'natural:wood' => array('label' => 'Wood', 'frequency' => 1845, 'icon' => 'landuse_coniferous_and_deciduous'),
'natural:water' => array('label' => 'Water', 'frequency' => 1790),
'landuse:forest' => array('label' => 'Forest', 'frequency' => 467),
'landuse:cemetery' => array('label' => 'Cemetery', 'frequency' => 463),
'landuse:allotments' => array('label' => 'Allotments', 'frequency' => 408),
'landuse:farmyard' => array('label' => 'Farmyard', 'frequency' => 397),
'railway:rail' => array('label' => 'Rail', 'frequency' => 4894),
'waterway:canal' => array('label' => 'Canal', 'frequency' => 1723),
'waterway:river' => array('label' => 'River', 'frequency' => 4089),
'waterway:stream' => array('label' => 'Stream', 'frequency' => 2684),
'shop:bicycle' => array('label' => 'Bicycle', 'frequency' => 349, 'icon' => 'shopping_bicycle'),
'shop:clothes' => array('label' => 'Clothes', 'frequency' => 315, 'icon' => 'shopping_clothes'),
'shop:hairdresser' => array('label' => 'Hairdresser', 'frequency' => 312, 'icon' => 'shopping_hairdresser'),
'shop:doityourself' => array('label' => 'Doityourself', 'frequency' => 247, 'icon' => 'shopping_diy'),
'shop:estate_agent' => array('label' => 'Estate Agent', 'frequency' => 162, 'icon' => 'shopping_estateagent2'),
'shop:car' => array('label' => 'Car', 'frequency' => 159, 'icon' => 'shopping_car'),
'shop:garden_centre' => array('label' => 'Garden Centre', 'frequency' => 143, 'icon' => 'shopping_garden_centre'),
'shop:car_repair' => array('label' => 'Car Repair', 'frequency' => 141, 'icon' => 'shopping_car_repair'),
'shop:newsagent' => array('label' => 'Newsagent', 'frequency' => 132),
'shop:bakery' => array('label' => 'Bakery', 'frequency' => 129, 'icon' => 'shopping_bakery'),
'shop:furniture' => array('label' => 'Furniture', 'frequency' => 124),
'shop:butcher' => array('label' => 'Butcher', 'frequency' => 105, 'icon' => 'shopping_butcher'),
'shop:apparel' => array('label' => 'Apparel', 'frequency' => 98, 'icon' => 'shopping_clothes'),
'shop:electronics' => array('label' => 'Electronics', 'frequency' => 96),
'shop:department_store' => array('label' => 'Department Store', 'frequency' => 86),
'shop:books' => array('label' => 'Books', 'frequency' => 85),
'shop:yes' => array('label' => 'Shop', 'frequency' => 68),
'shop:outdoor' => array('label' => 'Outdoor', 'frequency' => 67),
'shop:mall' => array('label' => 'Mall', 'frequency' => 63),
'shop:florist' => array('label' => 'Florist', 'frequency' => 61),
'shop:charity' => array('label' => 'Charity', 'frequency' => 60),
'shop:hardware' => array('label' => 'Hardware', 'frequency' => 59),
'shop:laundry' => array('label' => 'Laundry', 'frequency' => 51, 'icon' => 'shopping_laundrette'),
'shop:shoes' => array('label' => 'Shoes', 'frequency' => 49),
'shop:beverages' => array('label' => 'Beverages', 'frequency' => 48, 'icon' => 'shopping_alcohol'),
'shop:dry_cleaning' => array('label' => 'Dry Cleaning', 'frequency' => 46),
'shop:carpet' => array('label' => 'Carpet', 'frequency' => 45),
'shop:computer' => array('label' => 'Computer', 'frequency' => 44),
'shop:alcohol' => array('label' => 'Alcohol', 'frequency' => 44, 'icon' => 'shopping_alcohol'),
'shop:optician' => array('label' => 'Optician', 'frequency' => 55, 'icon' => 'health_opticians'),
'shop:chemist' => array('label' => 'Chemist', 'frequency' => 42, 'icon' => 'health_pharmacy'),
'shop:gallery' => array('label' => 'Gallery', 'frequency' => 38, 'icon' => 'tourist_art_gallery2'),
'shop:mobile_phone' => array('label' => 'Mobile Phone', 'frequency' => 37),
'shop:sports' => array('label' => 'Sports', 'frequency' => 37),
'shop:jewelry' => array('label' => 'Jewelry', 'frequency' => 32, 'icon' => 'shopping_jewelry'),
'shop:pet' => array('label' => 'Pet', 'frequency' => 29),
'shop:beauty' => array('label' => 'Beauty', 'frequency' => 28),
'shop:stationery' => array('label' => 'Stationery', 'frequency' => 25),
'shop:shopping_centre' => array('label' => 'Shopping Centre', 'frequency' => 25),
'shop:general' => array('label' => 'General', 'frequency' => 25),
'shop:electrical' => array('label' => 'Electrical', 'frequency' => 25),
'shop:toys' => array('label' => 'Toys', 'frequency' => 23),
'shop:jeweller' => array('label' => 'Jeweller', 'frequency' => 23),
'shop:betting' => array('label' => 'Betting', 'frequency' => 23),
'shop:household' => array('label' => 'Household', 'frequency' => 21),
'shop:travel_agency' => array('label' => 'Travel Agency', 'frequency' => 21),
'shop:hifi' => array('label' => 'Hifi', 'frequency' => 21),
'amenity:shop' => array('label' => 'Shop', 'frequency' => 61),
'tourism:information' => array('label' => 'Information', 'frequency' => 224, 'icon' => 'amenity_information'),
'place:house' => array('label' => 'House', 'frequency' => 2086, 'defzoom' => 18),
'place:house_name' => array('label' => 'House', 'frequency' => 2086, 'defzoom' => 18),
'place:house_number' => array('label' => 'House Number', 'frequency' => 2086, 'defzoom' => 18),
'place:country_code' => array('label' => 'Country Code', 'frequency' => 2086, 'defzoom' => 18),
//
'leisure:pitch' => array('label' => 'Pitch', 'frequency' => 762),
'highway:unsurfaced' => array('label' => 'Unsurfaced', 'frequency' => 492),
'historic:ruins' => array('label' => 'Ruins', 'frequency' => 483, 'icon' => 'tourist_ruin'),
'amenity:college' => array('label' => 'College', 'frequency' => 473, 'icon' => 'education_school'),
'historic:monument' => array('label' => 'Monument', 'frequency' => 470, 'icon' => 'tourist_monument'),
'railway:subway' => array('label' => 'Subway', 'frequency' => 385),
'historic:memorial' => array('label' => 'Memorial', 'frequency' => 382, 'icon' => 'tourist_monument'),
'leisure:nature_reserve' => array('label' => 'Nature Reserve', 'frequency' => 342),
'leisure:common' => array('label' => 'Common', 'frequency' => 322),
'waterway:lock_gate' => array('label' => 'Lock Gate', 'frequency' => 321),
'natural:fell' => array('label' => 'Fell', 'frequency' => 308),
'amenity:nightclub' => array('label' => 'Nightclub', 'frequency' => 292),
'highway:path' => array('label' => 'Path', 'frequency' => 287),
'leisure:garden' => array('label' => 'Garden', 'frequency' => 285),
'landuse:reservoir' => array('label' => 'Reservoir', 'frequency' => 276),
'leisure:playground' => array('label' => 'Playground', 'frequency' => 264),
'leisure:stadium' => array('label' => 'Stadium', 'frequency' => 212),
'historic:mine' => array('label' => 'Mine', 'frequency' => 193, 'icon' => 'poi_mine'),
'natural:cliff' => array('label' => 'Cliff', 'frequency' => 193),
'tourism:caravan_site' => array('label' => 'Caravan Site', 'frequency' => 183, 'icon' => 'accommodation_caravan_park'),
'amenity:bus_station' => array('label' => 'Bus Station', 'frequency' => 181, 'icon' => 'transport_bus_station'),
'amenity:kindergarten' => array('label' => 'Kindergarten', 'frequency' => 179),
'highway:construction' => array('label' => 'Construction', 'frequency' => 176),
'amenity:atm' => array('label' => 'Atm', 'frequency' => 172, 'icon' => 'money_atm2'),
'amenity:emergency_phone' => array('label' => 'Emergency Phone', 'frequency' => 164),
'waterway:lock' => array('label' => 'Lock', 'frequency' => 146),
'waterway:riverbank' => array('label' => 'Riverbank', 'frequency' => 143),
'natural:coastline' => array('label' => 'Coastline', 'frequency' => 142),
'tourism:viewpoint' => array('label' => 'Viewpoint', 'frequency' => 140, 'icon' => 'tourist_view_point'),
'tourism:hostel' => array('label' => 'Hostel', 'frequency' => 140),
'tourism:bed_and_breakfast' => array('label' => 'Bed And Breakfast', 'frequency' => 140, 'icon' => 'accommodation_bed_and_breakfast'),
'railway:halt' => array('label' => 'Halt', 'frequency' => 135),
'railway:platform' => array('label' => 'Platform', 'frequency' => 134),
'railway:tram' => array('label' => 'Tram', 'frequency' => 130, 'icon' => 'transport_tram_stop'),
'amenity:courthouse' => array('label' => 'Courthouse', 'frequency' => 129, 'icon' => 'amenity_court'),
'amenity:recycling' => array('label' => 'Recycling', 'frequency' => 126, 'icon' => 'amenity_recycling'),
'amenity:dentist' => array('label' => 'Dentist', 'frequency' => 124, 'icon' => 'health_dentist'),
'natural:beach' => array('label' => 'Beach', 'frequency' => 121, 'icon' => 'tourist_beach'),
'place:moor' => array('label' => 'Moor', 'frequency' => 118),
'amenity:grave_yard' => array('label' => 'Grave Yard', 'frequency' => 110),
'waterway:drain' => array('label' => 'Drain', 'frequency' => 108),
'landuse:grass' => array('label' => 'Grass', 'frequency' => 106),
'landuse:village_green' => array('label' => 'Village Green', 'frequency' => 106),
'natural:bay' => array('label' => 'Bay', 'frequency' => 102),
'railway:tram_stop' => array('label' => 'Tram Stop', 'frequency' => 101, 'icon' => 'transport_tram_stop'),
'leisure:marina' => array('label' => 'Marina', 'frequency' => 98),
'highway:stile' => array('label' => 'Stile', 'frequency' => 97),
'natural:moor' => array('label' => 'Moor', 'frequency' => 95),
'railway:light_rail' => array('label' => 'Light Rail', 'frequency' => 91),
'railway:narrow_gauge' => array('label' => 'Narrow Gauge', 'frequency' => 90),
'natural:land' => array('label' => 'Land', 'frequency' => 86),
'amenity:village_hall' => array('label' => 'Village Hall', 'frequency' => 82),
'waterway:dock' => array('label' => 'Dock', 'frequency' => 80),
'amenity:veterinary' => array('label' => 'Veterinary', 'frequency' => 79),
'landuse:brownfield' => array('label' => 'Brownfield', 'frequency' => 77),
'leisure:track' => array('label' => 'Track', 'frequency' => 76),
'railway:historic_station' => array('label' => 'Historic Station', 'frequency' => 74),
'landuse:construction' => array('label' => 'Construction', 'frequency' => 72),
'amenity:prison' => array('label' => 'Prison', 'frequency' => 71, 'icon' => 'amenity_prison'),
'landuse:quarry' => array('label' => 'Quarry', 'frequency' => 71),
'amenity:telephone' => array('label' => 'Telephone', 'frequency' => 70),
'highway:traffic_signals' => array('label' => 'Traffic Signals', 'frequency' => 66),
'natural:heath' => array('label' => 'Heath', 'frequency' => 62),
'historic:house' => array('label' => 'House', 'frequency' => 61),
'amenity:social_club' => array('label' => 'Social Club', 'frequency' => 61),
'landuse:military' => array('label' => 'Military', 'frequency' => 61),
'amenity:health_centre' => array('label' => 'Health Centre', 'frequency' => 59),
'historic:building' => array('label' => 'Building', 'frequency' => 58),
'amenity:clinic' => array('label' => 'Clinic', 'frequency' => 57),
'highway:services' => array('label' => 'Services', 'frequency' => 56),
'amenity:ferry_terminal' => array('label' => 'Ferry Terminal', 'frequency' => 55),
'natural:marsh' => array('label' => 'Marsh', 'frequency' => 55),
'natural:hill' => array('label' => 'Hill', 'frequency' => 54),
'highway:raceway' => array('label' => 'Raceway', 'frequency' => 53),
'amenity:taxi' => array('label' => 'Taxi', 'frequency' => 47),
'amenity:take_away' => array('label' => 'Take Away', 'frequency' => 45),
'amenity:car_rental' => array('label' => 'Car Rental', 'frequency' => 44),
'place:islet' => array('label' => 'Islet', 'frequency' => 44),
'amenity:nursery' => array('label' => 'Nursery', 'frequency' => 44),
'amenity:nursing_home' => array('label' => 'Nursing Home', 'frequency' => 43),
'amenity:toilets' => array('label' => 'Toilets', 'frequency' => 38),
'amenity:hall' => array('label' => 'Hall', 'frequency' => 38),
'waterway:boatyard' => array('label' => 'Boatyard', 'frequency' => 36),
'highway:mini_roundabout' => array('label' => 'Mini Roundabout', 'frequency' => 35),
'historic:manor' => array('label' => 'Manor', 'frequency' => 35),
'tourism:chalet' => array('label' => 'Chalet', 'frequency' => 34),
'amenity:bicycle_parking' => array('label' => 'Bicycle Parking', 'frequency' => 34),
'amenity:hotel' => array('label' => 'Hotel', 'frequency' => 34),
'waterway:weir' => array('label' => 'Weir', 'frequency' => 33),
'natural:wetland' => array('label' => 'Wetland', 'frequency' => 33),
'natural:cave_entrance' => array('label' => 'Cave Entrance', 'frequency' => 32),
'amenity:crematorium' => array('label' => 'Crematorium', 'frequency' => 31),
'tourism:picnic_site' => array('label' => 'Picnic Site', 'frequency' => 31),
'landuse:wood' => array('label' => 'Wood', 'frequency' => 30),
'landuse:basin' => array('label' => 'Basin', 'frequency' => 30),
'natural:tree' => array('label' => 'Tree', 'frequency' => 30),
'leisure:slipway' => array('label' => 'Slipway', 'frequency' => 29),
'landuse:meadow' => array('label' => 'Meadow', 'frequency' => 29),
'landuse:piste' => array('label' => 'Piste', 'frequency' => 28),
'amenity:care_home' => array('label' => 'Care Home', 'frequency' => 28),
'amenity:club' => array('label' => 'Club', 'frequency' => 28),
'amenity:medical_centre' => array('label' => 'Medical Centre', 'frequency' => 27),
'historic:roman_road' => array('label' => 'Roman Road', 'frequency' => 27),
'historic:fort' => array('label' => 'Fort', 'frequency' => 26),
'railway:subway_entrance' => array('label' => 'Subway Entrance', 'frequency' => 26),
'historic:yes' => array('label' => 'Historic', 'frequency' => 25),
'highway:gate' => array('label' => 'Gate', 'frequency' => 25),
'leisure:fishing' => array('label' => 'Fishing', 'frequency' => 24),
'historic:museum' => array('label' => 'Museum', 'frequency' => 24),
'amenity:car_wash' => array('label' => 'Car Wash', 'frequency' => 24),
'railway:level_crossing' => array('label' => 'Level Crossing', 'frequency' => 23),
'leisure:bird_hide' => array('label' => 'Bird Hide', 'frequency' => 23),
'natural:headland' => array('label' => 'Headland', 'frequency' => 21),
'tourism:apartments' => array('label' => 'Apartments', 'frequency' => 21),
'amenity:shopping' => array('label' => 'Shopping', 'frequency' => 21),
'natural:scrub' => array('label' => 'Scrub', 'frequency' => 20),
'natural:fen' => array('label' => 'Fen', 'frequency' => 20),
'building:yes' => array('label' => 'Building', 'frequency' => 200),
'mountain_pass:yes' => array('label' => 'Mountain Pass', 'frequency' => 200),
'amenity:parking' => array('label' => 'Parking', 'frequency' => 3157),
'highway:bus_stop' => array('label' => 'Bus Stop', 'frequency' => 35777, 'icon' => 'transport_bus_stop2'),
'place:postcode' => array('label' => 'Postcode', 'frequency' => 27267),
'amenity:post_box' => array('label' => 'Post Box', 'frequency' => 9613),
'place:houses' => array('label' => 'Houses', 'frequency' => 85),
'railway:preserved' => array('label' => 'Preserved', 'frequency' => 227),
'waterway:derelict_canal' => array('label' => 'Derelict Canal', 'frequency' => 21),
'amenity:dead_pub' => array('label' => 'Dead Pub', 'frequency' => 20),
'railway:disused_station' => array('label' => 'Disused Station', 'frequency' => 114),
'railway:abandoned' => array('label' => 'Abandoned', 'frequency' => 641),
'railway:disused' => array('label' => 'Disused', 'frequency' => 72),
);
$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)
{
$sIcon = getIcon($aPlace);
if (!isset($sIcon)) {
return null;
}
return CONST_Website_BaseURL.'images/mapicons/'.$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(
'place:country',
'place:state',
'place:province',
'place:county',
'place:city',
'place:region',
'place:island',
'place:town',
'place:village',
'place:hamlet',
'place:suburb',
'place:locality',
'landuse:farm',
'place:farm',
'highway:motorway_junction',
'highway:motorway',
'highway:trunk',
'highway:primary',
'highway:secondary',
'highway:tertiary',
'highway:residential',
'highway:unclassified',
'highway:living_street',
'highway:service',
'highway:track',
'highway:road',
'highway:byway',
'highway:bridleway',
'highway:cycleway',
'highway:pedestrian',
'highway:footway',
'highway:steps',
'highway:motorway_link',
'highway:trunk_link',
'highway:primary_link',
'landuse:industrial',
'landuse:residential',
'landuse:retail',
'landuse:commercial',
'place:airport',
'aeroway:aerodrome',
'railway:station',
'amenity:place_of_worship',
'amenity:pub',
'amenity:bar',
'amenity:university',
'tourism:museum',
'amenity:arts_centre',
'tourism:zoo',
'tourism:theme_park',
'tourism:attraction',
'leisure:golf_course',
'historic:castle',
'amenity:hospital',
'amenity:school',
'amenity:theatre',
'amenity:public_building',
'amenity:library',
'amenity:townhall',
'amenity:community_centre',
'amenity:fire_station',
'amenity:police',
'amenity:bank',
'amenity:post_office',
'leisure:park',
'amenity:park',
'landuse:park',
'landuse:recreation_ground',
'tourism:hotel',
'tourism:motel',
'amenity:cinema',
'tourism:artwork',
'historic:archaeological_site',
'amenity:doctors',
'leisure:sports_centre',
'leisure:swimming_pool',
'shop:supermarket',
'shop:convenience',
'amenity:restaurant',
'amenity:fast_food',
'amenity:cafe',
'tourism:guest_house',
'amenity:pharmacy',
'amenity:fuel',
'natural:peak',
'waterway:waterfall',
'natural:wood',
'natural:water',
'landuse:forest',
'landuse:cemetery',
'landuse:allotments',
'landuse:farmyard',
'railway:rail',
'waterway:canal',
'waterway:river',
'waterway:stream',
'shop:bicycle',
'shop:clothes',
'shop:hairdresser',
'shop:doityourself',
'shop:estate_agent',
'shop:car',
'shop:garden_centre',
'shop:car_repair',
'shop:newsagent',
'shop:bakery',
'shop:furniture',
'shop:butcher',
'shop:apparel',
'shop:electronics',
'shop:department_store',
'shop:books',
'shop:yes',
'shop:outdoor',
'shop:mall',
'shop:florist',
'shop:charity',
'shop:hardware',
'shop:laundry',
'shop:shoes',
'shop:beverages',
'shop:dry_cleaning',
'shop:carpet',
'shop:computer',
'shop:alcohol',
'shop:optician',
'shop:chemist',
'shop:gallery',
'shop:mobile_phone',
'shop:sports',
'shop:jewelry',
'shop:pet',
'shop:beauty',
'shop:stationery',
'shop:shopping_centre',
'shop:general',
'shop:electrical',
'shop:toys',
'shop:jeweller',
'shop:betting',
'shop:household',
'shop:travel_agency',
'shop:hifi',
'amenity:shop',
'tourism:information',
'place:house',
'place:house_name',
'place:house_number',
'place:country_code',
'leisure:pitch',
'highway:unsurfaced',
'historic:ruins',
'amenity:college',
'historic:monument',
'railway:subway',
'historic:memorial',
'leisure:nature_reserve',
'leisure:common',
'waterway:lock_gate',
'natural:fell',
'amenity:nightclub',
'highway:path',
'leisure:garden',
'landuse:reservoir',
'leisure:playground',
'leisure:stadium',
'historic:mine',
'natural:cliff',
'tourism:caravan_site',
'amenity:bus_station',
'amenity:kindergarten',
'highway:construction',
'amenity:atm',
'amenity:emergency_phone',
'waterway:lock',
'waterway:riverbank',
'natural:coastline',
'tourism:viewpoint',
'tourism:hostel',
'tourism:bed_and_breakfast',
'railway:halt',
'railway:platform',
'railway:tram',
'amenity:courthouse',
'amenity:recycling',
'amenity:dentist',
'natural:beach',
'place:moor',
'amenity:grave_yard',
'waterway:drain',
'landuse:grass',
'landuse:village_green',
'natural:bay',
'railway:tram_stop',
'leisure:marina',
'highway:stile',
'natural:moor',
'railway:light_rail',
'railway:narrow_gauge',
'natural:land',
'amenity:village_hall',
'waterway:dock',
'amenity:veterinary',
'landuse:brownfield',
'leisure:track',
'railway:historic_station',
'landuse:construction',
'amenity:prison',
'landuse:quarry',
'amenity:telephone',
'highway:traffic_signals',
'natural:heath',
'historic:house',
'amenity:social_club',
'landuse:military',
'amenity:health_centre',
'historic:building',
'amenity:clinic',
'highway:services',
'amenity:ferry_terminal',
'natural:marsh',
'natural:hill',
'highway:raceway',
'amenity:taxi',
'amenity:take_away',
'amenity:car_rental',
'place:islet',
'amenity:nursery',
'amenity:nursing_home',
'amenity:toilets',
'amenity:hall',
'waterway:boatyard',
'highway:mini_roundabout',
'historic:manor',
'tourism:chalet',
'amenity:bicycle_parking',
'amenity:hotel',
'waterway:weir',
'natural:wetland',
'natural:cave_entrance',
'amenity:crematorium',
'tourism:picnic_site',
'landuse:wood',
'landuse:basin',
'natural:tree',
'leisure:slipway',
'landuse:meadow',
'landuse:piste',
'amenity:care_home',
'amenity:club',
'amenity:medical_centre',
'historic:roman_road',
'historic:fort',
'railway:subway_entrance',
'historic:yes',
'highway:gate',
'leisure:fishing',
'historic:museum',
'amenity:car_wash',
'railway:level_crossing',
'leisure:bird_hide',
'natural:headland',
'tourism:apartments',
'amenity:shopping',
'natural:scrub',
'natural:fen',
'building:yes',
'mountain_pass:yes',
'amenity:parking',
'highway:bus_stop',
'place:postcode',
'amenity:post_box',
'place:houses',
'railway:preserved',
'waterway:derelict_canal',
'amenity:dead_pub',
'railway:disused_station',
'railway:abandoned',
'railway:disused'
));
}
$sClassPlace = $aPlace['class'].':'.$aPlace['type'];
return $aWithImportance[$sClassPlace] ?? null;
}

View File

@@ -1,421 +0,0 @@
<?php
namespace Nominatim;
require_once(CONST_BasePath.'/lib/DatabaseError.php');
/**
* Uses PDO to access the database specified in the CONST_Database_DSN
* setting.
*/
class DB
{
protected $connection;
public function __construct($sDSN = CONST_Database_DSN)
{
$this->sDSN = $sDSN;
}
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 {
$conn = 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());
}
$conn->exec("SET DateStyle TO 'sql,european'");
$conn->exec("SET client_encoding TO 'utf-8'");
$iMaxExecution = ini_get('max_execution_time');
if ($iMaxExecution > 0) $conn->setAttribute(\PDO::ATTR_TIMEOUT, $iMaxExecution); // seconds
$this->connection = $conn;
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);
}
/**
* Returns a list of table names in the database
*
* @return array[]
*/
public function getListOfTables()
{
return $this->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'");
}
/**
* Deletes a table. Returns true if deleted or didn't exist.
*
* @param string $sTableName
*
* @return boolean
*/
public function deleteTable($sTableName)
{
return $this->exec('DROP TABLE IF EXISTS '.$sTableName.' CASCADE') == 0;
}
/**
* Check if an index exists in the database. Optional filtered by tablename
*
* @param string $sTableName
*
* @return boolean
*/
public function indexExists($sIndexName, $sTableName = null)
{
return in_array($sIndexName, $this->getListOfIndices($sTableName));
}
/**
* Returns a list of index names in the database, optional filtered by tablename
*
* @param string $sTableName
*
* @return array
*/
public function getListOfIndices($sTableName = null)
{
// table_name | index_name | column_name
// -----------------------+---------------------------------+--------------
// country_name | idx_country_name_country_code | country_code
// country_osm_grid | idx_country_osm_grid_geometry | geometry
// import_polygon_delete | idx_import_polygon_delete_osmid | osm_id
// import_polygon_delete | idx_import_polygon_delete_osmid | osm_type
// import_polygon_error | idx_import_polygon_error_osmid | osm_id
// import_polygon_error | idx_import_polygon_error_osmid | osm_type
$sSql = <<< END
SELECT
t.relname as table_name,
i.relname as index_name,
a.attname as column_name
FROM
pg_class t,
pg_class i,
pg_index ix,
pg_attribute a
WHERE
t.oid = ix.indrelid
and i.oid = ix.indexrelid
and a.attrelid = t.oid
and a.attnum = ANY(ix.indkey)
and t.relkind = 'r'
and i.relname NOT LIKE 'pg_%'
FILTERS
ORDER BY
t.relname,
i.relname,
a.attname
END;
$aRows = null;
if ($sTableName) {
$sSql = str_replace('FILTERS', 'and t.relname = :tablename', $sSql);
$aRows = $this->getAll($sSql, array(':tablename' => $sTableName));
} else {
$sSql = str_replace('FILTERS', '', $sSql);
$aRows = $this->getAll($sSql);
}
$aIndexNames = array_unique(array_map(function ($aRow) {
return $aRow['index_name'];
}, $aRows));
sort($aIndexNames);
return $aIndexNames;
}
/**
* Tries to connect to the database but on failure doesn't throw an exception.
*
* @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';
if ($sKey == 'dbname') $sKey = 'database';
if ($sKey == 'user') $sKey = 'username';
$aInfo[$sKey] = $sVal;
}
}
return $aInfo;
}
/**
* Takes an array of settings and return the DNS string. Key names can be
* 'database', 'hostspec', 'port', 'username', 'password' but aliases
* 'dbname', 'host' and 'user' are also supported.
*
* @return string
*
*/
public static function generateDSN($aInfo)
{
$sDSN = sprintf(
'pgsql:host=%s;port=%s;dbname=%s;user=%s;password=%s;',
$aInfo['host'] ?? $aInfo['hostspec'] ?? '',
$aInfo['port'] ?? '',
$aInfo['dbname'] ?? $aInfo['database'] ?? '',
$aInfo['user'] ?? '',
$aInfo['password'] ?? ''
);
$sDSN = preg_replace('/\b\w+=;/', '', $sDSN);
$sDSN = preg_replace('/;\Z/', '', $sDSN);
return $sDSN;
}
}

View File

@@ -1,34 +0,0 @@
<?php
namespace Nominatim;
class DatabaseError extends \Exception
{
public function __construct($message, $code = 500, Exception $previous = null, $oPDOErr, $sSql = null)
{
parent::__construct($message, $code, $previous);
// https://secure.php.net/manual/en/class.pdoexception.php
$this->oPDOErr = $oPDOErr;
$this->sSql = $sSql;
}
public function __toString()
{
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
}
public function getSqlError()
{
return $this->oPDOErr->getMessage();
}
public function getSqlDebugDump()
{
if (CONST_Debug) {
return var_export($this->oPDOErr, true);
} else {
return $this->sSql;
}
}
}

View File

@@ -245,6 +245,7 @@ class Geocode
}
$this->oPlaceLookup->loadParamArray($oParams, $sForceGeometryType);
$this->oPlaceLookup->setIncludePolygonAsPoints($oParams->getBool('polygon'));
$this->oPlaceLookup->setIncludeAddressDetails($oParams->getBool('addressdetails', false));
}
@@ -347,7 +348,10 @@ class Geocode
$aNewPhraseSearches = array();
$sPhraseType = $bIsStructured ? $oPhrase->getPhraseType() : '';
foreach ($oPhrase->getWordSets() as $aWordset) {
foreach ($oPhrase->getWordSets() as $iWordSet => $aWordset) {
// Too many permutations - too expensive
if ($iWordSet > 120) break;
$aWordsetSearches = $aSearches;
// Add all words from this wordset
@@ -523,8 +527,8 @@ class Geocode
$sNormQuery = $this->normTerm($this->sQuery);
Debug::printVar('Normalized query', $sNormQuery);
$sLanguagePrefArraySQL = $this->oDB->getArraySQL(
$this->oDB->getDBQuotedList($this->aLangPrefOrder)
$sLanguagePrefArraySQL = getArraySQL(
array_map('getDBQuoted', $this->aLangPrefOrder)
);
$sQuery = $this->sQuery;
@@ -542,6 +546,7 @@ class Geocode
// Do we have anything that looks like a lat/lon pair?
$sQuery = $oCtx->setNearPointFromQuery($sQuery);
$aResults = array();
if ($sQuery || $this->aStructuredQuery) {
// Start with a single blank search
$aSearches = array(new SearchDescription($oCtx));
@@ -577,9 +582,8 @@ class Geocode
if ($sSpecialTerm && !$aSearches[0]->hasOperator()) {
$sSpecialTerm = pg_escape_string($sSpecialTerm);
$sToken = $this->oDB->getOne(
'SELECT make_standard_name(:term)',
array(':term' => $sSpecialTerm),
$sToken = chksql(
$this->oDB->getOne("SELECT make_standard_name('$sSpecialTerm')"),
'Cannot decode query. Wrong encoding?'
);
$sSQL = 'SELECT class, type FROM word ';
@@ -587,7 +591,7 @@ class Geocode
$sSQL .= ' AND class is not null AND class not in (\'place\')';
Debug::printSQL($sSQL);
$aSearchWords = $this->oDB->getAll($sSQL);
$aSearchWords = chksql($this->oDB->getAll($sSQL));
$aNewSearches = array();
foreach ($aSearches as $oSearch) {
foreach ($aSearchWords as $aSearchTerm) {
@@ -625,9 +629,8 @@ class Geocode
$aTokens = array();
$aPhrases = array();
foreach ($aInPhrases as $iPhrase => $sPhrase) {
$sPhrase = $this->oDB->getOne(
'SELECT make_standard_name(:phrase)',
array(':phrase' => $sPhrase),
$sPhrase = chksql(
$this->oDB->getOne('SELECT make_standard_name('.getDBQuoted($sPhrase).')'),
'Cannot normalize query string (is it a UTF-8 string?)'
);
if (trim($sPhrase)) {
@@ -637,6 +640,7 @@ class Geocode
}
}
Debug::printDebugTable('Phrases', $aPhrases);
Debug::printVar('Tokens', $aTokens);
$oValidTokens = new TokenList();
@@ -644,7 +648,7 @@ class Geocode
if (!empty($aTokens)) {
$sSQL = 'SELECT word_id, word_token, word, class, type, country_code, operator, search_name_count';
$sSQL .= ' FROM word ';
$sSQL .= ' WHERE word_token in ('.join(',', $this->oDB->getDBQuotedList($aTokens)).')';
$sSQL .= ' WHERE word_token in ('.join(',', array_map('getDBQuoted', $aTokens)).')';
Debug::printSQL($sSQL);
@@ -681,11 +685,6 @@ class Geocode
Debug::printGroupTable('Valid Tokens', $oValidTokens->debugInfo());
foreach ($aPhrases as $oPhrase) {
$oPhrase->computeWordSets($oValidTokens);
}
Debug::printDebugTable('Phrases', $aPhrases);
Debug::newSection('Search candidates');
$aGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens, $bStructuredPhrases);
@@ -747,10 +746,8 @@ class Geocode
// Start the search process
$iGroupLoop = 0;
$iQueryLoop = 0;
$aNextResults = array();
foreach ($aGroupedSearches as $iGroupedRank => $aSearches) {
$iGroupLoop++;
$aResults = $aNextResults;
foreach ($aSearches as $oSearch) {
$iQueryLoop++;
@@ -760,42 +757,16 @@ class Geocode
$oValidTokens->debugTokenByWordIdList()
);
$aNewResults = $oSearch->query(
$aResults += $oSearch->query(
$this->oDB,
$this->iMinAddressRank,
$this->iMaxAddressRank,
$this->iLimit
);
// The same result may appear in different rounds, only
// use the one with minimal rank.
foreach ($aNewResults as $iPlace => $oRes) {
if (!isset($aResults[$iPlace])
|| $aResults[$iPlace]->iResultRank > $oRes->iResultRank) {
$aResults[$iPlace] = $oRes;
}
}
if ($iQueryLoop > 20) break;
}
if (!empty($aResults)) {
$aSplitResults = Result::splitResults($aResults);
Debug::printVar('Split results', $aSplitResults);
if ($iGroupLoop <= 4 && empty($aSplitResults['tail'])
&& reset($aSplitResults['head'])->iResultRank > 0) {
// 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--;
}
$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
@@ -807,7 +778,9 @@ class Geocode
$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 (14 >= $this->iMinAddressRank && 14 <= $this->iMaxAddressRank) {
$sSQL .= " OR (extratags->'place') = 'city'";
}
if ($this->aAddressRankList) {
$sSQL .= ' OR placex.rank_address in ('.join(',', $this->aAddressRankList).')';
}
@@ -830,7 +803,7 @@ class Geocode
if ($aFilterSql) {
$sSQL = join(' UNION ', $aFilterSql);
Debug::printSQL($sSQL);
$aFilteredIDs = $this->oDB->getCol($sSQL);
$aFilteredIDs = chksql($this->oDB->getCol($sSQL));
}
$tempIDs = array();
@@ -887,6 +860,7 @@ class Geocode
$aSearchResults = $this->oPlaceLookup->lookup($aResults);
$aClassType = ClassTypes\getListWithImportance();
$aRecheckWords = preg_split('/\b[\s,\\-]*/u', $sQuery);
foreach ($aRecheckWords as $i => $sWord) {
if (!preg_match('/[\pL\pN]/', $sWord)) unset($aRecheckWords[$i]);
@@ -895,23 +869,33 @@ class Geocode
Debug::printVar('Recheck words', $aRecheckWords);
foreach ($aSearchResults as $iIdx => $aResult) {
$fRadius = ClassTypes\getDefRadius($aResult);
// Default
$fDiameter = ClassTypes\getProperty($aResult, 'defdiameter', 0.0001);
$aOutlineResult = $this->oPlaceLookup->getOutlines($aResult['place_id'], $aResult['lon'], $aResult['lat'], $fRadius);
$aOutlineResult = $this->oPlaceLookup->getOutlines($aResult['place_id'], $aResult['lon'], $aResult['lat'], $fDiameter/2);
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;
if ($aResult['extra_place'] == 'city') {
$aResult['class'] = 'place';
$aResult['type'] = 'city';
$aResult['rank_search'] = 16;
}
$sLabel = ClassTypes\getLabel($aResult);
if (isset($sLabel)) {
$aResult['label'] = $sLabel;
// Is there an icon set for this type of result?
$aClassInfo = ClassTypes\getInfo($aResult);
if ($aClassInfo) {
if (isset($aClassInfo['icon'])) {
$aResult['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aClassInfo['icon'].'.p.20.png';
}
if (isset($aClassInfo['label'])) {
$aResult['label'] = $aClassInfo['label'];
}
}
$aResult['name'] = $aResult['langaddress'];
if ($oCtx->hasNearPoint()) {
@@ -941,9 +925,10 @@ class Geocode
// - 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;
if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
&& $aClassType[$aResult['class'].':'.$aResult['type']]['importance']
) {
$aResult['foundorder'] += 0.0001 * $aClassType[$aResult['class'].':'.$aResult['type']]['importance'];
} else {
$aResult['foundorder'] += 0.01;
}

View File

@@ -91,7 +91,7 @@ class ParameterParser
$sLangString = $this->getString('accept-language', $sFallback);
if ($sLangString) {
if (preg_match_all('/(([a-z]{1,8})([-_][a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $sLangString, $aLanguagesParse, PREG_SET_ORDER)) {
if (preg_match_all('/(([a-z]{1,8})(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $sLangString, $aLanguagesParse, PREG_SET_ORDER)) {
foreach ($aLanguagesParse as $iLang => $aLanguage) {
$aLanguages[$aLanguage[1]] = isset($aLanguage[5])?(float)$aLanguage[5]:1 - ($iLang/100);
if (!isset($aLanguages[$aLanguage[2]])) $aLanguages[$aLanguage[2]] = $aLanguages[$aLanguage[1]]/10;
@@ -104,29 +104,18 @@ class ParameterParser
}
foreach ($aLanguages as $sLanguage => $fLanguagePref) {
$aLangPrefOrder['short_name:'.$sLanguage] = 'short_name:'.$sLanguage;
$aLangPrefOrder['name:'.$sLanguage] = 'name:'.$sLanguage;
}
$aLangPrefOrder['short_name'] = 'short_name';
$aLangPrefOrder['name'] = 'name';
$aLangPrefOrder['brand'] = 'brand';
foreach ($aLanguages as $sLanguage => $fLanguagePref) {
$aLangPrefOrder['official_name:'.$sLanguage] = 'official_name:'.$sLanguage;
$aLangPrefOrder['short_name:'.$sLanguage] = 'short_name:'.$sLanguage;
}
$aLangPrefOrder['official_name'] = 'official_name';
$aLangPrefOrder['short_name'] = 'short_name';
$aLangPrefOrder['ref'] = 'ref';
$aLangPrefOrder['type'] = 'type';
return $aLangPrefOrder;
}
public function hasSetAny($aParamNames)
{
foreach ($aParamNames as $sName) {
if ($this->getBool($sName)) {
return true;
}
}
return false;
}
}

View File

@@ -9,8 +9,7 @@ namespace Nominatim;
*/
class Phrase
{
const MAX_WORDSET_LEN = 20;
const MAX_WORDSETS = 100;
const MAX_DEPTH = 7;
// Complete phrase as a string.
private $sPhrase;
@@ -21,24 +20,13 @@ class Phrase
// Possible segmentations of the phrase.
private $aWordSets;
public static function cmpByArraylen($aA, $aB)
{
$iALen = count($aA);
$iBLen = count($aB);
if ($iALen == $iBLen) {
return 0;
}
return ($iALen < $iBLen) ? -1 : 1;
}
public function __construct($sPhrase, $sPhraseType)
{
$this->sPhrase = trim($sPhrase);
$this->sPhraseType = $sPhraseType;
$this->aWords = explode(' ', $this->sPhrase);
$this->aWordSets = $this->createWordSets($this->aWords, 0);
}
/**
@@ -72,17 +60,10 @@ class Phrase
*/
public function addTokens(&$aTokens)
{
$iNumWords = count($this->aWords);
for ($i = 0; $i < $iNumWords; $i++) {
$sPhrase = $this->aWords[$i];
$aTokens[' '.$sPhrase] = ' '.$sPhrase;
$aTokens[$sPhrase] = $sPhrase;
for ($j = $i + 1; $j < $iNumWords; $j++) {
$sPhrase .= ' '.$this->aWords[$j];
$aTokens[' '.$sPhrase] = ' '.$sPhrase;
$aTokens[$sPhrase] = $sPhrase;
foreach ($this->aWordSets as $aSet) {
foreach ($aSet as $sWord) {
$aTokens[' '.$sWord] = ' '.$sWord;
$aTokens[$sWord] = $sWord;
}
}
}
@@ -94,60 +75,45 @@ class Phrase
*/
public function invertWordSets()
{
foreach ($this->aWordSets as $i => $aSet) {
$this->aWordSets[$i] = array_reverse($aSet);
}
$this->aWordSets = $this->createInverseWordSets($this->aWords, 0);
}
public function computeWordSets($oTokens)
private function createWordSets($aWords, $iDepth)
{
$iNumWords = count($this->aWords);
// 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) < Phrase::MAX_WORDSET_LEN) {
$aSetCache[$i][] = array_merge($aSet, $aPartial);
}
}
if (count($aSetCache[$i]) > 2 * Phrase::MAX_WORDSETS) {
usort(
$aSetCache[$i],
array('\Nominatim\Phrase', 'cmpByArraylen')
);
$aSetCache[$i] = array_slice(
$aSetCache[$i],
0,
Phrase::MAX_WORDSETS
);
}
$aResult = array(array(join(' ', $aWords)));
$sFirstToken = '';
if ($iDepth < Phrase::MAX_DEPTH) {
while (count($aWords) > 1) {
$sWord = array_shift($aWords);
$sFirstToken .= ($sFirstToken?' ':'').$sWord;
$aRest = $this->createWordSets($aWords, $iDepth + 1);
foreach ($aRest as $aSet) {
$aResult[] = array_merge(array($sFirstToken), $aSet);
}
}
}
// finally the current full phrase
$sPartial = $this->aWords[0].' '.$sPartial;
if ($oTokens->containsAny($sPartial)) {
$aSetCache[$i][] = array($sPartial);
return $aResult;
}
private function createInverseWordSets($aWords, $iDepth)
{
$aResult = array(array(join(' ', $aWords)));
$sFirstToken = '';
if ($iDepth < Phrase::MAX_DEPTH) {
while (count($aWords) > 1) {
$sWord = array_pop($aWords);
$sFirstToken = $sWord.($sFirstToken?' ':'').$sFirstToken;
$aRest = $this->createInverseWordSets($aWords, $iDepth + 1);
foreach ($aRest as $aSet) {
$aResult[] = array_merge(array($sFirstToken), $aSet);
}
}
}
$this->aWordSets = $aSetCache[$iNumWords - 1];
usort($this->aWordSets, array('\Nominatim\Phrase', 'cmpByArraylen'));
$this->aWordSets = array_slice($this->aWordSets, 0, Phrase::MAX_WORDSETS);
return $aResult;
}
public function debugInfo()
{
return array(

View File

@@ -15,6 +15,7 @@ class PlaceLookup
protected $bExtraTags = false;
protected $bNameDetails = false;
protected $bIncludePolygonAsPoints = false;
protected $bIncludePolygonAsText = false;
protected $bIncludePolygonAsGeoJSON = false;
protected $bIncludePolygonAsKML = false;
@@ -37,6 +38,11 @@ class PlaceLookup
return $this->bDeDupe;
}
public function setIncludePolygonAsPoints($b = true)
{
$this->bIncludePolygonAsPoints = $b;
}
public function setIncludeAddressDetails($b)
{
$this->bAddressDetails = $b;
@@ -46,7 +52,7 @@ class PlaceLookup
{
$aLangs = $oParams->getPreferredLanguages();
$this->aLangPrefOrderSql =
'ARRAY['.join(',', $this->oDB->getDBQuotedList($aLangs)).']';
'ARRAY['.join(',', array_map('getDBQuoted', $aLangs)).']';
$this->bExtraTags = $oParams->getBool('extratags', false);
$this->bNameDetails = $oParams->getBool('namedetails', false);
@@ -55,6 +61,7 @@ class PlaceLookup
if ($sGeomType === null || $sGeomType == 'geojson') {
$this->bIncludePolygonAsGeoJSON = $oParams->getBool('polygon_geojson');
$this->bIncludePolygonAsPoints = false;
}
if ($oParams->getString('format', '') !== 'geojson') {
@@ -93,6 +100,7 @@ class PlaceLookup
if ($this->bExtraTags) $aParams['extratags'] = '1';
if ($this->bNameDetails) $aParams['namedetails'] = '1';
if ($this->bIncludePolygonAsPoints) $aParams['polygon'] = '1';
if ($this->bIncludePolygonAsText) $aParams['polygon_text'] = '1';
if ($this->bIncludePolygonAsGeoJSON) $aParams['polygon_geojson'] = '1';
if ($this->bIncludePolygonAsKML) $aParams['polygon_kml'] = '1';
@@ -124,9 +132,8 @@ class PlaceLookup
public function setLanguagePreference($aLangPrefOrder)
{
$this->aLangPrefOrderSql = $this->oDB->getArraySQL(
$this->oDB->getDBQuotedList($aLangPrefOrder)
);
$this->aLangPrefOrderSql =
'ARRAY['.join(',', array_map('getDBQuoted', $aLangPrefOrder)).']';
}
private function addressImportanceSql($sGeometry, $sPlaceId)
@@ -155,8 +162,8 @@ class PlaceLookup
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));
$sSQL = "select place_id from placex where osm_type = '".$sType."' and osm_id = ".$iID;
$iPlaceID = chksql($this->oDB->getOne($sSQL));
if (!$iPlaceID) {
return null;
@@ -207,7 +214,7 @@ class PlaceLookup
'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 .= " (extratags->'place') AS extra_place ";
$sSQL .= ' FROM placex';
$sSQL .= " WHERE place_id in ($sPlaceIDs) ";
$sSQL .= ' AND (';
@@ -240,7 +247,7 @@ class PlaceLookup
$sSQL .= ' ref, ';
if ($this->bExtraTags) $sSQL .= 'extratags, ';
if ($this->bNameDetails) $sSQL .= 'name, ';
$sSQL .= ' extra_place ';
$sSQL .= " extratags->'place' ";
$aSubSelects[] = $sSQL;
}
@@ -417,10 +424,9 @@ class PlaceLookup
$sSQL = join(' UNION ', $aSubSelects);
Debug::printSQL($sSQL);
$aPlaces = $this->oDB->getAll($sSQL, null, 'Could not lookup place');
$aPlaces = chksql($this->oDB->getAll($sSQL), '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(
@@ -448,9 +454,10 @@ class PlaceLookup
}
}
$aPlace['addresstype'] = ClassTypes\getLabelTag(
$aPlace['addresstype'] = ClassTypes\getProperty(
$aPlace,
$aPlace['country_code']
'simplelabel',
$aPlace['class']
);
}
@@ -491,7 +498,7 @@ class PlaceLookup
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';
if ($this->bIncludePolygonAsText || $this->bIncludePolygonAsPoints) $sSQL .= ',ST_AsText(geometry) as astext';
if ($fLonReverse != null && $fLatReverse != null) {
$sFrom = ' from (SELECT * , CASE WHEN (class = \'highway\') AND (ST_GeometryType(geometry) = \'ST_LineString\') THEN ';
$sFrom .=' ST_ClosestPoint(geometry, ST_SetSRID(ST_Point('.$fLatReverse.','.$fLonReverse.'),4326))';
@@ -506,9 +513,9 @@ class PlaceLookup
$sSQL .= $sFrom;
}
$aPointPolygon = $this->oDB->getRow($sSQL, null, 'Could not get outline');
$aPointPolygon = chksql($this->oDB->getRow($sSQL), 'Could not get outline');
if ($aPointPolygon && $aPointPolygon['place_id']) {
if ($aPointPolygon['place_id']) {
if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null) {
$aOutlineResult['lat'] = $aPointPolygon['centrelat'];
$aOutlineResult['lon'] = $aPointPolygon['centrelon'];
@@ -518,6 +525,8 @@ class PlaceLookup
if ($this->bIncludePolygonAsKML) $aOutlineResult['askml'] = $aPointPolygon['askml'];
if ($this->bIncludePolygonAsSVG) $aOutlineResult['assvg'] = $aPointPolygon['assvg'];
if ($this->bIncludePolygonAsText) $aOutlineResult['astext'] = $aPointPolygon['astext'];
if ($this->bIncludePolygonAsPoints) $aOutlineResult['aPolyPoints'] = geometryText2Points($aPointPolygon['astext'], $fRadius);
if (abs($aPointPolygon['minlat'] - $aPointPolygon['maxlat']) < 0.0000001) {
$aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
@@ -540,12 +549,17 @@ class PlaceLookup
// 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
);
//
if ($this->bIncludePolygonAsPoints) {
$sGeometryText = 'POINT('.$fLon.','.$fLat.')';
$aOutlineResult['aPolyPoints'] = geometryText2Points($sGeometryText, $fRadius);
}
$aBounds = array();
$aBounds['minlat'] = $fLat - $fRadius;
$aBounds['maxlat'] = $fLat + $fRadius;
$aBounds['minlon'] = $fLon - $fRadius;
$aBounds['maxlon'] = $fLon + $fRadius;
$aOutlineResult['aBoundingBox'] = array(
(string)$aBounds['minlat'],

View File

@@ -68,32 +68,4 @@ class Result
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 = array_merge($aTail, $aHead);
$aHead = array($oRes->iId => $oRes);
$iMinRank = $oRes->iResultRank;
} elseif ($oRes->iResultRank == $iMinRank) {
$aHead[$oRes->iId] = $oRes;
} else {
$aTail[$oRes->iId] = $oRes;
}
}
return array('head' => $aHead, 'tail' => $aTail);
}
}

View File

@@ -36,8 +36,8 @@ class ReverseGeocode
13 => 18,
14 => 22, // Suburb
15 => 22,
16 => 26, // major street
17 => 27, // minor street
16 => 26, // Street, TODO: major street?
17 => 26,
18 => 30, // or >, Building
19 => 30, // or >, Building
);
@@ -63,9 +63,8 @@ class ReverseGeocode
$sSQL .= ' and indexed_status = 0 and startnumber is not NULL ';
$sSQL .= ' ORDER BY distance ASC limit 1';
return $this->oDB->getRow(
$sSQL,
null,
return chksql(
$this->oDB->getRow($sSQL),
'Could not determine closest housenumber on an osm interpolation line.'
);
}
@@ -93,9 +92,8 @@ class ReverseGeocode
$sSQL = 'SELECT country_code FROM country_osm_grid';
$sSQL .= ' WHERE ST_CONTAINS(geometry, '.$sPointSQL.') LIMIT 1';
$sCountryCode = $this->oDB->getOne(
$sSQL,
null,
$sCountryCode = chksql(
$this->oDB->getOne($sSQL),
'Could not determine country polygon containing the point.'
);
if ($sCountryCode) {
@@ -117,7 +115,10 @@ class ReverseGeocode
$sSQL .= ' LIMIT 1';
if (CONST_Debug) var_dump($sSQL);
$aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine place node.');
$aPlace = chksql(
$this->oDB->getRow($sSQL),
'Could not determine place node.'
);
if ($aPlace) {
return new Result($aPlace['place_id']);
}
@@ -133,7 +134,10 @@ class ReverseGeocode
$sSQL .= ' ORDER BY distance ASC';
if (CONST_Debug) var_dump($sSQL);
$aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine place node.');
$aPlace = chksql(
$this->oDB->getRow($sSQL),
'Could not determine place node.'
);
if ($aPlace) {
return new Result($aPlace['place_id']);
}
@@ -174,8 +178,10 @@ class ReverseGeocode
$sSQL .= ' WHERE ST_CONTAINS(geometry, '.$sPointSQL.' )';
$sSQL .= ' ORDER BY rank_address DESC LIMIT 1';
$aPoly = $this->oDB->getRow($sSQL, null, 'Could not determine polygon containing the point.');
$aPoly = chksql(
$this->oDB->getRow($sSQL),
'Could not determine polygon containing the point.'
);
if ($aPoly) {
// if a polygon is found, search for placenodes begins ...
$iParentPlaceID = $aPoly['parent_place_id'];
@@ -207,7 +213,10 @@ class ReverseGeocode
$sSQL .= ' LIMIT 1';
if (CONST_Debug) var_dump($sSQL);
$aPlacNode = $this->oDB->getRow($sSQL, null, 'Could not determine place node.');
$aPlacNode = chksql(
$this->oDB->getRow($sSQL),
'Could not determine place node.'
);
if ($aPlacNode) {
return $aPlacNode;
}
@@ -246,18 +255,24 @@ class ReverseGeocode
$sSQL .= ' placex';
$sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, '.$fSearchDiam.')';
$sSQL .= ' AND';
$sSQL .= ' rank_address between 26 and '.$iMaxRank;
// only streets
if ($iMaxRank == 26) {
$sSQL .= ' rank_address = 26';
} else {
$sSQL .= ' rank_address between 26 and '.$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 class not in (\'railway\',\'tunnel\',\'bridge\',\'man_made\')';
$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';
if (CONST_Debug) var_dump($sSQL);
$aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine closest place.');
$aPlace = chksql(
$this->oDB->getRow($sSQL),
'Could not determine closest place.'
);
if (CONST_Debug) var_dump($aPlace);
if ($aPlace) {
@@ -299,14 +314,16 @@ class ReverseGeocode
// 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 rank_address != 28';
$sSQL .= ' and (name is not null or housenumber is not null)';
$sSQL .= ' and class not in (\'boundary\')';
$sSQL .= ' and class not in (\'railway\',\'tunnel\',\'bridge\',\'man_made\')';
$sSQL .= ' and indexed_status = 0 and linked_place_id is null';
$sSQL .= ' ORDER BY distance ASC limit 1';
if (CONST_Debug) var_dump($sSQL);
$aStreet = $this->oDB->getRow($sSQL, null, 'Could not determine closest place.');
$aStreet = chksql(
$this->oDB->getRow($sSQL),
'Could not determine closest place.'
);
if ($aStreet) {
if (CONST_Debug) var_dump($aStreet);
$oResult = new Result($aStreet['place_id']);
@@ -327,7 +344,10 @@ class ReverseGeocode
$sSQL .= ' AND ST_DWithin('.$sPointSQL.', linegeo, 0.001)';
$sSQL .= ' ORDER BY distance ASC limit 1';
if (CONST_Debug) var_dump($sSQL);
$aPlaceTiger = $this->oDB->getRow($sSQL, null, 'Could not determine closest Tiger place.');
$aPlaceTiger = chksql(
$this->oDB->getRow($sSQL),
'Could not determine closest Tiger place.'
);
if ($aPlaceTiger) {
if (CONST_Debug) var_dump('found Tiger housenumber', $aPlaceTiger);
$oResult = new Result($aPlaceTiger['place_id'], Result::TABLE_TIGER);

View File

@@ -126,7 +126,7 @@ class SearchContext
* 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 object $oDB DB connection 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.
@@ -146,11 +146,11 @@ class SearchContext
$this->sqlViewboxCentre .= ")'::geometry,4326)";
$sSQL = 'ST_BUFFER('.$this->sqlViewboxCentre.','.($fRouteWidth/69).')';
$sGeom = $oDB->getOne('select '.$sSQL, null, 'Could not get small viewbox');
$sGeom = chksql($oDB->getOne('select '.$sSQL), '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');
$sGeom = chksql($oDB->getOne('select '.$sSQL), 'Could not get large viewbox');
$this->sqlViewboxLarge = "'".$sGeom."'::geometry";
}
@@ -203,7 +203,7 @@ class SearchContext
}
/**
* Get an SQL snippet for computing the distance from the reference point.
* Get an SQL snipped for computing the distance from the reference point.
*
* @param string $sObj SQL variable name to compute the distance from.
*
@@ -215,7 +215,7 @@ class SearchContext
}
/**
* Get an SQL snippet for checking if something is within range of the
* Get an SQL snipped for checking if something is within range of the
* reference point.
*
* @param string $sObj SQL variable name to compute if it is within range.
@@ -228,14 +228,14 @@ class SearchContext
}
/**
* Get an SQL snippet of the importance factor of the viewbox.
* Get an SQL snipped 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.
* @return string SQL snipped of the factor with a leading multiply sign.
*/
public function viewboxImportanceSQL($sObj)
{
@@ -252,7 +252,7 @@ class SearchContext
}
/**
* SQL snippet checking if a place ID should be excluded.
* SQL snipped checking if a place ID should be excluded.
*
* @param string $sVariable SQL variable name of place ID to check,
* potentially prefixed with more SQL.

View File

@@ -237,8 +237,7 @@ class SearchDescription
$oSearch->sHouseNumber = $oSearchTerm->sToken;
// sanity check: if the housenumber is not mainly made
// up of numbers, add a penalty
if (preg_match('/\\d/', $oSearch->sHouseNumber) === 0
|| preg_match_all('/[^0-9]/', $oSearch->sHouseNumber, $aMatches) > 2) {
if (preg_match_all('/[^0-9]/', $oSearch->sHouseNumber, $aMatches) > 2) {
$oSearch->iSearchRank++;
}
if (empty($oSearchTerm->iId)) {
@@ -288,7 +287,7 @@ class SearchDescription
if (!empty($this->aName) || !($bFirstPhrase || $sPhraseType == '')) {
if (($sPhraseType == '' || !$bFirstPhrase) && !$bHasPartial) {
$oSearch = clone $this;
$oSearch->iSearchRank += 2;
$oSearch->iSearchRank++;
$oSearch->aAddress[$iWordID] = $iWordID;
$aNewSearches[] = $oSearch;
} else {
@@ -404,7 +403,7 @@ class SearchDescription
/**
* Query database for places that match this search.
*
* @param object $oDB Nominatim::DB instance to use.
* @param object $oDB Database connection 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.
@@ -447,20 +446,13 @@ class SearchDescription
$iLimit
);
// Now search for housenumber, if housenumber provided. Can be zero.
if (($this->sHouseNumber || $this->sHouseNumber === '0') && !empty($aResults)) {
// Downgrade the rank of the street results, they are missing
// the housenumber.
foreach ($aResults as $oRes) {
$oRes->iResultRank++;
}
//now search for housenumber, if housenumber provided
if ($this->sHouseNumber && !empty($aResults)) {
$aNamedPlaceIDs = $aResults;
$aResults = $this->queryHouseNumber($oDB, $aNamedPlaceIDs);
$aHnResults = $this->queryHouseNumber($oDB, $aResults);
if (!empty($aHnResults)) {
foreach ($aHnResults as $oRes) {
$aResults[$oRes->iId] = $oRes;
}
if (empty($aResults) && $this->looksLikeFullAddress()) {
$aResults = $aNamedPlaceIDs;
}
}
@@ -477,13 +469,16 @@ class SearchDescription
if ($sPlaceIds) {
$sSQL = 'SELECT place_id FROM placex';
$sSQL .= ' WHERE place_id in ('.$sPlaceIds.')';
$sSQL .= " AND postcode != '".$this->sPostcode."'";
$sSQL .= " AND postcode = '".$this->sPostcode."'";
Debug::printSQL($sSQL);
$aFilteredPlaceIDs = $oDB->getCol($sSQL);
$aFilteredPlaceIDs = chksql($oDB->getCol($sSQL));
if ($aFilteredPlaceIDs) {
$aNewResults = array();
foreach ($aFilteredPlaceIDs as $iPlaceId) {
$aResults[$iPlaceId]->iResultRank++;
$aNewResults[$iPlaceId] = $aResults[$iPlaceId];
}
$aResults = $aNewResults;
Debug::printVar('Place IDs after postcode filtering', $aResults);
}
}
}
@@ -504,10 +499,8 @@ class SearchDescription
Debug::printSQL($sSQL);
$iPlaceId = $oDB->getOne($sSQL);
$aResults = array();
if ($iPlaceId) {
foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$aResults[$iPlaceId] = new Result($iPlaceId);
}
@@ -523,7 +516,8 @@ class SearchDescription
$aDBResults = array();
$sPoiTable = $this->poiTable();
if ($oDB->tableExists($sPoiTable)) {
$sSQL = 'SELECT count(*) FROM pg_tables WHERE tablename = \''.$sPoiTable."'";
if (chksql($oDB->getOne($sSQL))) {
$sSQL = 'SELECT place_id FROM '.$sPoiTable.' ct';
if ($this->oContext->sqlCountryList) {
$sSQL .= ' JOIN placex USING (place_id)';
@@ -543,14 +537,14 @@ class SearchDescription
} elseif ($this->oContext->hasNearPoint()) {
$sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('ct.centroid').' ASC';
}
$sSQL .= " LIMIT $iLimit";
$sSQL .= " limit $iLimit";
Debug::printSQL($sSQL);
$aDBResults = $oDB->getCol($sSQL);
$aDBResults = chksql($oDB->getCol($sSQL));
}
if ($this->oContext->hasNearPoint()) {
$sSQL = 'SELECT place_id FROM placex WHERE ';
$sSQL .= 'class = :class and type = :type';
$sSQL .= 'class=\''.$this->sClass."' and type='".$this->sType."'";
$sSQL .= ' AND '.$this->oContext->withinSQL('geometry');
$sSQL .= ' AND linked_place_id is null';
if ($this->oContext->sqlCountryList) {
@@ -559,10 +553,7 @@ class SearchDescription
$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)
);
$aDBResults = chksql($oDB->getCol($sSQL));
}
$aResults = array();
@@ -581,23 +572,20 @@ class SearchDescription
$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 ';
$sSQL .= ' @> '.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) {
foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$aResults[$iPlaceId] = new Result($iPlaceId, Result::TABLE_POSTCODE);
}
@@ -640,14 +628,14 @@ class SearchDescription
}
if (!empty($this->aName)) {
$aTerms[] = 'name_vector @> '.$oDB->getArraySQL($this->aName);
$aTerms[] = 'name_vector @> '.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);
$aTerms[] = 'array_cat(nameaddress_vector,ARRAY[]::integer[]) @> '.getArraySQL($this->aAddress);
} else {
$aTerms[] = 'nameaddress_vector @> '.$oDB->getArraySQL($this->aAddress);
$aTerms[] = 'nameaddress_vector @> '.getArraySQL($this->aAddress);
}
}
@@ -660,7 +648,10 @@ class SearchDescription
$aTerms[] = 'address_rank between 16 and 27';
} elseif (!$this->sClass || $this->iOperator == Operator::NAME) {
if ($iMinAddressRank > 0) {
$aTerms[] = "((address_rank between $iMinAddressRank and $iMaxAddressRank) or (search_rank between $iMinAddressRank and $iMaxAddressRank))";
$aTerms[] = 'address_rank >= '.$iMinAddressRank;
}
if ($iMaxAddressRank < 30) {
$aTerms[] = 'address_rank <= '.$iMaxAddressRank;
}
}
@@ -699,7 +690,7 @@ class SearchDescription
if (!empty($this->aFullNameAddress)) {
$sExactMatchSQL = ' ( ';
$sExactMatchSQL .= ' SELECT count(*) FROM ( ';
$sExactMatchSQL .= ' SELECT unnest('.$oDB->getArraySQL($this->aFullNameAddress).')';
$sExactMatchSQL .= ' SELECT unnest('.getArraySQL($this->aFullNameAddress).')';
$sExactMatchSQL .= ' INTERSECT ';
$sExactMatchSQL .= ' SELECT unnest(nameaddress_vector)';
$sExactMatchSQL .= ' ) s';
@@ -710,7 +701,7 @@ class SearchDescription
}
if ($this->sHouseNumber || $this->sClass) {
$iLimit = 40;
$iLimit = 20;
}
$aResults = array();
@@ -724,7 +715,10 @@ class SearchDescription
Debug::printSQL($sSQL);
$aDBResults = $oDB->getAll($sSQL, null, 'Could not get places for search terms.');
$aDBResults = chksql(
$oDB->getAll($sSQL),
'Could not get places for search terms.'
);
foreach ($aDBResults as $aResult) {
$oResult = new Result($aResult['place_id']);
@@ -754,7 +748,7 @@ class SearchDescription
Debug::printSQL($sSQL);
// XXX should inherit the exactMatches from its parent
foreach ($oDB->getCol($sSQL) as $iPlaceId) {
foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$aResults[$iPlaceId] = new Result($iPlaceId);
}
@@ -780,7 +774,7 @@ class SearchDescription
Debug::printSQL($sSQL);
foreach ($oDB->getCol($sSQL) as $iPlaceId) {
foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$oResult = new Result($iPlaceId, Result::TABLE_OSMLINE);
$oResult->iHouseNumber = $iHousenumber;
$aResults[$iPlaceId] = $oResult;
@@ -796,7 +790,7 @@ class SearchDescription
Debug::printSQL($sSQL);
foreach ($oDB->getCol($sSQL) as $iPlaceId) {
foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$aResults[$iPlaceId] = new Result($iPlaceId, Result::TABLE_AUX);
}
}
@@ -817,7 +811,7 @@ class SearchDescription
Debug::printSQL($sSQL);
foreach ($oDB->getCol($sSQL) as $iPlaceId) {
foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$oResult = new Result($iPlaceId, Result::TABLE_TIGER);
$oResult->iHouseNumber = $iHousenumber;
$aResults[$iPlaceId] = $oResult;
@@ -851,7 +845,7 @@ class SearchDescription
Debug::printSQL($sSQL);
foreach ($oDB->getCol($sSQL) as $iPlaceId) {
foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$aResults[$iPlaceId] = new Result($iPlaceId);
}
}
@@ -859,11 +853,12 @@ class SearchDescription
// 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 count(*) FROM pg_tables WHERE tablename = '$sClassTable'";
$bCacheTable = (bool) chksql($oDB->getOne($sSQL));
$sSQL = "SELECT min(rank_search) FROM placex WHERE place_id in ($sPlaceIDs)";
Debug::printSQL($sSQL);
$iMaxRank = (int) $oDB->getOne($sSQL);
$iMaxRank = (int)chksql($oDB->getOne($sSQL));
// For state / country level searches the normal radius search doesn't work very well
$sPlaceGeom = false;
@@ -876,7 +871,7 @@ class SearchDescription
$sSQL .= ' ORDER BY rank_search ASC ';
$sSQL .= ' LIMIT 1';
Debug::printSQL($sSQL);
$sPlaceGeom = $oDB->getOne($sSQL);
$sPlaceGeom = chksql($oDB->getOne($sSQL));
}
if ($sPlaceGeom) {
@@ -886,7 +881,7 @@ class SearchDescription
$sSQL = 'SELECT place_id FROM placex';
$sSQL .= " WHERE place_id in ($sPlaceIDs) and rank_search < $iMaxRank";
Debug::printSQL($sSQL);
$aPlaceIDs = $oDB->getCol($sSQL);
$aPlaceIDs = chksql($oDB->getCol($sSQL));
$sPlaceIDs = join(',', $aPlaceIDs);
}
@@ -932,7 +927,7 @@ class SearchDescription
Debug::printSQL($sSQL);
foreach ($oDB->getCol($sSQL) as $iPlaceId) {
foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$aResults[$iPlaceId] = new Result($iPlaceId);
}
} else {
@@ -964,7 +959,7 @@ class SearchDescription
Debug::printSQL($sSQL);
foreach ($oDB->getCol($sSQL) as $iPlaceId) {
foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$aResults[$iPlaceId] = new Result($iPlaceId);
}
}

View File

@@ -1,80 +0,0 @@
<?php
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()
{
$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);
return $iStat;
}
private function escapeParam($sParam)
{
if (preg_match('/^-*\w+$/', $sParam)) return $sParam;
return escapeshellarg($sParam);
}
}

View File

@@ -3,6 +3,7 @@
namespace Nominatim;
use Exception;
use PEAR;
class Status
{
@@ -15,18 +16,12 @@ class Status
public function status()
{
if (!$this->oDB) {
if (!$this->oDB || PEAR::isError($this->oDB)) {
throw new Exception('No database', 700);
}
try {
$this->oDB->connect();
} catch (\Nominatim\DatabaseError $e) {
throw new Exception('Database connection failed', 700);
}
$sStandardWord = $this->oDB->getOne("SELECT make_standard_name('a')");
if ($sStandardWord === false) {
if (PEAR::isError($sStandardWord)) {
throw new Exception('Module failed', 701);
}
@@ -37,7 +32,7 @@ class Status
$sSQL = 'SELECT word_id, word_token, word, class, type, country_code, ';
$sSQL .= "operator, search_name_count FROM word WHERE word_token IN (' a')";
$iWordID = $this->oDB->getOne($sSQL);
if ($iWordID === false) {
if (PEAR::isError($iWordID)) {
throw new Exception('Query failed', 703);
}
if (!$iWordID) {
@@ -50,7 +45,7 @@ class Status
$sSQL = 'SELECT EXTRACT(EPOCH FROM lastimportdate) FROM import_status LIMIT 1';
$iDataDateEpoch = $this->oDB->getOne($sSQL);
if ($iDataDateEpoch === false) {
if (PEAR::isError($iDataDateEpoch)) {
throw Exception('Data date query failed '.$iDataDateEpoch->getMessage(), 705);
}

View File

@@ -55,18 +55,6 @@ class TokenList
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]) || isset($this->aTokens[' '.$sWord]);
}
/**
* Get the list of tokens for the given token word.
*
@@ -83,7 +71,7 @@ class TokenList
/**
* Add token information from the word table in the database.
*
* @param object $oDB Nominatim::DB instance.
* @param object $oDB Database connection.
* @param string[] $aTokens List of tokens to look up in the database.
* @param string[] $aCountryCodes List of country restrictions.
* @param string $sNormQuery Normalized query string.
@@ -97,11 +85,11 @@ class TokenList
$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(',', $oDB->getDBQuotedList($aTokens)).')';
$sSQL .= join(',', array_map('getDBQuoted', $aTokens)).')';
Debug::printSQL($sSQL);
$aDBWords = $oDB->getAll($sSQL, null, 'Could not get word tokens.');
$aDBWords = chksql($oDB->getAll($sSQL), 'Could not get word tokens.');
foreach ($aDBWords as $aWord) {
$oToken = null;

View File

@@ -1,6 +1,5 @@
<?php
require_once(CONST_BasePath.'/lib/Shell.php');
function getCmdOpt($aArg, $aSpec, &$aResult, $bExitOnError = false, $bExitOnUnknown = false)
{
@@ -121,6 +120,15 @@ function showUsage($aSpec, $bExit = false, $sError = false)
exit;
}
function chksql($oSql, $sMsg = false)
{
if (PEAR::isError($oSql)) {
fail($sMsg || $oSql->getMessage(), $oSql->userinfo);
}
return $oSql;
}
function info($sMsg)
{
echo date('Y-m-d H:i:s == ').$sMsg."\n";
@@ -147,35 +155,32 @@ function repeatWarnings()
function runSQLScript($sScript, $bfatal = true, $bVerbose = false, $bIgnoreErrors = false)
{
// Convert database DSN to psql parameters
$aDSNInfo = \Nominatim\DB::parseDSN(CONST_Database_DSN);
$aDSNInfo = DB::parseDSN(CONST_Database_DSN);
if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
$oCmd = new \Nominatim\Shell('psql');
$oCmd->addParams('--port', $aDSNInfo['port']);
$oCmd->addParams('--dbname', $aDSNInfo['database']);
$sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
$oCmd->addParams('--host', $aDSNInfo['hostspec']);
$sCMD .= ' -h ' . $aDSNInfo['hostspec'];
}
if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
$oCmd->addParams('--username', $aDSNInfo['username']);
$sCMD .= ' -U ' . $aDSNInfo['username'];
}
if (isset($aDSNInfo['password'])) {
$oCmd->addEnvPair('PGPASSWORD', $aDSNInfo['password']);
$aProcEnv = null;
if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
$aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
}
if (!$bVerbose) {
$oCmd->addParams('--quiet');
$sCMD .= ' -q';
}
if ($bfatal && !$bIgnoreErrors) {
$oCmd->addParams('-v', 'ON_ERROR_STOP=1');
$sCMD .= ' -v ON_ERROR_STOP=1';
}
$aDescriptors = array(
0 => array('pipe', 'r'),
1 => STDOUT,
2 => STDERR
);
$ahPipes = null;
$hProcess = @proc_open($oCmd->escapedCmd(), $aDescriptors, $ahPipes, null, $oCmd->aEnv);
$hProcess = @proc_open($sCMD, $aDescriptors, $ahPipes, null, $aProcEnv);
if (!is_resource($hProcess)) {
fail('unable to start pgsql');
}
@@ -195,3 +200,23 @@ function runSQLScript($sScript, $bfatal = true, $bVerbose = false, $bIgnoreError
fail("pgsql returned with error code ($iReturn)");
}
}
function runWithEnv($sCmd, $aEnv)
{
$aFDs = array(
0 => array('pipe', 'r'),
1 => STDOUT,
2 => STDERR
);
$aPipes = null;
$hProc = @proc_open($sCmd, $aFDs, $aPipes, null, $aEnv);
if (!is_resource($hProc)) {
fail('unable to run command:' . $sCmd);
}
fclose($aPipes[0]); // no stdin
$iStat = proc_close($hProc);
return $iStat;
}

43
lib/db.php Normal file
View File

@@ -0,0 +1,43 @@
<?php
require_once('DB.php');
function &getDB($bNew = false, $bPersistent = false)
{
// Get the database object
$oDB = chksql(
DB::connect(CONST_Database_DSN.($bNew?'?new_link=true':''), $bPersistent),
'Failed to establish database connection'
);
$oDB->setFetchMode(DB_FETCHMODE_ASSOC);
$oDB->query("SET DateStyle TO 'sql,european'");
$oDB->query("SET client_encoding TO 'utf-8'");
$iMaxExecution = ini_get('max_execution_time') * 1000;
if ($iMaxExecution > 0) $oDB->query("SET statement_timeout TO $iMaxExecution");
return $oDB;
}
function getDBQuoted($s)
{
return "'".pg_escape_string($s)."'";
}
function getArraySQL($a)
{
return 'ARRAY['.join(',', $a).']';
}
function getPostgresVersion(&$oDB)
{
$sVersionString = $oDB->getOne('select version()');
preg_match('#PostgreSQL ([0-9]+)[.]([0-9]+)[^0-9]#', $sVersionString, $aMatches);
return (float) ($aMatches[1].'.'.$aMatches[2]);
}
function getPostgisVersion(&$oDB)
{
$sVersionString = $oDB->getOne('select postgis_full_version()');
preg_match('#POSTGIS="([0-9]+)[.]([0-9]+)[.]([0-9]+)( r([0-9]+))?"#', $sVersionString, $aMatches);
return (float) ($aMatches[1].'.'.$aMatches[2]);
}

View File

@@ -10,83 +10,79 @@ require_once(CONST_Debug ? 'DebugHtml.php' : 'DebugNone.php');
*
*/
function userError($sMsg)
function chksql($oSql, $sMsg = 'Database request failed')
{
throw new Exception($sMsg, 400);
}
if (!PEAR::isError($oSql)) return $oSql;
header('HTTP/1.0 500 Internal Server Error');
header('Content-type: text/html; charset=utf-8');
function exception_handler_html($exception)
{
http_response_code($exception->getCode());
header('Content-type: text/html; charset=UTF-8');
include(CONST_BasePath.'/lib/template/error-html.php');
exit();
}
$sSqlError = $oSql->getMessage();
function exception_handler_json($exception)
{
http_response_code($exception->getCode());
header('Content-type: application/json; charset=utf-8');
include(CONST_BasePath.'/lib/template/error-json.php');
exit();
}
echo <<<INTERNALFAIL
<html>
<head><title>Internal Server Error</title></head>
<body>
<h1>Internal Server Error</h1>
<p>Nominatim has encountered an internal error while accessing the database.
This may happen because the database is broken or because of a bug in
the software. If you think it is a bug, feel free to report
it over on <a href="https://github.com/openstreetmap/Nominatim/issues">
Github</a>. Please include the URL that caused the problem and the
complete error details below.</p>
<p><b>Message:</b> $sMsg</p>
<p><b>SQL Error:</b> $sSqlError</p>
<p><b>Details:</b> <pre>
INTERNALFAIL;
function exception_handler_xml($exception)
{
http_response_code($exception->getCode());
header('Content-type: text/xml; charset=utf-8');
echo '<?xml version="1.0" encoding="UTF-8" ?>'."\n";
include(CONST_BasePath.'/lib/template/error-xml.php');
exit();
}
function shutdown_exception_handler_html()
{
$error = error_get_last();
if ($error !== null && $error['type'] === E_ERROR) {
exception_handler_html(new Exception($error['message'], 500));
}
}
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_html');
} elseif ($sFormat == 'html') {
set_exception_handler('exception_handler_html');
register_shutdown_function('shutdown_exception_handler_html');
} elseif ($sFormat == 'xml') {
set_exception_handler('exception_handler_xml');
register_shutdown_function('shutdown_exception_handler_xml');
if (CONST_Debug) {
var_dump($oSql);
} else {
set_exception_handler('exception_handler_json');
register_shutdown_function('shutdown_exception_handler_json');
echo "<pre>\n".$oSql->getUserInfo().'</pre>';
}
echo '</pre></p></body></html>';
exit;
}
function failInternalError($sError, $sSQL = false, $vDumpVar = false)
{
header('HTTP/1.0 500 Internal Server Error');
header('Content-type: text/html; charset=utf-8');
echo '<html><body><h1>Internal Server Error</h1>';
echo '<p>Nominatim has encountered an internal error while processing your request. This is most likely because of a bug in the software.</p>';
echo '<p><b>Details:</b> '.$sError,'</p>';
echo '<p>Feel free to file an issue on <a href="https://github.com/openstreetmap/Nominatim/issues">Github</a>. ';
echo 'Please include the error message above and the URL you used.</p>';
if (CONST_Debug) {
echo '<hr><h2>Debugging Information</h2><br>';
if ($sSQL) {
echo '<h3>SQL query</h3><code>'.$sSQL.'</code>';
}
if ($vDumpVar) {
echo '<h3>Result</h3> <code>';
var_dump($vDumpVar);
echo '</code>';
}
}
echo "\n</body></html>\n";
exit;
}
function userError($sError)
{
header('HTTP/1.0 400 Bad Request');
header('Content-type: text/html; charset=utf-8');
echo '<html><body><h1>Bad Request</h1>';
echo '<p>Nominatim has encountered an error with your request.</p>';
echo '<p><b>Details:</b> '.$sError.'</p>';
echo '<p>If you feel this error is incorrect feel file an issue on <a href="https://github.com/openstreetmap/Nominatim/issues">Github</a>. ';
echo 'Please include the error message above and the URL you used.</p>';
echo "\n</body></html>\n";
exit;
}
// set a default
set_exception_handler_by_format();
/***************************************************************************
@@ -100,6 +96,6 @@ if (CONST_NoAccessControl) {
header('Access-Control-Allow-Headers: '.$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
}
}
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS') exit;
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') exit;
if (CONST_Debug) header('Content-type: text/html; charset=utf-8');

View File

@@ -1,7 +1,7 @@
<?php
require_once(CONST_BasePath.'/lib/lib.php');
require_once(CONST_BasePath.'/lib/DB.php');
require_once(CONST_BasePath.'/lib/db.php');
if (get_magic_quotes_gpc()) {
echo "Please disable magic quotes in your php.ini configuration\n";

View File

@@ -4,7 +4,7 @@ function fail($sError, $sUserError = false)
{
if (!$sUserError) $sUserError = $sError;
error_log('ERROR: '.$sError);
var_dump($sUserError)."\n";
echo $sUserError."\n";
exit(-1);
}
@@ -61,26 +61,23 @@ function byImportance($a, $b)
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;
$iOptions |= JSON_UNESCAPED_UNICODE;
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 {
if (!isset($_GET['json_callback'])) {
header('Content-Type: application/json; charset=UTF-8');
echo $jsonout;
} else {
if (preg_match('/^[$_\p{L}][$_\p{L}\p{Nd}.[\]]*$/u', $_GET['json_callback'])) {
header('Content-Type: application/javascript; charset=UTF-8');
echo $_GET['json_callback'].'('.$jsonout.')';
} else {
header('HTTP/1.0 400 Bad Request');
}
}
}
@@ -95,8 +92,8 @@ function parseLatLon($sQuery)
$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
if (preg_match('/\\s*([NS])[ ]+([0-9]+[0-9.]*)[° ]+([0-9.]+)?[\']*[, ]+([EW])[ ]+([0-9]+)[° ]+([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
@@ -104,8 +101,8 @@ function parseLatLon($sQuery)
$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
} elseif (preg_match('/\\s*([0-9]+)[° ]+([0-9]+[0-9.]*)?[\']*[ ]+([NS])[, ]+([0-9]+)[° ]+([0-9]+[0-9.]*)?[\' ]+([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
@@ -113,8 +110,8 @@ function parseLatLon($sQuery)
$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
} elseif (preg_match('/\\s*([NS])[ ]([0-9]+)[° ]+([0-9]+)[\' ]+([0-9]+)[″"]*[, ]+([EW])[ ]([0-9]+)[° ]+([0-9]+)[\' ]+([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″
@@ -122,8 +119,8 @@ function parseLatLon($sQuery)
$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
} elseif (preg_match('/\\s*([0-9]+)[° ]+([0-9]+)[\' ]+([0-9]+[0-9.]*)[″" ]+([NS])[, ]+([0-9]+)[° ]+([0-9]+)[\' ]+([0-9]+[0-9.]*)[″" ]+([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
@@ -132,24 +129,24 @@ function parseLatLon($sQuery)
$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
} elseif (preg_match('/\\s*([NS])[ ]([0-9]+[0-9]*\\.[0-9]+)[°]*[, ]+([EW])[ ]([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
} elseif (preg_match('/\\s*([0-9]+[0-9]*\\.[0-9]+)[° ]+([NS])[, ]+([0-9]+[0-9]*\\.[0-9]+)[° ]+([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
} elseif (preg_match('/(\\s*\\[|^\\s*|\\s*)(-?[0-9]+[0-9]*\\.[0-9]+)[, ]+(-?[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
@@ -165,6 +162,39 @@ function parseLatLon($sQuery)
return array($sFound, $fQueryLat, $fQueryLon);
}
function geometryText2Points($geometry_as_text, $fRadius)
{
$aPolyPoints = null;
if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch)) {
//
preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/', $aMatch[1], $aPolyPoints, PREG_SET_ORDER);
//
} elseif (preg_match('#LINESTRING\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch)) {
//
preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/', $aMatch[1], $aPolyPoints, PREG_SET_ORDER);
//
} elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch)) {
//
preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/', $aMatch[1], $aPolyPoints, PREG_SET_ORDER);
//
} elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#', $geometry_as_text, $aMatch)) {
//
$aPolyPoints = createPointsAroundCenter($aMatch[1], $aMatch[2], $fRadius);
//
}
if (isset($aPolyPoints)) {
$aResultPoints = array();
foreach ($aPolyPoints as $aPoint) {
$aResultPoints[] = array($aPoint[1], $aPoint[2]);
}
return $aResultPoints;
}
return;
}
function createPointsAroundCenter($fLon, $fLat, $fRadius)
{
$iSteps = max(8, min(100, ($fRadius * 40000)^2));
@@ -195,25 +225,3 @@ function closestHouseNumber($aRow)
return max(min($aRow['endnumber'], $iHn), $aRow['startnumber']);
}
function getSearchRankLabel($iRank)
{
if (!isset($iRank)) return 'unknown';
if ($iRank < 2) return 'continent';
if ($iRank < 4) return 'sea';
if ($iRank < 8) return 'country';
if ($iRank < 12) return 'state';
if ($iRank < 16) return 'county';
if ($iRank == 16) return 'city';
if ($iRank == 17) return 'town / island';
if ($iRank == 18) return 'village / hamlet';
if ($iRank == 20) return 'suburb';
if ($iRank == 21) return 'postcode area';
if ($iRank == 22) return 'croft / farm / locality / islet';
if ($iRank == 23) return 'postcode area';
if ($iRank == 25) return 'postcode point';
if ($iRank == 26) return 'street / major landmark';
if ($iRank == 27) return 'minory street / path';
if ($iRank == 28) return 'house / building';
return 'other: ' . $iRank;
}

View File

@@ -36,19 +36,9 @@ function logStart(&$oDB, $sType = '', $sQuery = '', $aLanguageList = array())
$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);
$sSQL .= ' values ('.getDBQuoted($sType).','.getDBQuoted($hLog[0]).','.getDBQuoted($hLog[2]);
$sSQL .= ','.getDBQuoted($hLog[1]).','.getDBQuoted($sUserAgent).','.getDBQuoted(join(',', $aLanguageList)).','.getDBQuoted($sOutputFormat).','.getDBQuoted($hLog[3]).')';
$oDB->query($sSQL);
}
return $hLog;
@@ -63,11 +53,11 @@ function logEnd(&$oDB, $hLog, $iNumResults)
if (!$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);
$sSQL = 'update new_query_log set endtime = '.getDBQuoted($sEndTime).', results = '.$iNumResults;
$sSQL .= ' where starttime = '.getDBQuoted($hLog[0]);
$sSQL .= ' and ipaddress = '.getDBQuoted($hLog[1]);
$sSQL .= ' and query = '.getDBQuoted($hLog[2]);
$oDB->query($sSQL);
}
if (CONST_Log_File) {

View File

@@ -12,8 +12,6 @@ function formatOSMType($sType, $bIncludeExternal = true)
if ($sType == 'T') return 'way';
if ($sType == 'I') return 'way';
// not handled: P, L
return '';
}
@@ -35,39 +33,20 @@ function wikipediaLink($aFeature)
return '';
}
function detailsLink($aFeature, $sTitle = false, $sExtraProperties = false)
function detailsLink($aFeature, $sTitle = false)
{
if (!$aFeature['place_id']) return '';
$sHtml = '<a ';
if ($sExtraProperties) {
$sHtml .= $sExtraProperties.' ';
}
$sHtml .= 'href="details.php?place_id='.$aFeature['place_id'].'">'.($sTitle?$sTitle:$aFeature['place_id']).'</a>';
return $sHtml;
return '<a href="details.php?place_id='.$aFeature['place_id'].'">'.($sTitle?$sTitle:$aFeature['place_id']).'</a>';
}
function detailsPermaLink($aFeature, $sRefText = false, $sExtraProperties = false)
function detailsPermaLink($aFeature, $sRefText = false)
{
$sOSMType = formatOSMType($aFeature['osm_type'], false);
if ($sOSMType) {
$sHtml = '<a ';
if ($sExtraProperties) {
$sHtml .= $sExtraProperties.' ';
}
$sHtml .= 'href="details.php?osmtype='.$aFeature['osm_type']
.'&osmid='.$aFeature['osm_id'].'&class='.$aFeature['class'].'">';
if ($sRefText) {
$sHtml .= $sRefText.'</a>';
} else {
$sHtml .= $sOSMType.' '.$aFeature['osm_id'].'</a>';
}
return $sHtml;
$sLabel = $sRefText ? $sRefText : $sOSMType.' '.$aFeature['osm_id'];
return '<a href="details.php?osmtype='.$aFeature['osm_type'].'&osmid='.$aFeature['osm_id'].'&class='.$aFeature['class'].'">'.$sLabel.'</a>';
}
return detailsLink($aFeature, $sRefText, $sExtraProperties);
return '';
}

View File

@@ -1,98 +0,0 @@
<?php
namespace Nominatim\Setup;
/**
* Parses an address level description.
*/
class AddressLevelParser
{
private $aLevels;
public function __construct($sDescriptionFile)
{
$sJson = file_get_contents($sDescriptionFile);
$this->aLevels = json_decode($sJson, true);
if (!$this->aLevels) {
switch (json_last_error()) {
case JSON_ERROR_NONE:
break;
case JSON_ERROR_DEPTH:
fail('JSON error - Maximum stack depth exceeded');
break;
case JSON_ERROR_STATE_MISMATCH:
fail('JSON error - Underflow or the modes mismatch');
break;
case JSON_ERROR_CTRL_CHAR:
fail('JSON error - Unexpected control character found');
break;
case JSON_ERROR_SYNTAX:
fail('JSON error - Syntax error, malformed JSON');
break;
case JSON_ERROR_UTF8:
fail('JSON error - Malformed UTF-8 characters, possibly incorrectly encoded');
break;
default:
fail('JSON error - Unknown error');
break;
}
}
}
/**
* Dump the description into a database table.
*
* @param object $oDB Database conneciton to use.
* @param string $sTable Name of table to create.
*
* @return null
*
* A new table is created. Any previously existing table is dropped.
* The table has the following columns:
* country, class, type, rank_search, rank_address.
*/
public function createTable($oDB, $sTable)
{
$oDB->exec('DROP TABLE IF EXISTS '.$sTable);
$sSql = 'CREATE TABLE '.$sTable;
$sSql .= '(country_code varchar(2), class TEXT, type TEXT,';
$sSql .= ' rank_search SMALLINT, rank_address SMALLINT)';
$oDB->exec($sSql);
$sSql = 'CREATE UNIQUE INDEX ON '.$sTable.' (country_code, class, type)';
$oDB->exec($sSql);
$sSql = 'INSERT INTO '.$sTable.' VALUES ';
foreach ($this->aLevels as $aLevel) {
$aCountries = array();
if (isset($aLevel['countries'])) {
foreach ($aLevel['countries'] as $sCountry) {
$aCountries[$sCountry] = $oDB->getDBQuoted($sCountry);
}
} else {
$aCountries['NULL'] = 'NULL';
}
foreach ($aLevel['tags'] as $sKey => $aValues) {
foreach ($aValues as $sValue => $mRanks) {
$aFields = array(
$oDB->getDBQuoted($sKey),
$sValue ? $oDB->getDBQuoted($sValue) : 'NULL'
);
if (is_array($mRanks)) {
$aFields[] = (string) $mRanks[0];
$aFields[] = (string) $mRanks[1];
} else {
$aFields[] = (string) $mRanks;
$aFields[] = (string) $mRanks;
}
$sLine = ','.join(',', $aFields).'),';
foreach ($aCountries as $sCountries) {
$sSql .= '('.$sCountries.$sLine;
}
}
}
}
$oDB->exec(rtrim($sSql, ','));
}
}

View File

@@ -1,885 +0,0 @@
<?php
namespace Nominatim\Setup;
require_once(CONST_BasePath.'/lib/setup/AddressLevelParser.php');
require_once(CONST_BasePath.'/lib/Shell.php');
class SetupFunctions
{
protected $iCacheMemory;
protected $iInstances;
protected $sModulePath;
protected $aDSNInfo;
protected $bQuiet;
protected $bVerbose;
protected $sIgnoreErrors;
protected $bEnableDiffUpdates;
protected $bEnableDebugStatements;
protected $bNoPartitions;
protected $bDrop;
protected $oDB = null;
public function __construct(array $aCMDResult)
{
// by default, use all but one processor, but never more than 15.
$this->iInstances = isset($aCMDResult['threads'])
? $aCMDResult['threads']
: (min(16, getProcessorCount()) - 1);
if ($this->iInstances < 1) {
$this->iInstances = 1;
warn('resetting threads to '.$this->iInstances);
}
if (isset($aCMDResult['osm2pgsql-cache'])) {
$this->iCacheMemory = $aCMDResult['osm2pgsql-cache'];
} elseif (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
// When flatnode files are enabled then disable cache per default.
$this->iCacheMemory = 0;
} else {
// Otherwise: Assume we can steal all the cache memory in the box.
$this->iCacheMemory = getCacheMemoryMB();
}
$this->sModulePath = CONST_Database_Module_Path;
info('module path: ' . $this->sModulePath);
// parse database string
$this->aDSNInfo = \Nominatim\DB::parseDSN(CONST_Database_DSN);
if (!isset($this->aDSNInfo['port'])) {
$this->aDSNInfo['port'] = 5432;
}
// setting member variables based on command line options stored in $aCMDResult
$this->bQuiet = isset($aCMDResult['quiet']) && $aCMDResult['quiet'];
$this->bVerbose = $aCMDResult['verbose'];
//setting default values which are not set by the update.php array
if (isset($aCMDResult['ignore-errors'])) {
$this->sIgnoreErrors = $aCMDResult['ignore-errors'];
} else {
$this->sIgnoreErrors = false;
}
if (isset($aCMDResult['enable-debug-statements'])) {
$this->bEnableDebugStatements = $aCMDResult['enable-debug-statements'];
} else {
$this->bEnableDebugStatements = false;
}
if (isset($aCMDResult['no-partitions'])) {
$this->bNoPartitions = $aCMDResult['no-partitions'];
} else {
$this->bNoPartitions = false;
}
if (isset($aCMDResult['enable-diff-updates'])) {
$this->bEnableDiffUpdates = $aCMDResult['enable-diff-updates'];
} else {
$this->bEnableDiffUpdates = false;
}
$this->bDrop = isset($aCMDResult['drop']) && $aCMDResult['drop'];
}
public function createDB()
{
info('Create DB');
$oDB = new \Nominatim\DB;
if ($oDB->checkConnection()) {
fail('database already exists ('.CONST_Database_DSN.')');
}
$oCmd = (new \Nominatim\Shell('createdb'))
->addParams('-E', 'UTF-8')
->addParams('-p', $this->aDSNInfo['port']);
if (isset($this->aDSNInfo['username'])) {
$oCmd->addParams('-U', $this->aDSNInfo['username']);
}
if (isset($this->aDSNInfo['password'])) {
$oCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
}
if (isset($this->aDSNInfo['hostspec'])) {
$oCmd->addParams('-h', $this->aDSNInfo['hostspec']);
}
$oCmd->addParams($this->aDSNInfo['database']);
$result = $oCmd->run();
if ($result != 0) fail('Error executing external command: '.$oCmd->escapedCmd());
}
public function connect()
{
$this->oDB = new \Nominatim\DB();
$this->oDB->connect();
}
public function setupDB()
{
info('Setup DB');
$fPostgresVersion = $this->oDB->getPostgresVersion();
echo 'Postgres version found: '.$fPostgresVersion."\n";
if ($fPostgresVersion < 9.03) {
fail('Minimum supported version of Postgresql is 9.3.');
}
$this->pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
$this->pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
$fPostgisVersion = $this->oDB->getPostgisVersion();
echo 'Postgis version found: '.$fPostgisVersion."\n";
if ($fPostgisVersion < 2.2) {
echo "Minimum required Postgis version 2.2\n";
exit(1);
}
$i = $this->oDB->getOne("select count(*) from pg_user where usename = '".CONST_Database_Web_User."'");
if ($i == 0) {
echo "\nERROR: Web user '".CONST_Database_Web_User."' does not exist. Create it with:\n";
echo "\n createuser ".CONST_Database_Web_User."\n\n";
exit(1);
}
// Try accessing the C module, so we know early if something is wrong
checkModulePresence(); // raises exception on failure
if (!file_exists(CONST_ExtraDataPath.'/country_osm_grid.sql.gz')) {
echo 'Error: you need to download the country_osm_grid first:';
echo "\n wget -O ".CONST_ExtraDataPath."/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz\n";
exit(1);
}
$this->pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
$this->pgsqlRunScriptFile(CONST_ExtraDataPath.'/country_osm_grid.sql.gz');
$this->pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
$this->pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode_table.sql');
$sPostcodeFilename = CONST_BasePath.'/data/gb_postcode_data.sql.gz';
if (file_exists($sPostcodeFilename)) {
$this->pgsqlRunScriptFile($sPostcodeFilename);
} else {
warn('optional external GB postcode table file ('.$sPostcodeFilename.') not found. Skipping.');
}
$sPostcodeFilename = CONST_BasePath.'/data/us_postcode_data.sql.gz';
if (file_exists($sPostcodeFilename)) {
$this->pgsqlRunScriptFile($sPostcodeFilename);
} else {
warn('optional external US postcode table file ('.$sPostcodeFilename.') not found. Skipping.');
}
if ($this->bNoPartitions) {
$this->pgsqlRunScript('update country_name set partition = 0');
}
}
public function importData($sOSMFile)
{
info('Import data');
if (!file_exists(CONST_Osm2pgsql_Binary)) {
echo "Check CONST_Osm2pgsql_Binary in your local settings file.\n";
echo "Normally you should not need to set this manually.\n";
fail("osm2pgsql not found in '".CONST_Osm2pgsql_Binary."'");
}
$oCmd = new \Nominatim\Shell(CONST_Osm2pgsql_Binary);
$oCmd->addParams('--style', CONST_Import_Style);
if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
$oCmd->addParams('--flat-nodes', CONST_Osm2pgsql_Flatnode_File);
}
if (CONST_Tablespace_Osm2pgsql_Data) {
$oCmd->addParams('--tablespace-slim-data', CONST_Tablespace_Osm2pgsql_Data);
}
if (CONST_Tablespace_Osm2pgsql_Index) {
$oCmd->addParams('--tablespace-slim-index', CONST_Tablespace_Osm2pgsql_Index);
}
if (CONST_Tablespace_Place_Data) {
$oCmd->addParams('--tablespace-main-data', CONST_Tablespace_Place_Data);
}
if (CONST_Tablespace_Place_Index) {
$oCmd->addParams('--tablespace-main-index', CONST_Tablespace_Place_Index);
}
$oCmd->addParams('--latlong', '--slim', '--create');
$oCmd->addParams('--output', 'gazetteer');
$oCmd->addParams('--hstore');
$oCmd->addParams('--number-processes', 1);
$oCmd->addParams('--cache', $this->iCacheMemory);
$oCmd->addParams('--port', $this->aDSNInfo['port']);
if (isset($this->aDSNInfo['username'])) {
$oCmd->addParams('--username', $this->aDSNInfo['username']);
}
if (isset($this->aDSNInfo['password'])) {
$oCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
}
if (isset($this->aDSNInfo['hostspec'])) {
$oCmd->addParams('--host', $this->aDSNInfo['hostspec']);
}
$oCmd->addParams('--database', $this->aDSNInfo['database']);
$oCmd->addParams($sOSMFile);
$oCmd->run();
if (!$this->sIgnoreErrors && !$this->oDB->getRow('select * from place limit 1')) {
fail('No Data');
}
if ($this->bDrop) {
$this->dropTable('planet_osm_nodes');
$this->removeFlatnodeFile();
}
}
public function createFunctions()
{
info('Create Functions');
// Try accessing the C module, so we know early if something is wrong
checkModulePresence(); // raises exception on failure
$this->createSqlFunctions();
}
public function createTables($bReverseOnly = false)
{
info('Create Tables');
$sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
$sTemplate = $this->replaceSqlPatterns($sTemplate);
$this->pgsqlRunScript($sTemplate, false);
if ($bReverseOnly) {
$this->dropTable('search_name');
}
$oAlParser = new AddressLevelParser(CONST_Address_Level_Config);
$oAlParser->createTable($this->oDB, 'address_levels');
}
public function createTableTriggers()
{
info('Create Tables');
$sTemplate = file_get_contents(CONST_BasePath.'/sql/table-triggers.sql');
$sTemplate = $this->replaceSqlPatterns($sTemplate);
$this->pgsqlRunScript($sTemplate, false);
}
public function createPartitionTables()
{
info('Create Partition Tables');
$sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
$sTemplate = $this->replaceSqlPatterns($sTemplate);
$this->pgsqlRunPartitionScript($sTemplate);
}
public function createPartitionFunctions()
{
info('Create Partition Functions');
$sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
$this->pgsqlRunPartitionScript($sTemplate);
}
public function importWikipediaArticles()
{
$sWikiArticlesFile = CONST_Wikipedia_Data_Path.'/wikimedia-importance.sql.gz';
if (file_exists($sWikiArticlesFile)) {
info('Importing wikipedia articles and redirects');
$this->dropTable('wikipedia_article');
$this->dropTable('wikipedia_redirect');
$this->pgsqlRunScriptFile($sWikiArticlesFile);
} else {
warn('wikipedia importance dump file not found - places will have default importance');
}
}
public function loadData($bDisableTokenPrecalc)
{
info('Drop old Data');
$this->oDB->exec('TRUNCATE word');
echo '.';
$this->oDB->exec('TRUNCATE placex');
echo '.';
$this->oDB->exec('TRUNCATE location_property_osmline');
echo '.';
$this->oDB->exec('TRUNCATE place_addressline');
echo '.';
$this->oDB->exec('TRUNCATE location_area');
echo '.';
if (!$this->dbReverseOnly()) {
$this->oDB->exec('TRUNCATE search_name');
echo '.';
}
$this->oDB->exec('TRUNCATE search_name_blank');
echo '.';
$this->oDB->exec('DROP SEQUENCE seq_place');
echo '.';
$this->oDB->exec('CREATE SEQUENCE seq_place start 100000');
echo '.';
$sSQL = 'select distinct partition from country_name';
$aPartitions = $this->oDB->getCol($sSQL);
if (!$this->bNoPartitions) $aPartitions[] = 0;
foreach ($aPartitions as $sPartition) {
$this->oDB->exec('TRUNCATE location_road_'.$sPartition);
echo '.';
}
// used by getorcreate_word_id to ignore frequent partial words
$sSQL = 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS ';
$sSQL .= '$$ SELECT '.CONST_Max_Word_Frequency.' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE';
$this->oDB->exec($sSQL);
echo ".\n";
// pre-create the word list
if (!$bDisableTokenPrecalc) {
info('Loading word list');
$this->pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
}
info('Load Data');
$sColumns = 'osm_type, osm_id, class, type, name, admin_level, address, extratags, geometry';
$aDBInstances = array();
$iLoadThreads = max(1, $this->iInstances - 1);
for ($i = 0; $i < $iLoadThreads; $i++) {
// https://secure.php.net/manual/en/function.pg-connect.php
$DSN = CONST_Database_DSN;
$DSN = preg_replace('/^pgsql:/', '', $DSN);
$DSN = preg_replace('/;/', ' ', $DSN);
$aDBInstances[$i] = pg_connect($DSN, PGSQL_CONNECT_FORCE_NEW);
pg_ping($aDBInstances[$i]);
}
for ($i = 0; $i < $iLoadThreads; $i++) {
$sSQL = "INSERT INTO placex ($sColumns) SELECT $sColumns FROM place WHERE osm_id % $iLoadThreads = $i";
$sSQL .= " and not (class='place' and type='houses' and osm_type='W'";
$sSQL .= " and ST_GeometryType(geometry) = 'ST_LineString')";
$sSQL .= ' and ST_IsValid(geometry)';
if ($this->bVerbose) echo "$sSQL\n";
if (!pg_send_query($aDBInstances[$i], $sSQL)) {
fail(pg_last_error($aDBInstances[$i]));
}
}
// last thread for interpolation lines
// https://secure.php.net/manual/en/function.pg-connect.php
$DSN = CONST_Database_DSN;
$DSN = preg_replace('/^pgsql:/', '', $DSN);
$DSN = preg_replace('/;/', ' ', $DSN);
$aDBInstances[$iLoadThreads] = pg_connect($DSN, PGSQL_CONNECT_FORCE_NEW);
pg_ping($aDBInstances[$iLoadThreads]);
$sSQL = 'insert into location_property_osmline';
$sSQL .= ' (osm_id, address, linegeo)';
$sSQL .= ' SELECT osm_id, address, geometry from place where ';
$sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
if ($this->bVerbose) echo "$sSQL\n";
if (!pg_send_query($aDBInstances[$iLoadThreads], $sSQL)) {
fail(pg_last_error($aDBInstances[$iLoadThreads]));
}
$bFailed = false;
for ($i = 0; $i <= $iLoadThreads; $i++) {
while (($hPGresult = pg_get_result($aDBInstances[$i])) !== false) {
$resultStatus = pg_result_status($hPGresult);
// PGSQL_EMPTY_QUERY, PGSQL_COMMAND_OK, PGSQL_TUPLES_OK,
// PGSQL_COPY_OUT, PGSQL_COPY_IN, PGSQL_BAD_RESPONSE,
// PGSQL_NONFATAL_ERROR and PGSQL_FATAL_ERROR
// echo 'Query result ' . $i . ' is: ' . $resultStatus . "\n";
if ($resultStatus != PGSQL_COMMAND_OK && $resultStatus != PGSQL_TUPLES_OK) {
$resultError = pg_result_error($hPGresult);
echo '-- error text ' . $i . ': ' . $resultError . "\n";
$bFailed = true;
}
}
}
if ($bFailed) {
fail('SQL errors loading placex and/or location_property_osmline tables');
}
for ($i = 0; $i < $this->iInstances; $i++) {
pg_close($aDBInstances[$i]);
}
echo "\n";
info('Reanalysing database');
$this->pgsqlRunScript('ANALYSE');
$sDatabaseDate = getDatabaseDate($this->oDB);
$this->oDB->exec('TRUNCATE import_status');
if (!$sDatabaseDate) {
warn('could not determine database date.');
} else {
$sSQL = "INSERT INTO import_status (lastimportdate) VALUES('".$sDatabaseDate."')";
$this->oDB->exec($sSQL);
echo "Latest data imported from $sDatabaseDate.\n";
}
}
public function importTigerData()
{
info('Import Tiger data');
$aFilenames = glob(CONST_Tiger_Data_Path.'/*.sql');
info('Found '.count($aFilenames).' SQL files in path '.CONST_Tiger_Data_Path);
if (empty($aFilenames)) {
warn('Tiger data import selected but no files found in path '.CONST_Tiger_Data_Path);
return;
}
$sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
$sTemplate = $this->replaceSqlPatterns($sTemplate);
$this->pgsqlRunScript($sTemplate, false);
$aDBInstances = array();
for ($i = 0; $i < $this->iInstances; $i++) {
// https://secure.php.net/manual/en/function.pg-connect.php
$DSN = CONST_Database_DSN;
$DSN = preg_replace('/^pgsql:/', '', $DSN);
$DSN = preg_replace('/;/', ' ', $DSN);
$aDBInstances[$i] = pg_connect($DSN, PGSQL_CONNECT_FORCE_NEW | PGSQL_CONNECT_ASYNC);
pg_ping($aDBInstances[$i]);
}
foreach ($aFilenames as $sFile) {
echo $sFile.': ';
$hFile = fopen($sFile, 'r');
$sSQL = fgets($hFile, 100000);
$iLines = 0;
while (true) {
for ($i = 0; $i < $this->iInstances; $i++) {
if (!pg_connection_busy($aDBInstances[$i])) {
while (pg_get_result($aDBInstances[$i]));
$sSQL = fgets($hFile, 100000);
if (!$sSQL) break 2;
if (!pg_send_query($aDBInstances[$i], $sSQL)) fail(pg_last_error($aDBInstances[$i]));
$iLines++;
if ($iLines == 1000) {
echo '.';
$iLines = 0;
}
}
}
usleep(10);
}
fclose($hFile);
$bAnyBusy = true;
while ($bAnyBusy) {
$bAnyBusy = false;
for ($i = 0; $i < $this->iInstances; $i++) {
if (pg_connection_busy($aDBInstances[$i])) $bAnyBusy = true;
}
usleep(10);
}
echo "\n";
}
for ($i = 0; $i < $this->iInstances; $i++) {
pg_close($aDBInstances[$i]);
}
info('Creating indexes on Tiger data');
$sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
$sTemplate = $this->replaceSqlPatterns($sTemplate);
$this->pgsqlRunScript($sTemplate, false);
}
public function calculatePostcodes($bCMDResultAll)
{
info('Calculate Postcodes');
$this->oDB->exec('TRUNCATE location_postcode');
$sSQL = 'INSERT INTO location_postcode';
$sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
$sSQL .= "SELECT nextval('seq_place'), 1, country_code,";
$sSQL .= " upper(trim (both ' ' from address->'postcode')) as pc,";
$sSQL .= ' ST_Centroid(ST_Collect(ST_Centroid(geometry)))';
$sSQL .= ' FROM placex';
$sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";
$sSQL .= ' AND geometry IS NOT null';
$sSQL .= ' GROUP BY country_code, pc';
$this->oDB->exec($sSQL);
// only add postcodes that are not yet available in OSM
$sSQL = 'INSERT INTO location_postcode';
$sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
$sSQL .= "SELECT nextval('seq_place'), 1, 'us', postcode,";
$sSQL .= ' ST_SetSRID(ST_Point(x,y),4326)';
$sSQL .= ' FROM us_postcode WHERE postcode NOT IN';
$sSQL .= ' (SELECT postcode FROM location_postcode';
$sSQL .= " WHERE country_code = 'us')";
$this->oDB->exec($sSQL);
// add missing postcodes for GB (if available)
$sSQL = 'INSERT INTO location_postcode';
$sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
$sSQL .= "SELECT nextval('seq_place'), 1, 'gb', postcode, geometry";
$sSQL .= ' FROM gb_postcode WHERE postcode NOT IN';
$sSQL .= ' (SELECT postcode FROM location_postcode';
$sSQL .= " WHERE country_code = 'gb')";
$this->oDB->exec($sSQL);
if (!$bCMDResultAll) {
$sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
$sSQL .= 'and word NOT IN (SELECT postcode FROM location_postcode)';
$this->oDB->exec($sSQL);
}
$sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
$sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
$this->oDB->exec($sSQL);
}
public function index($bIndexNoanalyse)
{
$oBaseCmd = (new \Nominatim\Shell(CONST_BasePath.'/nominatim/nominatim.py'))
->addParams('--database', $this->aDSNInfo['database'])
->addParams('--port', $this->aDSNInfo['port'])
->addParams('--threads', $this->iInstances);
if (!$this->bQuiet) {
$oBaseCmd->addParams('-v');
}
if ($this->bVerbose) {
$oBaseCmd->addParams('-v');
}
if (isset($this->aDSNInfo['hostspec'])) {
$oBaseCmd->addParams('--host', $this->aDSNInfo['hostspec']);
}
if (isset($this->aDSNInfo['username'])) {
$oBaseCmd->addParams('--user', $this->aDSNInfo['username']);
}
if (isset($this->aDSNInfo['password'])) {
$oBaseCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
}
info('Index ranks 0 - 4');
$oCmd = (clone $oBaseCmd)->addParams('--maxrank', 4);
echo $oCmd->escapedCmd();
$iStatus = $oCmd->run();
if ($iStatus != 0) {
fail('error status ' . $iStatus . ' running nominatim!');
}
if (!$bIndexNoanalyse) $this->pgsqlRunScript('ANALYSE');
info('Index ranks 5 - 25');
$oCmd = (clone $oBaseCmd)->addParams('--minrank', 5, '--maxrank', 25);
$iStatus = $oCmd->run();
if ($iStatus != 0) {
fail('error status ' . $iStatus . ' running nominatim!');
}
if (!$bIndexNoanalyse) $this->pgsqlRunScript('ANALYSE');
info('Index ranks 26 - 30');
$oCmd = (clone $oBaseCmd)->addParams('--minrank', 26);
$iStatus = $oCmd->run();
if ($iStatus != 0) {
fail('error status ' . $iStatus . ' running nominatim!');
}
info('Index postcodes');
$sSQL = 'UPDATE location_postcode SET indexed_status = 0';
$this->oDB->exec($sSQL);
}
public function createSearchIndices()
{
info('Create Search indices');
$sSQL = 'SELECT relname FROM pg_class, pg_index ';
$sSQL .= 'WHERE pg_index.indisvalid = false AND pg_index.indexrelid = pg_class.oid';
$aInvalidIndices = $this->oDB->getCol($sSQL);
foreach ($aInvalidIndices as $sIndexName) {
info("Cleaning up invalid index $sIndexName");
$this->oDB->exec("DROP INDEX $sIndexName;");
}
$sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
if (!$this->bDrop) {
$sTemplate .= file_get_contents(CONST_BasePath.'/sql/indices_updates.src.sql');
}
if (!$this->dbReverseOnly()) {
$sTemplate .= file_get_contents(CONST_BasePath.'/sql/indices_search.src.sql');
}
$sTemplate = $this->replaceSqlPatterns($sTemplate);
$this->pgsqlRunScript($sTemplate);
}
public function createCountryNames()
{
info('Create search index for default country names');
$this->pgsqlRunScript("select getorcreate_country(make_standard_name('uk'), 'gb')");
$this->pgsqlRunScript("select getorcreate_country(make_standard_name('united states'), 'us')");
$this->pgsqlRunScript('select count(*) from (select getorcreate_country(make_standard_name(country_code), country_code) from country_name where country_code is not null) as x');
$this->pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(name->'name'), country_code) from country_name where name ? 'name') as x");
$sSQL = 'select count(*) from (select getorcreate_country(make_standard_name(v),'
.'country_code) from (select country_code, skeys(name) as k, svals(name) as v from country_name) x where k ';
if (CONST_Languages) {
$sSQL .= 'in ';
$sDelim = '(';
foreach (explode(',', CONST_Languages) as $sLang) {
$sSQL .= $sDelim."'name:$sLang'";
$sDelim = ',';
}
$sSQL .= ')';
} else {
// all include all simple name tags
$sSQL .= "like 'name:%'";
}
$sSQL .= ') v';
$this->pgsqlRunScript($sSQL);
}
public function drop()
{
info('Drop tables only required for updates');
// The implementation is potentially a bit dangerous because it uses
// a positive selection of tables to keep, and deletes everything else.
// Including any tables that the unsuspecting user might have manually
// created. USE AT YOUR OWN PERIL.
// tables we want to keep. everything else goes.
$aKeepTables = array(
'*columns',
'import_polygon_*',
'import_status',
'place_addressline',
'location_postcode',
'location_property*',
'placex',
'search_name',
'seq_*',
'word',
'query_log',
'new_query_log',
'spatial_ref_sys',
'country_name',
'place_classtype_*',
'country_osm_grid'
);
$aDropTables = array();
$aHaveTables = $this->oDB->getListOfTables();
foreach ($aHaveTables as $sTable) {
$bFound = false;
foreach ($aKeepTables as $sKeep) {
if (fnmatch($sKeep, $sTable)) {
$bFound = true;
break;
}
}
if (!$bFound) array_push($aDropTables, $sTable);
}
foreach ($aDropTables as $sDrop) {
$this->dropTable($sDrop);
}
$this->removeFlatnodeFile();
}
private function removeFlatnodeFile()
{
if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
if (file_exists(CONST_Osm2pgsql_Flatnode_File)) {
if ($this->bVerbose) echo 'Deleting '.CONST_Osm2pgsql_Flatnode_File."\n";
unlink(CONST_Osm2pgsql_Flatnode_File);
}
}
}
private function pgsqlRunScript($sScript, $bfatal = true)
{
runSQLScript(
$sScript,
$bfatal,
$this->bVerbose,
$this->sIgnoreErrors
);
}
private function createSqlFunctions()
{
$sBasePath = CONST_BasePath.'/sql/functions/';
$sTemplate = file_get_contents($sBasePath.'utils.sql');
$sTemplate .= file_get_contents($sBasePath.'normalization.sql');
$sTemplate .= file_get_contents($sBasePath.'ranking.sql');
$sTemplate .= file_get_contents($sBasePath.'importance.sql');
$sTemplate .= file_get_contents($sBasePath.'address_lookup.sql');
$sTemplate .= file_get_contents($sBasePath.'interpolation.sql');
if ($this->oDB->tableExists('place')) {
$sTemplate .= file_get_contents($sBasePath.'place_triggers.sql');
}
if ($this->oDB->tableExists('placex')) {
$sTemplate .= file_get_contents($sBasePath.'placex_triggers.sql');
}
if ($this->oDB->tableExists('location_postcode')) {
$sTemplate .= file_get_contents($sBasePath.'postcode_triggers.sql');
}
$sTemplate = str_replace('{modulepath}', $this->sModulePath, $sTemplate);
if ($this->bEnableDiffUpdates) {
$sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
}
if ($this->bEnableDebugStatements) {
$sTemplate = str_replace('--DEBUG:', '', $sTemplate);
}
if (CONST_Limit_Reindexing) {
$sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
}
if (!CONST_Use_US_Tiger_Data) {
$sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
}
if (!CONST_Use_Aux_Location_data) {
$sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
}
$sReverseOnly = $this->dbReverseOnly() ? 'true' : 'false';
$sTemplate = str_replace('%REVERSE-ONLY%', $sReverseOnly, $sTemplate);
$this->pgsqlRunScript($sTemplate);
}
private function pgsqlRunPartitionScript($sTemplate)
{
$sSQL = 'select distinct partition from country_name';
$aPartitions = $this->oDB->getCol($sSQL);
if (!$this->bNoPartitions) $aPartitions[] = 0;
preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
foreach ($aMatches as $aMatch) {
$sResult = '';
foreach ($aPartitions as $sPartitionName) {
$sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
}
$sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
}
$this->pgsqlRunScript($sTemplate);
}
private function pgsqlRunScriptFile($sFilename)
{
if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
$oCmd = (new \Nominatim\Shell('psql'))
->addParams('--port', $this->aDSNInfo['port'])
->addParams('--dbname', $this->aDSNInfo['database']);
if (!$this->bVerbose) {
$oCmd->addParams('--quiet');
}
if (isset($this->aDSNInfo['hostspec'])) {
$oCmd->addParams('--host', $this->aDSNInfo['hostspec']);
}
if (isset($this->aDSNInfo['username'])) {
$oCmd->addParams('--username', $this->aDSNInfo['username']);
}
if (isset($this->aDSNInfo['password'])) {
$oCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
}
$ahGzipPipes = null;
if (preg_match('/\\.gz$/', $sFilename)) {
$aDescriptors = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('file', '/dev/null', 'a')
);
$oZcatCmd = new \Nominatim\Shell('zcat', $sFilename);
$hGzipProcess = proc_open($oZcatCmd->escapedCmd(), $aDescriptors, $ahGzipPipes);
if (!is_resource($hGzipProcess)) fail('unable to start zcat');
$aReadPipe = $ahGzipPipes[1];
fclose($ahGzipPipes[0]);
} else {
$oCmd->addParams('--file', $sFilename);
$aReadPipe = array('pipe', 'r');
}
$aDescriptors = array(
0 => $aReadPipe,
1 => array('pipe', 'w'),
2 => array('file', '/dev/null', 'a')
);
$ahPipes = null;
$hProcess = proc_open($oCmd->escapedCmd(), $aDescriptors, $ahPipes, null, $oCmd->aEnv);
if (!is_resource($hProcess)) fail('unable to start pgsql');
// TODO: error checking
while (!feof($ahPipes[1])) {
echo fread($ahPipes[1], 4096);
}
fclose($ahPipes[1]);
$iReturn = proc_close($hProcess);
if ($iReturn > 0) {
fail("pgsql returned with error code ($iReturn)");
}
if ($ahGzipPipes) {
fclose($ahGzipPipes[1]);
proc_close($hGzipProcess);
}
}
private function replaceSqlPatterns($sSql)
{
$sSql = str_replace('{www-user}', CONST_Database_Web_User, $sSql);
$aPatterns = array(
'{ts:address-data}' => CONST_Tablespace_Address_Data,
'{ts:address-index}' => CONST_Tablespace_Address_Index,
'{ts:search-data}' => CONST_Tablespace_Search_Data,
'{ts:search-index}' => CONST_Tablespace_Search_Index,
'{ts:aux-data}' => CONST_Tablespace_Aux_Data,
'{ts:aux-index}' => CONST_Tablespace_Aux_Index,
);
foreach ($aPatterns as $sPattern => $sTablespace) {
if ($sTablespace) {
$sSql = str_replace($sPattern, 'TABLESPACE "'.$sTablespace.'"', $sSql);
} else {
$sSql = str_replace($sPattern, '', $sSql);
}
}
return $sSql;
}
/**
* Drop table with the given name if it exists.
*
* @param string $sName Name of table to remove.
*
* @return null
*
* @pre connect() must have been called.
*/
private function dropTable($sName)
{
if ($this->bVerbose) echo "Dropping table $sName\n";
$this->oDB->deleteTable($sName);
}
/**
* Check if the database is in reverse-only mode.
*
* @return True if there is no search_name table and infrastructure.
*/
private function dbReverseOnly()
{
return !($this->oDB->tableExists('search_name'));
}
}

View File

@@ -1,31 +0,0 @@
<?php
function checkInFile($sOSMFile)
{
if (!isset($sOSMFile)) {
fail('missing --osm-file for data import');
}
if (!file_exists($sOSMFile)) {
fail('the path supplied to --osm-file does not exist');
}
if (!is_readable($sOSMFile)) {
fail('osm-file "' . $aCMDResult['osm-file'] . '" not readable');
}
}
function checkModulePresence()
{
// Try accessing the C module, so we know early if something is wrong.
// Raises Nominatim\DatabaseError on failure
$sModulePath = CONST_Database_Module_Path;
$sSQL = "CREATE FUNCTION nominatim_test_import_func(text) RETURNS text AS '";
$sSQL .= $sModulePath . "/nominatim.so', 'transliteration' LANGUAGE c IMMUTABLE STRICT";
$sSQL .= ';DROP FUNCTION nominatim_test_import_func(text);';
$oDB = new \Nominatim\DB();
$oDB->connect();
$oDB->exec($sSQL, null, 'Database server failed to load '.$sModulePath.'/nominatim.so module');
}

View File

@@ -30,14 +30,27 @@ if (empty($aPlace)) {
$aFilteredPlaces['properties']['geocoding']['label'] = $aPlace['langaddress'];
if ($aPlace['placename'] !== null) {
$aFilteredPlaces['properties']['geocoding']['name'] = $aPlace['placename'];
}
$aFilteredPlaces['properties']['geocoding']['name'] = $aPlace['placename'];
if (isset($aPlace['address'])) {
$aPlace['address']->addGeocodeJsonAddressParts(
$aFilteredPlaces['properties']['geocoding']
);
$aFieldMappings = array(
'house_number' => 'housenumber',
'road' => 'street',
'locality' => 'locality',
'postcode' => 'postcode',
'city' => 'city',
'district' => 'district',
'county' => 'county',
'state' => 'state',
'country' => 'country'
);
$aAddressNames = $aPlace['address']->getAddressNames();
foreach ($aFieldMappings as $sFrom => $sTo) {
if (isset($aAddressNames[$sFrom])) {
$aFilteredPlaces['properties']['geocoding'][$sTo] = $aAddressNames[$sFrom];
}
}
$aFilteredPlaces['properties']['geocoding']['admin']
= $aPlace['address']->getAdminLevels();

View File

@@ -9,7 +9,7 @@
<body id="reverse-page">
<?php include(CONST_BasePath.'/lib/template/includes/html-top-navigation.php'); ?>
<div class="top-bar">
<form class="form-inline" role="search" accept-charset="UTF-8" action="<?php echo CONST_Website_BaseURL; ?>reverse.php">
<div class="form-group">
<input name="format" type="hidden" value="html">
@@ -64,7 +64,7 @@
<a href="<?php echo CONST_Website_BaseURL; ?>search.php">forward search</a>
</div>
</form>
</div>
<div id="content">
@@ -85,7 +85,7 @@
else
echo ' <span class="type">('.ucwords(str_replace('_',' ',$aResult['type'])).')</span>';
echo '<p>'.$aResult['lat'].','.$aResult['lon'].'</p>';
echo detailsPermaLink($aResult, 'details', 'class="btn btn-default btn-xs details"');
echo ' <a class="btn btn-default btn-xs details" href="details.php?place_id='.$aResult['place_id'].'">details</a>';
echo '</div>';
?>
</div>

View File

@@ -1,48 +0,0 @@
<?php
header("content-type: text/html; charset=UTF-8");
include(CONST_BasePath.'/lib/template/includes/html-header.php');
?>
<title>Nominatim Deleted Data</title>
<meta name="description" content="List of OSM data that has been deleted" lang="en-US" />
</head>
<body>
<div class="container">
<h1>Deletable</h1>
<p>
<?php echo sizeof($aPolygons) ?> objects have been deleted in OSM but are still in the Nominatim database.
Also available in <a href="<?php echo CONST_Website_BaseURL; ?>deletable.php?format=json">JSON format</a>.
</p>
<table class="table table-striped table-hover">
<?php
if (!empty($aPolygons)) {
echo '<tr>';
foreach (array_keys($aPolygons[0]) as $sCol) {
echo '<th>'.$sCol.'</th>';
}
echo '</tr>';
foreach ($aPolygons as $aRow) {
echo '<tr>';
foreach ($aRow as $sCol => $sVal) {
switch ($sCol) {
case 'osm_id':
echo '<td>'.osmLink($aRow).'</td>';
break;
case 'place_id':
echo '<td>'.detailsLink($aRow).'</td>';
break;
default:
echo '<td>'.($sVal?$sVal:'&nbsp;').'</td>';
break;
}
}
echo '</tr>';
}
}
?>
</table>
</div>
</body>
</html>

View File

@@ -20,25 +20,20 @@
}
function format_distance($fDistance, $bInMeters = false)
function format_distance($fDistance)
{
if ($bInMeters) {
// $fDistance is in meters
if ($fDistance < 1) {
return '0';
}
elseif ($fDistance < 1000) {
return '<abbr class="distance" title="'.$fDistance.' meters">~'.(round($fDistance,0)).' m</abbr>';
}
else {
return '<abbr class="distance" title="'.$fDistance.' meters">~'.(round($fDistance/1000,1)).' km</abbr>';
}
} else {
if ($fDistance == 0) {
return '0';
} else {
return '<abbr class="distance" title="spheric distance '.$fDistance.'">'.(round($fDistance,4)).'</abbr>';
}
// $fDistance is in meters
if ($fDistance < 1)
{
return '0';
}
elseif ($fDistance < 1000)
{
return'<abbr class="distance" title="'.$fDistance.'">~'.(round($fDistance,0)).' m</abbr>';
}
else
{
return'<abbr class="distance" title="'.$fDistance.'">~'.(round($fDistance/1000,1)).' km</abbr>';
}
}
@@ -57,33 +52,25 @@
return $sHTML;
}
function map_icon($aPlace)
function map_icon($sIcon)
{
$sIcon = Nominatim\ClassTypes\getIconFile($aPlace);
if (isset($sIcon)) {
$sLabel = Nominatim\ClassTypes\getIcon($aPlace);
echo '<img id="mapicon" src="'.$sIcon.'" alt="'.$sLabel.'" />';
if ($sIcon){
echo '<img id="mapicon" src="'.CONST_Website_BaseURL.'images/mapicons/'.$sIcon.'.n.32.png'.'" alt="'.$sIcon.'" />';
}
}
function _one_row($aAddressLine, $bDistanceInMeters = false){
$bNotUsed = isset($aAddressLine['isaddress']) && !$aAddressLine['isaddress'];
function _one_row($aAddressLine){
$bNotUsed = (isset($aAddressLine['isaddress']) && $aAddressLine['isaddress'] == 'f');
echo '<tr class="' . ($bNotUsed?'notused':'') . '">'."\n";
echo ' <td class="name">'.(trim($aAddressLine['localname'])!==null?$aAddressLine['localname']:'<span class="noname">No Name</span>')."</td>\n";
echo ' <td>' . $aAddressLine['class'].':'.$aAddressLine['type'];
if ($aAddressLine['type'] == 'administrative'
&& isset($aAddressLine['place_type']))
{
echo '('.$aAddressLine['place_type'].')';
}
echo "</td>\n";
echo ' <td class="name">'.(trim($aAddressLine['localname'])?$aAddressLine['localname']:'<span class="noname">No Name</span>')."</td>\n";
echo ' <td>' . $aAddressLine['class'].':'.$aAddressLine['type'] . "</td>\n";
echo ' <td>' . osmLink($aAddressLine) . "</td>\n";
echo ' <td>' . (isset($aAddressLine['rank_address']) ? $aAddressLine['rank_address'] : '') . "</td>\n";
echo ' <td>' . ($aAddressLine['admin_level'] < 15 ? $aAddressLine['admin_level'] : '') . "</td>\n";
echo ' <td>' . format_distance($aAddressLine['distance'], $bDistanceInMeters)."</td>\n";
echo ' <td>' . detailsPermaLink($aAddressLine,'details &gt;') . "</td>\n";
echo ' <td>' . format_distance($aAddressLine['distance'])."</td>\n";
echo ' <td>' . detailsLink($aAddressLine,'details &gt;') . "</td>\n";
echo "</tr>\n";
}
@@ -111,10 +98,11 @@
<div class="col-sm-10">
<h1>
<?php echo $aPointDetails['localname'] ?>
<small><?php echo detailsPermaLink($aPointDetails, 'link to this page') ?></small>
</h1>
</div>
<div class="col-sm-2 text-right">
<?php map_icon($aPointDetails) ?>
<?php map_icon($aPointDetails['icon']) ?>
</div>
</div>
<div class="row">
@@ -131,11 +119,9 @@
if ($aPointDetails['calculated_importance']) {
kv('Importance' , $aPointDetails['calculated_importance'].($aPointDetails['importance']?'':' (estimated)') );
}
kv('Coverage' , ($aPointDetails['isarea']?'Polygon':'Point') );
kv('Coverage' , ($aPointDetails['isarea']=='t'?'Polygon':'Point') );
kv('Centre Point' , $aPointDetails['lat'].','.$aPointDetails['lon'] );
kv('OSM' , osmLink($aPointDetails) );
kv('Place Id (<a href="https://nominatim.org/release-docs/develop/api/Output/#place_id-is-not-a-persistent-id">on this server</a>)'
, $aPointDetails['place_id'] );
if ($aPointDetails['wikipedia'])
{
kv('Wikipedia Calculated' , wikipediaLink($aPointDetails) );
@@ -187,7 +173,7 @@
{
headline('Linked Places');
foreach ($aLinkedLines as $aAddressLine) {
_one_row($aAddressLine, true);
_one_row($aAddressLine);
}
}
@@ -226,7 +212,7 @@
headline3($sGroupHeading);
foreach ($aHierarchyLines as $aAddressLine) {
_one_row($aAddressLine, true);
_one_row($aAddressLine);
}
}
if (count($aHierarchyLines) >= 500) {

View File

@@ -1,55 +0,0 @@
<?php
header("content-type: text/html; charset=UTF-8");
?>
<?php include(CONST_BasePath.'/lib/template/includes/html-header.php'); ?>
<link href="css/common.css" rel="stylesheet" type="text/css" />
<link href="css/details.css" rel="stylesheet" type="text/css" />
</head>
<body id="details-index-page">
<div class="container">
<div class="row">
<div class="col-md-12">
<h1>Show details for place</h1>
<div class="search-form">
<h4>Search by place id</h4>
<form class="form-inline" action="details.php">
<input type="edit" class="form-control input-sm" pattern="^[0-9]+$" name="place_id" placeholder="12345" />
<input type="submit" class="btn btn-primary btn-sm" value="Show" />
</form>
</div>
<div class="search-form">
<h4>Search by OSM type and OSM id</h4>
<form id="form-by-type-and-id" class="form-inline" action="details.php">
<input type="edit" class="form-control input-sm" pattern="^[NWR][0-9]+$" placeholder="N123 or W123 or R123" />
<input type="hidden" name="osmtype" />
<input type="hidden" name="osmid" />
<input type="submit" class="btn btn-primary btn-sm" value="Show" />
</form>
</div>
<div class="search-form">
<h4>Search by openstreetmap.org URL</h4>
<form id="form-by-osm-url" class="form-inline" action="details.php">
<input type="edit" class="form-control input-sm" pattern=".*openstreetmap.*" placeholder="https://www.openstreetmap.org/relation/123" />
<input type="hidden" name="osmtype" />
<input type="hidden" name="osmid" />
<input type="submit" class="btn btn-primary btn-sm" value="Show" />
</form>
</div>
</div>
</div>
</div>
<?php include(CONST_BasePath.'/lib/template/includes/html-footer.php'); ?>
</body>
</html>

View File

@@ -26,15 +26,14 @@ $aPlaceDetails['calculated_importance'] = (float) $aPointDetails['calculated_imp
$aPlaceDetails['extratags'] = $aPointDetails['aExtraTags'];
$aPlaceDetails['calculated_wikipedia'] = $aPointDetails['wikipedia'];
$sIcon = Nominatim\ClassTypes\getIconFile($aPointDetails);
if (isset($sIcon)) {
$aPlaceDetails['icon'] = $sIcon;
if ($aPointDetails['icon']) {
$aPlaceDetails['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aPointDetails['icon'].'.n.32.png';
}
$aPlaceDetails['rank_address'] = (int) $aPointDetails['rank_address'];
$aPlaceDetails['rank_search'] = (int) $aPointDetails['rank_search'];
$aPlaceDetails['isarea'] = $aPointDetails['isarea'];
$aPlaceDetails['isarea'] = ($aPointDetails['isarea'] == 't');
$aPlaceDetails['centroid'] = array(
'type' => 'Point',
'coordinates' => array( (float) $aPointDetails['lon'], (float) $aPointDetails['lat'] )
@@ -48,13 +47,11 @@ $funcMapAddressLine = function ($aFull) {
'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
'distance' => (float) $aFull['distance']
);
return $aMapped;

View File

@@ -1,60 +0,0 @@
<?php
$title = 'Internal Server Error';
if ( $exception->getCode() == 400 ) {
$title = 'Bad Request';
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<style>
em { font-weight: bold; font-family: monospace; color: #e00404; background-color: #ffeaea; }
</style>
</head>
<body>
<h1><?php echo $title ?></h1>
<?php if (get_class($exception) == 'Nominatim\DatabaseError') { ?>
<p>Nominatim has encountered an internal error while accessing the database.
This may happen because the database is broken or because of a bug in
the software.</p>
<?php } else { ?>
<p>Nominatim has encountered an error with your request.</p>
<?php } ?>
<h3>Details</h3>
<?php echo $exception->getMessage() ?>
<?php if (CONST_Debug) { ?>
<p>
Exception <em><?php echo get_class($exception) ?></em> thrown in <em><?php echo $exception->getFile() . '('. $exception->getLine() . ')' ?></em>.
<?php if (get_class($exception) == 'Nominatim\DatabaseError') { ?>
<h3>SQL Error</h3>
<em><?php echo $exception->getSqlError() ?></em>
<pre><?php echo $exception->getSqlDebugDump() ?></pre>
<?php } ?>
<h3>Stack trace</h3>
<pre><?php echo $exception->getTraceAsString() ?></pre>
<?php } ?>
<p>
If you feel this error is incorrect feel file an issue on
<a href="https://github.com/openstreetmap/Nominatim/issues">Github</a>.
Please include the error message above and the URL you used.
</p>
</body>
</html>

View File

@@ -1,11 +0,0 @@
<?php
$error = array(
'code' => $exception->getCode(),
'message' => $exception->getMessage()
);
if (CONST_Debug) {
$error['details'] = $exception->getFile() . '('. $exception->getLine() . ')';
}
echo javascript_renderData(array('error' => $error));

View File

@@ -1,7 +0,0 @@
<error>
<code><?php echo $exception->getCode() ?></code>
<message><?php echo $exception->getMessage() ?></message>
<?php if (CONST_Debug) { ?>
<details><?php echo $exception->getFile() . '('. $exception->getLine() . ')' ?></details>
<?php } ?>
</error>

View File

@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<base href="<?php echo CONST_Website_BaseURL;?>" />
<link href="nominatim.xml" rel="search" title="Nominatim Search" type="application/opensearchdescription+xml" />
<link href="css/leaflet.css" rel="stylesheet" />
<link href="css/Control.Minimap.min.css" rel="stylesheet" />
<link href="css/bootstrap-theme.min.css" rel="stylesheet" />

View File

@@ -21,10 +21,8 @@
About &amp; Help <span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li><a href="https://nominatim.org/release-docs/develop/api/Overview/" target="_blank">API Reference</a></li>
<li><a href="https://nominatim.org/release-docs/develop/api/Faq/" target="_blank">FAQ</a></li>
<li><a href="https://help.openstreetmap.org/tags/nominatim/">OpenStreetMap Help</a></li>
<li><a href="https://github.com/openstreetmap/Nominatim">Nominatim on Github</a></li>
<li><a href="https://wiki.openstreetmap.org/wiki/Nominatim" target="_blank">Documentation</a></li>
<li><a href="https://wiki.openstreetmap.org/wiki/Nominatim/FAQ" target="_blank">FAQ</a></li>
<li role="separator" class="divider"></li>
<li><a href="#" class="" data-toggle="modal" data-target="#report-modal">Report problem with results</a></li>
</ul>

View File

@@ -6,4 +6,4 @@ look up data by its geographic coordinate (reverse search). Each result comes wi
link to a details page where you can inspect what data about the object is saved in
the database and investigate how the address of the object has been computed.</p>
For more information visit the <a href="https://nominatim.org">Nominatim home page</a>.
For more information visit the <a href="https://wiki.openstreetmap.org/wiki/Nominatim">Nominatim wiki page</a>.

View File

@@ -1,42 +1,24 @@
<p>
Before reporting problems please read the <a target="_blank" href="https://nominatim.org/release-docs/develop/api/Overview">user documentation</a>.
<h4>Finding the expected result</h4>
First of all, please make sure that the result that you expect is
available in the OpenStreetMap data.
To find the OpenStreetMap data, do the following:
<ul>
<li>Go to <a href="https://openstreetmap.org">https://openstreetmap.org</a>.</li>
<li>Go to the area of the map where you expect the result
and zoom in until you see the object you are looking for.</li>
<li>Click on the question mark on the right side of the map,
then with the question cursor on the map where your object is located.</li>
<li>Find the object of interest in the list that appears on the left side.</li>
<li>Click on the object and note down the URL that the browser shows.</li>
</ul>
If you cannot find the data you are looking for, there is a good chance
that it has not been entered yet. You should <a href="https://www.openstreetmap.org/fixthemap">report or fix the problem in OpenStreetMap</a> directly.
<h4>Reporting bad searches</h4>
Problems may be reported at the <a target="_blank" href="https://github.com/openstreetmap/nominatim/issues">issue tracker on github</a>. Please read through
the open tickets first and check if your problem has not already been
reported.
When reporting a problem, include the following:
<ul>
<li>A full description of the problem, including the exact term you
were searching for.</li>
<li>The result you get.</li>
<li>The OpenStreetMap object you expect to find (see above).</li>
</ul>
For general questions about installing and searching in Nominatim, please
use <a href="https://help.openstreetmap.org/tags/nominatim/">Help OpenStreetMap</a>.
Before reporting problems please read the <a target="_blank" href="https://wiki.openstreetmap.org/wiki/Nominatim">user documentation</a>
and
<a target="_blank" href="https://wiki.openstreetmap.org/wiki/Nominatim/FAQ">FAQ</a>.
If your problem relates to the address of a particular search result please use the 'details' link
to check how the address was generated before reporting a problem.
</p>
<p>
Use <a target="_blank" href="https://github.com/openstreetmap/nominatim/issues">Nominatim issues on github</a>
to report problems.
<!-- You can search for existing bug reports
<a href="https://trac.openstreetmap.org/query?status=new&amp;status=assigned&amp;status=reopened&amp;component=nominatim&amp;order=priority">here</a>.</p>
-->
</p>
<p>
Please ensure that you include a full description of the problem, including the search
query that you used, the problem with the result and, if the problem relates to missing data,
the osm type (node, way, relation) and id of the item that is missing.
</p>
<p>
Problems that contain enough detail are likely to get looked at before ones that require
significant research.
</p>

View File

@@ -1,71 +0,0 @@
<?php
header("content-type: text/html; charset=UTF-8");
include(CONST_BasePath.'/lib/template/includes/html-header.php');
?>
<title>Nominatim Broken Polygon Data</title>
<meta name="description" content="List of broken OSM polygon data by date" lang="en-US" />
</head>
<body>
<div class="container">
<h1>Broken polygons</h1>
<p>
Total number of broken polygons: <?php echo $iTotalBroken ?>.
Also available in <a href="<?php echo CONST_Website_BaseURL; ?>polygons.php?format=json">JSON format</a>.
</p>
<table class="table table-striped table-hover">
<?php
if (!empty($aPolygons)) {
echo '<tr>';
//var_dump($aPolygons[0]);
foreach (array_keys($aPolygons[0]) as $sCol) {
echo '<th>'.$sCol.'</th>';
}
echo '<th>&nbsp;</th>';
echo '</tr>';
$aSeen = array();
foreach ($aPolygons as $aRow) {
if (isset($aSeen[$aRow['osm_type'].$aRow['osm_id']])) continue;
$aSeen[$aRow['osm_type'].$aRow['osm_id']] = 1;
echo '<tr>';
$sOSMType = formatOSMType($aRow['osm_type']);
foreach ($aRow as $sCol => $sVal) {
switch ($sCol) {
case 'errormessage':
if (preg_match('/Self-intersection\\[([0-9.\\-]+) ([0-9.\\-]+)\\]/', $sVal, $aMatch)) {
$aRow['lat'] = $aMatch[2];
$aRow['lon'] = $aMatch[1];
$sUrl = sprintf('https://www.openstreetmap.org/?lat=%f&lon=%f&zoom=18&layers=M&%s=%d',
$aRow['lat'],
$aRow['lon'],
$sOSMType,
$aRow['osm_id']);
echo '<td><a href="'.$sUrl.'">'.($sVal?$sVal:'&nbsp;').'</a></td>';
} else {
echo '<td>'.($sVal?$sVal:'&nbsp;').'</td>';
}
break;
case 'osm_id':
echo '<td>'.osmLink(array('osm_type' => $aRow['osm_type'], 'osm_id' => $aRow['osm_id'])).'</td>';
break;
default:
echo '<td>'.($sVal?$sVal:'&nbsp;').'</td>';
break;
}
}
$sJosmUrl = 'http://localhost:8111/import?url=https://www.openstreetmap.org/api/0.6/'.$sOSMType.'/'.$aRow['osm_id'].'/full';
echo '<td><a href="'.$sJosmUrl.'" target="josm">josm</a></td>';
echo '</tr>';
}
echo '</table>';
}
?>
</div>
</body>
</html>

View File

@@ -25,6 +25,10 @@ foreach ($aBatchResults as $aSearchResults) {
$aPointDetails['aBoundingBox'][2],
$aPointDetails['aBoundingBox'][3]
);
if (isset($aPointDetails['aPolyPoints']) && $bShowPolygons) {
$aPlace['polygonpoints'] = $aPointDetails['aPolyPoints'];
}
}
if (isset($aPointDetails['zoom'])) {

View File

@@ -20,14 +20,27 @@ foreach ($aSearchResults as $iResNum => $aPointDetails) {
$aPlace['properties']['geocoding']['label'] = $aPointDetails['langaddress'];
if ($aPointDetails['placename'] !== null) {
$aPlace['properties']['geocoding']['name'] = $aPointDetails['placename'];
}
$aPlace['properties']['geocoding']['name'] = $aPointDetails['placename'];
if (isset($aPointDetails['address'])) {
$aPointDetails['address']->addGeocodeJsonAddressParts(
$aPlace['properties']['geocoding']
);
$aFieldMappings = array(
'house_number' => 'housenumber',
'road' => 'street',
'locality' => 'locality',
'postcode' => 'postcode',
'city' => 'city',
'district' => 'district',
'county' => 'county',
'state' => 'state',
'country' => 'country'
);
$aAddrNames = $aPointDetails['address']->getAddressNames();
foreach ($aFieldMappings as $sFrom => $sTo) {
if (isset($aAddrNames[$sFrom])) {
$aPlace['properties']['geocoding'][$sTo] = $aAddrNames[$sFrom];
}
}
$aPlace['properties']['geocoding']['admin']
= $aPointDetails['address']->getAdminLevels();

View File

@@ -10,46 +10,26 @@
<?php include(CONST_BasePath.'/lib/template/includes/html-top-navigation.php'); ?>
<div class="top-bar" id="structured-query-selector">
<div class="search-type-link">
<a id="switch-to-reverse" href="<?php echo CONST_Website_BaseURL; ?>reverse.php?format=html">reverse search</a>
</div>
<div class="radio-inline">
<input type="radio" name="query-selector" id="simple" value="simple">
<label for="simple">simple</label>
<form class="form-inline" role="search" accept-charset="UTF-8" action="<?php echo CONST_Website_BaseURL; ?>search.php">
<div class="form-group">
<input id="q" name="q" type="text" class="form-control input-sm" placeholder="Search" value="<?php echo htmlspecialchars($sQuery); ?>" >
</div>
<div class="radio-inline">
<input type="radio" name="query-selector" id="structured" value="structured">
<label for="structured">structured</label>
</div>
<form role="search" accept-charset="UTF-8" action="<?php echo CONST_Website_BaseURL; ?>search.php">
<div class="form-group-simple">
<input id="q" name="q" type="text" class="form-control input-sm" placeholder="Search" value="<?php echo htmlspecialchars($aMoreParams['q'] ?? ''); ?>" >
</div>
<div class="form-group-structured">
<div class="form-inline">
<input id="street" name="street" type="text" class="form-control input-sm" placeholder="House number/Street" value="<?php echo htmlspecialchars($aMoreParams['street'] ?? ''); ?>" >
<input id="city" name="city" type="text" class="form-control input-sm" placeholder="City" value="<?php echo htmlspecialchars($aMoreParams['city'] ?? ''); ?>" >
<input id="county" name="county" type="text" class="form-control input-sm" placeholder="County" value="<?php echo htmlspecialchars($aMoreParams['county'] ?? ''); ?>" >
<input id="state" name="state" type="text" class="form-control input-sm" placeholder="State" value="<?php echo htmlspecialchars($aMoreParams['state'] ?? ''); ?>" >
<input id="country" name="country" type="text" class="form-control input-sm" placeholder="Country" value="<?php echo htmlspecialchars($aMoreParams['country'] ?? ''); ?>" >
<input id="postalcode" name="postalcode" type="text" class="form-control input-sm" placeholder="Postal Code" value="<?php echo htmlspecialchars($aMoreParams['postalcode'] ?? ''); ?>" >
</div></div>
<div class="form-group search-button-group">
<button type="submit" class="btn btn-primary btn-sm">Search</button>
<?php if (CONST_Search_AreaPolygons) { ?>
<input type="hidden" value="1" name="polygon_geojson" />
<?php } ?>
<input type="hidden" name="viewbox" value="<?php echo htmlspecialchars($aMoreParams['viewbox'] ?? ''); ?>" />
<input type="hidden" name="viewbox" value="<?php if (isset($aMoreParams['viewbox'])) echo ($aMoreParams['viewbox']); ?>" />
<div class="checkbox-inline">
<input type="checkbox" id="use_viewbox" <?php if (!empty($aMoreParams['viewbox'])) echo "checked='checked'"; ?>>
<input type="checkbox" id="use_viewbox" <?php if (isset($aMoreParams['viewbox'])) echo "checked='checked'"; ?>>
<label for="use_viewbox">apply viewbox</label>
</div>
</div>
<div class="search-type-link">
<a id="switch-to-reverse" href="<?php echo CONST_Website_BaseURL; ?>reverse.php?format=html">reverse search</a>
</div>
</form>
</div>
<div id="content">
@@ -73,7 +53,7 @@
echo ' <span class="type">('.ucwords(str_replace('_',' ',$aResult['class'])).')</span>';
else
echo ' <span class="type">('.ucwords(str_replace('_',' ',$aResult['type'])).')</span>';
echo detailsPermaLink($aResult, 'details', 'class="btn btn-default btn-xs details"');
echo ' <a class="btn btn-default btn-xs details" href="details.php?place_id='.$aResult['place_id'].'">details</a>';
echo '</div>';
$i = $i+1;
}
@@ -109,6 +89,10 @@
<script type="text/javascript">
<?php
@@ -121,16 +105,7 @@
);
echo 'var nominatim_map_init = ' . json_encode($aNominatimMapInit, JSON_PRETTY_PRINT) . ';';
echo 'var nominatim_results = ' . json_encode($aSearchResults, JSON_PRETTY_PRINT) . ';';
$sStructuredQuery = (empty($aMoreParams['q'])
&& !(empty($aMoreParams['street'])
&& empty($aMoreParams['city'])
&& empty($aMoreParams['county'])
&& empty($aMoreParams['state'])
&& empty($aMoreParams['country'])
&& empty($aMoreParams['postalcode'])))
? 'true' : 'false';
echo 'var nominatim_structured_query = '.$sStructuredQuery.';';
echo 'var nominatim_results = ' . json_encode($aSearchResults, JSON_PRETTY_PRINT) . ';';
?>
</script>
<?php include(CONST_BasePath.'/lib/template/includes/html-footer.php'); ?>

View File

@@ -15,6 +15,10 @@ foreach ($aSearchResults as $iResNum => $aPointDetails) {
if (isset($aPointDetails['aBoundingBox'])) {
$aPlace['boundingbox'] = $aPointDetails['aBoundingBox'];
if (isset($aPointDetails['aPolyPoints'])) {
$aPlace['polygonpoints'] = $aPointDetails['aPolyPoints'];
}
}
if (isset($aPointDetails['zoom'])) {

View File

@@ -11,6 +11,7 @@ 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)."'";
echo " polygon='".(isset($aMoreParams['polygon'])?'true':'false')."'";
if (isset($aMoreParams['exclude_place_ids'])) {
echo " exclude_place_ids='".htmlspecialchars($aMoreParams['exclude_place_ids'])."'";
}
@@ -30,6 +31,12 @@ foreach ($aSearchResults as $iResNum => $aResult) {
echo ' boundingbox="';
echo join(',', $aResult['aBoundingBox']);
echo '"';
if (isset($aResult['aPolyPoints'])) {
echo ' polygonpoints=\'';
echo json_encode($aResult['aPolyPoints']);
echo '\'';
}
}
if (isset($aResult['asgeojson'])) {

View File

@@ -1,13 +1,4 @@
# just use the pgxs makefile
find_program(PG_CONFIG pg_config)
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}"

File diff suppressed because one or more lines are too long

View File

@@ -341,7 +341,7 @@ if (/(create\s+table\s+)([-_\w]+)\s/i) { # example: CREATE TABLE `english_engli
# in the foreign-key case it will only remove the foreign-key constraint, not the other table entirely.)
# (source: 8.1.3 docs, section "drop table")
warn "table $table will be dropped CASCADE\n";
$pre_create_sql .= "DROP TABLE $table CASCADE;\n"; # custom dumps may be missing the 'dump' commands
$pre_create_sql .= "DROP TABLE $table CASCADE\\g\n"; # custom dumps may be missing the 'dump' commands
}
s/(create\s+table\s+)([-_\w]+)\s/$1 $table /i;
@@ -367,7 +367,6 @@ if ($create_sql ne "") { # we are inside create table statement so lets
s/INSERT METHOD[=\s+][^;\s]+//i;
s/PASSWORD=[^;\s]+//i;
s/ROW_FORMAT=(?:DEFAULT|DYNAMIC|FIXED|COMPRESSED|REDUNDANT|COMPACT)+//i;
s/KEY_BLOCK_SIZE=8//i;
s/DELAY KEY WRITE=[^;\s]+//i;
s/INDEX DIRECTORY[=\s+][^;\s]+//i;
s/DATA DIRECTORY=[^;\s]+//i;
@@ -390,7 +389,6 @@ if ($create_sql ne "") { # we are inside create table statement so lets
s/DEFAULT CHARSET=[^;\s]+//i; # my mysql version is 4.1.11
s/ENGINE\s*=\s*[^;\s]+//i; # my mysql version is 4.1.11
s/ROW_FORMAT=[^;\s]+//i; # my mysql version is 5.0.22
s/KEY_BLOCK_SIZE=8//i;
s/MIN_ROWS=[^;\s]+//i;
s/MAX_ROWS=[^;\s]+//i;
s/AVG_ROW_LENGTH=[^;\s]+//i;

12
nominatim/CMakeLists.txt Normal file
View File

@@ -0,0 +1,12 @@
add_executable(nominatim export.c geometry.cpp import.c index.c input.c nominatim.c postgresql.c sprompt.c)
CHECK_SYMBOL_EXISTS(bswap_32 "byteswap.h" HAVE_BYTESWAP)
CHECK_SYMBOL_EXISTS(bswap32 "sys/endian.h" HAVE_SYS_ENDIAN)
target_compile_definitions(nominatim
PRIVATE HAVE_BYTESWAP=$<BOOL:${HAVE_BYTESWAP}>
PRIVATE HAVE_SYS_ENDIAN=$<BOOL:${HAVE_SYS_ENDIAN}>
)
target_link_libraries(nominatim ${LIBXML2_LIBRARIES} ${ZLIB_LIBRARIES} ${BZIP2_LIBRARIES} ${PostgreSQL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})

0
nominatim/README.txt Normal file
View File

558
nominatim/export.c Normal file
View File

@@ -0,0 +1,558 @@
/*
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <stdint.h>
#include <pthread.h>
#include <libpq-fe.h>
#include "nominatim.h"
#include "export.h"
#include "postgresql.h"
extern int verbose;
int mode = 0;
void nominatim_export(int rank_min, int rank_max, const char *conninfo, const char *structuredoutputfile)
{
xmlTextWriterPtr writer;
int rankTotalDone;
PGconn *conn;
PGresult * res;
PGresult * resSectors;
PGresult * resPlaces;
int rank;
int i;
int iSector;
int tuples;
const char *paramValues[2];
int paramLengths[2];
int paramFormats[2];
uint32_t paramRank;
uint32_t paramSector;
uint32_t sector;
Oid pg_prepare_params[2];
conn = PQconnectdb(conninfo);
if (PQstatus(conn) != CONNECTION_OK)
{
fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn));
exit(EXIT_FAILURE);
}
pg_prepare_params[0] = PG_OID_INT4;
res = PQprepare(conn, "index_sectors",
"select geometry_sector,count(*) from placex where rank_search = $1 and indexed_status = 0 group by geometry_sector order by geometry_sector",
1, pg_prepare_params);
if (PQresultStatus(res) != PGRES_COMMAND_OK) exit(EXIT_FAILURE);
PQclear(res);
pg_prepare_params[0] = PG_OID_INT4;
pg_prepare_params[1] = PG_OID_INT4;
res = PQprepare(conn, "index_sector_places",
"select place_id from placex where rank_search = $1 and geometry_sector = $2",
2, pg_prepare_params);
if (PQresultStatus(res) != PGRES_COMMAND_OK) exit(EXIT_FAILURE);
PQclear(res);
nominatim_exportCreatePreparedQueries(conn);
// Create the output file
writer = nominatim_exportXMLStart(structuredoutputfile);
for (rank = rank_min; rank <= rank_max; rank++)
{
printf("Starting rank %d\n", rank);
paramRank = PGint32(rank);
paramValues[0] = (char *)&paramRank;
paramLengths[0] = sizeof(paramRank);
paramFormats[0] = 1;
resSectors = PQexecPrepared(conn, "index_sectors", 1, paramValues, paramLengths, paramFormats, 1);
if (PQresultStatus(resSectors) != PGRES_TUPLES_OK)
{
fprintf(stderr, "index_sectors: SELECT failed: %s", PQerrorMessage(conn));
PQclear(resSectors);
exit(EXIT_FAILURE);
}
if (PQftype(resSectors, 0) != PG_OID_INT4)
{
fprintf(stderr, "Sector value has unexpected type\n");
PQclear(resSectors);
exit(EXIT_FAILURE);
}
if (PQftype(resSectors, 1) != PG_OID_INT8)
{
fprintf(stderr, "Sector value has unexpected type\n");
PQclear(resSectors);
exit(EXIT_FAILURE);
}
rankTotalDone = 0;
for (iSector = 0; iSector < PQntuples(resSectors); iSector++)
{
sector = PGint32(*((uint32_t *)PQgetvalue(resSectors, iSector, 0)));
// Get all the place_id's for this sector
paramRank = PGint32(rank);
paramValues[0] = (char *)&paramRank;
paramLengths[0] = sizeof(paramRank);
paramFormats[0] = 1;
paramSector = PGint32(sector);
paramValues[1] = (char *)&paramSector;
paramLengths[1] = sizeof(paramSector);
paramFormats[1] = 1;
resPlaces = PQexecPrepared(conn, "index_sector_places", 2, paramValues, paramLengths, paramFormats, 1);
if (PQresultStatus(resPlaces) != PGRES_TUPLES_OK)
{
fprintf(stderr, "index_sector_places: SELECT failed: %s", PQerrorMessage(conn));
PQclear(resPlaces);
exit(EXIT_FAILURE);
}
if (PQftype(resPlaces, 0) != PG_OID_INT8)
{
fprintf(stderr, "Place_id value has unexpected type\n");
PQclear(resPlaces);
exit(EXIT_FAILURE);
}
tuples = PQntuples(resPlaces);
for (i = 0; i < tuples; i++)
{
nominatim_exportPlace(PGint64(*((uint64_t *)PQgetvalue(resPlaces, i, 0))), conn, writer, NULL, NULL);
rankTotalDone++;
if (rankTotalDone%1000 == 0) printf("Done %i (k)\n", rankTotalDone/1000);
}
PQclear(resPlaces);
}
PQclear(resSectors);
}
nominatim_exportXMLEnd(writer);
PQfinish(conn);
}
void nominatim_exportCreatePreparedQueries(PGconn * conn)
{
Oid pg_prepare_params[2];
PGresult * res;
pg_prepare_params[0] = PG_OID_INT8;
res = PQprepare(conn, "placex_details",
"select placex.osm_type, placex.osm_id, placex.class, placex.type, placex.name, placex.housenumber, placex.country_code, ST_AsText(placex.geometry), placex.admin_level, placex.rank_address, placex.rank_search, placex.parent_place_id, parent.osm_type, parent.osm_id, placex.indexed_status, placex.linked_place_id from placex left outer join placex as parent on (placex.parent_place_id = parent.place_id) where placex.place_id = $1",
1, pg_prepare_params);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
fprintf(stderr, "Error preparing placex_details: %s", PQerrorMessage(conn));
exit(EXIT_FAILURE);
}
PQclear(res);
pg_prepare_params[0] = PG_OID_INT8;
res = PQprepare(conn, "placex_address",
"select osm_type,osm_id,class,type,distance,cached_rank_address,isaddress from place_addressline join placex on (address_place_id = placex.place_id) where place_addressline.place_id = $1 and address_place_id != place_addressline.place_id order by cached_rank_address asc,osm_type,osm_id",
1, pg_prepare_params);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
fprintf(stderr, "Error preparing placex_address: %s", PQerrorMessage(conn));
exit(EXIT_FAILURE);
}
PQclear(res);
pg_prepare_params[0] = PG_OID_INT8;
res = PQprepare(conn, "placex_names",
"select (each(name)).key,(each(name)).value from (select name from placex where place_id = $1) as x order by (each(name)).key",
1, pg_prepare_params);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
fprintf(stderr, "Error preparing placex_names: %s", PQerrorMessage(conn));
exit(EXIT_FAILURE);
}
PQclear(res);
pg_prepare_params[0] = PG_OID_INT8;
res = PQprepare(conn, "placex_extratags",
"select (each(extratags)).key,(each(extratags)).value from (select extratags from placex where place_id = $1) as x order by (each(extratags)).key",
1, pg_prepare_params);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
fprintf(stderr, "Error preparing placex_extratags: %s", PQerrorMessage(conn));
exit(EXIT_FAILURE);
}
PQclear(res);
}
xmlTextWriterPtr nominatim_exportXMLStart(const char *structuredoutputfile)
{
xmlTextWriterPtr writer;
writer = xmlNewTextWriterFilename(structuredoutputfile, 0);
if (writer==NULL)
{
fprintf(stderr, "Unable to open %s\n", structuredoutputfile);
exit(EXIT_FAILURE);
}
xmlTextWriterSetIndent(writer, 1);
if (xmlTextWriterStartDocument(writer, NULL, "UTF8", NULL) < 0)
{
fprintf(stderr, "xmlTextWriterStartDocument failed\n");
exit(EXIT_FAILURE);
}
if (xmlTextWriterStartElement(writer, BAD_CAST "osmStructured") < 0)
{
fprintf(stderr, "xmlTextWriterStartElement failed\n");
exit(EXIT_FAILURE);
}
if (xmlTextWriterWriteAttribute(writer, BAD_CAST "version", BAD_CAST "0.1") < 0)
{
fprintf(stderr, "xmlTextWriterWriteAttribute failed\n");
exit(EXIT_FAILURE);
}
if (xmlTextWriterWriteAttribute(writer, BAD_CAST "generator", BAD_CAST "Nominatim") < 0)
{
fprintf(stderr, "xmlTextWriterWriteAttribute failed\n");
exit(EXIT_FAILURE);
}
mode = 0;
return writer;
}
void nominatim_exportXMLEnd(xmlTextWriterPtr writer)
{
nominatim_exportEndMode(writer);
// End <osmStructured>
if (xmlTextWriterEndElement(writer) < 0)
{
fprintf(stderr, "xmlTextWriterEndElement failed\n");
exit(EXIT_FAILURE);
}
if (xmlTextWriterEndDocument(writer) < 0)
{
fprintf(stderr, "xmlTextWriterEndDocument failed\n");
exit(EXIT_FAILURE);
}
xmlFreeTextWriter(writer);
}
void nominatim_exportStartMode(xmlTextWriterPtr writer, int newMode)
{
if (mode == newMode) return;
nominatim_exportEndMode(writer);
switch(newMode)
{
case 0:
break;
case 1:
if (xmlTextWriterStartElement(writer, BAD_CAST "add") < 0)
{
fprintf(stderr, "xmlTextWriterStartElement failed\n");
exit(EXIT_FAILURE);
}
break;
case 2:
if (xmlTextWriterStartElement(writer, BAD_CAST "update") < 0)
{
fprintf(stderr, "xmlTextWriterStartElement failed\n");
exit(EXIT_FAILURE);
}
break;
case 3:
if (xmlTextWriterStartElement(writer, BAD_CAST "delete") < 0)
{
fprintf(stderr, "xmlTextWriterStartElement failed\n");
exit(EXIT_FAILURE);
}
break;
}
mode = newMode;
}
void nominatim_exportEndMode(xmlTextWriterPtr writer)
{
if (!mode) return;
if (xmlTextWriterEndElement(writer) < 0)
{
fprintf(stderr, "xmlTextWriterEndElement failed\n");
exit(EXIT_FAILURE);
}
}
void nominatim_exportPlaceQueries(uint64_t place_id, PGconn * conn, struct export_data * querySet)
{
const char * paramValues[1];
int paramLengths[1];
int paramFormats[1];
uint64_t paramPlaceID;
paramPlaceID = PGint64(place_id);
paramValues[0] = (char *)&paramPlaceID;
paramLengths[0] = sizeof(paramPlaceID);
paramFormats[0] = 1;
querySet->res = PQexecPrepared(conn, "placex_details", 1, paramValues, paramLengths, paramFormats, 0);
if (PQresultStatus(querySet->res) != PGRES_TUPLES_OK)
{
fprintf(stderr, "placex_details: SELECT failed: %s", PQerrorMessage(conn));
PQclear(querySet->res);
exit(EXIT_FAILURE);
}
querySet->resNames = PQexecPrepared(conn, "placex_names", 1, paramValues, paramLengths, paramFormats, 0);
if (PQresultStatus(querySet->resNames) != PGRES_TUPLES_OK)
{
fprintf(stderr, "placex_names: SELECT failed: %s", PQerrorMessage(conn));
PQclear(querySet->resNames);
exit(EXIT_FAILURE);
}
querySet->resAddress = PQexecPrepared(conn, "placex_address", 1, paramValues, paramLengths, paramFormats, 0);
if (PQresultStatus(querySet->resAddress) != PGRES_TUPLES_OK)
{
fprintf(stderr, "placex_address: SELECT failed: %s", PQerrorMessage(conn));
PQclear(querySet->resAddress);
exit(EXIT_FAILURE);
}
querySet->resExtraTags = PQexecPrepared(conn, "placex_extratags", 1, paramValues, paramLengths, paramFormats, 0);
if (PQresultStatus(querySet->resExtraTags) != PGRES_TUPLES_OK)
{
fprintf(stderr, "placex_extratags: SELECT failed: %s", PQerrorMessage(conn));
PQclear(querySet->resExtraTags);
exit(EXIT_FAILURE);
}
}
void nominatim_exportFreeQueries(struct export_data * querySet)
{
PQclear(querySet->res);
PQclear(querySet->resNames);
PQclear(querySet->resAddress);
PQclear(querySet->resExtraTags);
}
/*
* Requirements: the prepared queries must exist
*/
void nominatim_exportPlace(uint64_t place_id, PGconn * conn,
xmlTextWriterPtr writer, pthread_mutex_t * writer_mutex, struct export_data * prevQuerySet)
{
struct export_data querySet;
int i;
nominatim_exportPlaceQueries(place_id, conn, &querySet);
// Add, modify or delete?
if (prevQuerySet)
{
if ((PQgetvalue(prevQuerySet->res, 0, 14) && strcmp(PQgetvalue(prevQuerySet->res, 0, 14), "100") == 0) || PQntuples(querySet.res) == 0)
{
// Delete
if (writer_mutex) pthread_mutex_lock( writer_mutex );
nominatim_exportStartMode(writer, 3);
xmlTextWriterStartElement(writer, BAD_CAST "feature");
xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "place_id", "%li", place_id);
xmlTextWriterWriteAttribute(writer, BAD_CAST "type", BAD_CAST PQgetvalue(prevQuerySet->res, 0, 0));
xmlTextWriterWriteAttribute(writer, BAD_CAST "id", BAD_CAST PQgetvalue(prevQuerySet->res, 0, 1));
xmlTextWriterWriteAttribute(writer, BAD_CAST "key", BAD_CAST PQgetvalue(prevQuerySet->res, 0, 2));
xmlTextWriterWriteAttribute(writer, BAD_CAST "value", BAD_CAST PQgetvalue(prevQuerySet->res, 0, 3));
xmlTextWriterEndElement(writer);
if (writer_mutex) pthread_mutex_unlock( writer_mutex );
nominatim_exportFreeQueries(&querySet);
return;
}
if (PQgetvalue(prevQuerySet->res, 0, 14) && strcmp(PQgetvalue(prevQuerySet->res, 0, 14), "1") == 0)
{
// Add
if (writer_mutex) pthread_mutex_lock( writer_mutex );
nominatim_exportStartMode(writer, 1);
}
else
{
// Update, but only if something has changed
// TODO: detect changes
if (writer_mutex) pthread_mutex_lock( writer_mutex );
nominatim_exportStartMode(writer, 2);
}
}
else
{
// Add
if (writer_mutex) pthread_mutex_lock( writer_mutex );
nominatim_exportStartMode(writer, 1);
}
xmlTextWriterStartElement(writer, BAD_CAST "feature");
xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "place_id", "%li", place_id);
xmlTextWriterWriteAttribute(writer, BAD_CAST "type", BAD_CAST PQgetvalue(querySet.res, 0, 0));
xmlTextWriterWriteAttribute(writer, BAD_CAST "id", BAD_CAST PQgetvalue(querySet.res, 0, 1));
xmlTextWriterWriteAttribute(writer, BAD_CAST "key", BAD_CAST PQgetvalue(querySet.res, 0, 2));
xmlTextWriterWriteAttribute(writer, BAD_CAST "value", BAD_CAST PQgetvalue(querySet.res, 0, 3));
xmlTextWriterWriteAttribute(writer, BAD_CAST "rank", BAD_CAST PQgetvalue(querySet.res, 0, 9));
xmlTextWriterWriteAttribute(writer, BAD_CAST "importance", BAD_CAST PQgetvalue(querySet.res, 0, 10));
xmlTextWriterWriteAttribute(writer, BAD_CAST "parent_place_id", BAD_CAST PQgetvalue(querySet.res, 0, 11));
xmlTextWriterWriteAttribute(writer, BAD_CAST "parent_type", BAD_CAST PQgetvalue(querySet.res, 0, 12));
xmlTextWriterWriteAttribute(writer, BAD_CAST "parent_id", BAD_CAST PQgetvalue(querySet.res, 0, 13));
xmlTextWriterWriteAttribute(writer, BAD_CAST "linked_place_id", BAD_CAST PQgetvalue(querySet.res, 0, 15));
if (PQntuples(querySet.resNames))
{
xmlTextWriterStartElement(writer, BAD_CAST "names");
for (i = 0; i < PQntuples(querySet.resNames); i++)
{
xmlTextWriterStartElement(writer, BAD_CAST "name");
xmlTextWriterWriteAttribute(writer, BAD_CAST "type", BAD_CAST PQgetvalue(querySet.resNames, i, 0));
xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.resNames, i, 1));
xmlTextWriterEndElement(writer);
}
xmlTextWriterEndElement(writer);
}
if (PQgetvalue(querySet.res, 0, 5) && strlen(PQgetvalue(querySet.res, 0, 5)))
{
xmlTextWriterStartElement(writer, BAD_CAST "houseNumber");
xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.res, 0, 5));
xmlTextWriterEndElement(writer);
}
if (PQgetvalue(querySet.res, 0, 8) && strlen(PQgetvalue(querySet.res, 0, 8)))
{
xmlTextWriterStartElement(writer, BAD_CAST "adminLevel");
xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.res, 0, 8));
xmlTextWriterEndElement(writer);
}
if (PQgetvalue(querySet.res, 0, 6) && strlen(PQgetvalue(querySet.res, 0, 6)))
{
xmlTextWriterStartElement(writer, BAD_CAST "countryCode");
xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.res, 0, 6));
xmlTextWriterEndElement(writer);
}
if (PQntuples(querySet.resAddress) > 0)
{
xmlTextWriterStartElement(writer, BAD_CAST "address");
for (i = 0; i < PQntuples(querySet.resAddress); i++)
{
xmlTextWriterStartElement(writer, BAD_CAST getRankLabel(atoi(PQgetvalue(querySet.resAddress, i, 5))));
xmlTextWriterWriteAttribute(writer, BAD_CAST "rank", BAD_CAST PQgetvalue(querySet.resAddress, i, 5));
xmlTextWriterWriteAttribute(writer, BAD_CAST "type", BAD_CAST PQgetvalue(querySet.resAddress, i, 0));
xmlTextWriterWriteAttribute(writer, BAD_CAST "id", BAD_CAST PQgetvalue(querySet.resAddress, i, 1));
xmlTextWriterWriteAttribute(writer, BAD_CAST "key", BAD_CAST PQgetvalue(querySet.resAddress, i, 2));
xmlTextWriterWriteAttribute(writer, BAD_CAST "value", BAD_CAST PQgetvalue(querySet.resAddress, i, 3));
xmlTextWriterWriteAttribute(writer, BAD_CAST "distance", BAD_CAST PQgetvalue(querySet.resAddress, i, 4));
xmlTextWriterWriteAttribute(writer, BAD_CAST "isaddress", BAD_CAST PQgetvalue(querySet.resAddress, i, 6));
xmlTextWriterEndElement(writer);
}
xmlTextWriterEndElement(writer);
}
if (PQntuples(querySet.resExtraTags))
{
xmlTextWriterStartElement(writer, BAD_CAST "tags");
for (i = 0; i < PQntuples(querySet.resExtraTags); i++)
{
xmlTextWriterStartElement(writer, BAD_CAST "tag");
xmlTextWriterWriteAttribute(writer, BAD_CAST "type", BAD_CAST PQgetvalue(querySet.resExtraTags, i, 0));
xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.resExtraTags, i, 1));
xmlTextWriterEndElement(writer);
}
xmlTextWriterEndElement(writer);
}
xmlTextWriterStartElement(writer, BAD_CAST "osmGeometry");
xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.res, 0, 7));
xmlTextWriterEndElement(writer);
xmlTextWriterEndElement(writer); // </feature>
if (writer_mutex) pthread_mutex_unlock( writer_mutex );
nominatim_exportFreeQueries(&querySet);
}
const char * getRankLabel(int rank)
{
switch (rank)
{
case 0:
case 1:
return "continent";
case 2:
case 3:
return "sea";
case 4:
case 5:
case 6:
case 7:
return "country";
case 8:
case 9:
case 10:
case 11:
return "state";
case 12:
case 13:
case 14:
case 15:
return "county";
case 16:
return "city";
case 17:
return "town";
case 18:
return "village";
case 19:
return "unknown";
case 20:
return "suburb";
case 21:
return "postcode";
case 22:
return "neighborhood";
case 23:
return "postcode";
case 24:
return "unknown";
case 25:
return "postcode";
case 26:
return "street";
case 27:
return "access";
case 28:
return "building";
case 29:
default:
return "other";
}
}

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