Compare commits

..

2 Commits
3.3.x ... 3.2.x

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
129 changed files with 2318 additions and 4759 deletions

View File

@@ -1,6 +1,6 @@
--- ---
sudo: required sudo: required
dist: xenial dist: trusty
language: python language: python
python: python:
- "3.6" - "3.6"
@@ -11,8 +11,6 @@ git:
env: env:
- TEST_SUITE=tests - TEST_SUITE=tests
- TEST_SUITE=monaco - TEST_SUITE=monaco
before_install:
- phpenv global 7.1
install: install:
- vagrant/install-on-travis-ci.sh - vagrant/install-on-travis-ci.sh
before_script: before_script:
@@ -21,10 +19,10 @@ script:
- cd $TRAVIS_BUILD_DIR/ - cd $TRAVIS_BUILD_DIR/
- if [[ $TEST_SUITE == "tests" ]]; then phpcs --report-width=120 . ; fi - if [[ $TEST_SUITE == "tests" ]]; then phpcs --report-width=120 . ; fi
- cd $TRAVIS_BUILD_DIR/test/php - 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 - cd $TRAVIS_BUILD_DIR/test/bdd
- # behave --format=progress3 api - # 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 - if [[ $TEST_SUITE == "tests" ]]; then behave --format=progress3 osm2pgsql ; fi
- cd $TRAVIS_BUILD_DIR/build - 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 wget --no-verbose --output-document=../data/monaco.osm.pbf http://download.geofabrik.de/europe/monaco-latest.osm.pbf; fi

View File

@@ -19,7 +19,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
project(nominatim) project(nominatim)
set(NOMINATIM_VERSION_MAJOR 3) set(NOMINATIM_VERSION_MAJOR 3)
set(NOMINATIM_VERSION_MINOR 3) set(NOMINATIM_VERSION_MINOR 2)
set(NOMINATIM_VERSION_PATCH 1) set(NOMINATIM_VERSION_PATCH 1)
set(NOMINATIM_VERSION "${NOMINATIM_VERSION_MAJOR}.${NOMINATIM_VERSION_MINOR}.${NOMINATIM_VERSION_PATCH}") set(NOMINATIM_VERSION "${NOMINATIM_VERSION_MAJOR}.${NOMINATIM_VERSION_MINOR}.${NOMINATIM_VERSION_PATCH}")
@@ -93,7 +93,8 @@ message (STATUS "Using PHP binary " ${PHP_BIN})
# #
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
set(WEBSITESCRIPTS set(CUSTOMFILES
settings/phrase_settings.php
website/deletable.php website/deletable.php
website/details.php website/details.php
website/hierarchy.php website/hierarchy.php
@@ -102,31 +103,24 @@ set(WEBSITESCRIPTS
website/reverse.php website/reverse.php
website/search.php website/search.php
website/status.php website/status.php
) utils/blocks.php
set(CUSTOMSCRIPTS
utils/country_languages.php utils/country_languages.php
utils/imports.php
utils/importWikipedia.php utils/importWikipedia.php
utils/export.php utils/export.php
utils/query.php utils/query.php
utils/server_compare.php
utils/setup.php utils/setup.php
utils/specialphrases.php utils/specialphrases.php
utils/update.php utils/update.php
utils/warm.php utils/warm.php
) )
foreach (script_source ${CUSTOMSCRIPTS}) foreach (cfile ${CUSTOMFILES})
configure_file(${PROJECT_SOURCE_DIR}/cmake/script.tmpl configure_file(${PROJECT_SOURCE_DIR}/${cfile} ${PROJECT_BINARY_DIR}/${cfile})
${PROJECT_BINARY_DIR}/${script_source})
endforeach() endforeach()
foreach (script_source ${WEBSITESCRIPTS}) configure_file(${PROJECT_SOURCE_DIR}/settings/defaults.php ${PROJECT_BINARY_DIR}/settings/settings.php)
configure_file(${PROJECT_SOURCE_DIR}/cmake/website.tmpl
${PROJECT_BINARY_DIR}/${script_source})
endforeach()
configure_file(${PROJECT_SOURCE_DIR}/settings/defaults.php
${PROJECT_BINARY_DIR}/settings/settings.php)
set(WEBPATHS css images js) set(WEBPATHS css images js)

View File

@@ -1,30 +1,6 @@
3.3.1 3.2.1
* security fix: fix possible SQL injection via details API * security fix: fix possible SQL injection via details API
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.0 3.2.0
* complete rewrite of reverse search algorithm * complete rewrite of reverse search algorithm

View File

@@ -47,17 +47,11 @@ License
The source code is available under a GPLv2 license. The source code is available under a GPLv2 license.
Contact and Bug reports
======================
Contributing For questions you can join the geocoding mailinglist, see
============
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
https://lists.openstreetmap.org/listinfo/geocoding https://lists.openstreetmap.org/listinfo/geocoding
Bugs may be reported on the github project site:
https://github.com/openstreetmap/Nominatim

View File

@@ -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 inside the virtual machine. It will map the port to `localhost:9999` and then
you edit `settings/local.php` with 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` To access postgres directly remember to specify the hostname, e.g. `psql --host localhost --port 9999 nominatim_it`

9
Vagrantfile vendored
View File

@@ -23,15 +23,6 @@ Vagrant.configure("2") do |config|
end end
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| config.vm.define "ubuntu16" do |sub|
sub.vm.box = "bento/ubuntu-16.04" sub.vm.box = "bento/ubuntu-16.04"
sub.vm.provision :shell do |s| sub.vm.provision :shell do |s|

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,29 +0,0 @@
# US TIGER address data
Convert [TIGER](https://www.census.gov/geo/maps-data/data/tiger.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 prown to be slow (can take a full day) and converting them can take hours as well.
Replace '2018' with the current year throughout.
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 the TIGER 2018 data. You will need the EDGES files
(3,233 zip files, 11GB total).
wget -r ftp://ftp2.census.gov/geo/tiger/TIGER2018/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 tiger2018-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

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,6 @@
configure_file(mkdocs.yml ../mkdocs.yml) configure_file(mkdocs.yml ../mkdocs.yml)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/appendix) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/appendix)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/data-sources)
ADD_CUSTOM_TARGET(doc 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}/admin ${CMAKE_CURRENT_BINARY_DIR}/admin
@@ -13,11 +12,6 @@ ADD_CUSTOM_TARGET(doc
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}/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}/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_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/extra.css ${CMAKE_CURRENT_BINARY_DIR}/extra.css
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/data-sources/overview.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/overview.md
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/us-tiger/README.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/US-Tiger.md
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/gb-postcodes/README.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/GB-Postcodes.md
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/country-grid/README.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/Country-Grid.md
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
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-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-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-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-18.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Ubuntu-18.md

View File

@@ -71,23 +71,6 @@ and recompile (`cmake .. && make`).
Reinstall the nominatim functions with `setup.php --create--functions` Reinstall the nominatim functions with `setup.php --create--functions`
and check for any errors, e.g. a missing `nominatim.so` file. and check for any errors, e.g. a missing `nominatim.so` file.
### I see the error: "ERROR: mmap (remap) failed"
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.
### 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" ### The website shows: "Could not get word tokens"
@@ -110,11 +93,6 @@ However, you can solve this the quick and dirty way by commenting out that line
sudo systemctl restart httpd sudo systemctl restart httpd
### "must be an array or an object that implements Countable" warning in /usr/share/pear/DB.php
The warning started with PHP 7.2. Make sure you have at least [version 1.9.3 of PEAR DB](https://github.com/pear/DB/releases)
installed.
### Website reports "DB Error: insufficient permissions" ### 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`.

View File

@@ -58,93 +58,13 @@ Nominatim can use postcodes from an external source to improve searches that inv
cd $NOMINATIM_SOURCE_DIR/data cd $NOMINATIM_SOURCE_DIR/data
wget https://www.nominatim.org/data/gb_postcode_data.sql.gz wget https://www.nominatim.org/data/gb_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 32GB 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 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](http://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.
The style can be changed with the configuration `CONST_Import_Style`.
To give you an idea of the impact of using the different style, 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 32GB 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
You can also customize the styles further. For an description of the
style format see [the developement section](../develop/Import.md).
## Initial import of the data ## Initial import of the data
**Important:** first try the import with a small extract, for example from **Important:** first try the import with a small excerpt, for example from
[Geofabrik](https://download.geofabrik.de). [Geofabrik](https://download.geofabrik.de).
Download the data to import and load the data with the following command Download the data to import and load the data with the following command:
from the build directory:
```sh ```sh
./utils/setup.php --osm-file <data file> --all [--osm2pgsql-cache 28000] 2>&1 | tee setup.log ./utils/setup.php --osm-file <data file> --all [--osm2pgsql-cache 28000] 2>&1 | tee setup.log
@@ -181,34 +101,52 @@ Note that this command downloads the phrases from the wiki link above.
## Installing Tiger housenumber data for the US ## Installing Tiger housenumber data for the US
Nominatim is able to use the official [TIGER](https://www.census.gov/geo/maps-data/data/tiger.html) Nominatim is able to use the official TIGER address set to complement the
address set to complement the OSM house number data in the US. You can add OSM house number data in the US. You can add TIGER data to your own Nominatim
TIGER data to your own Nominatim instance by following these steps. The instance by following these steps:
entire US adds about 10GB to your database.
1. Get preprocessed TIGER 2018 data and unpack it into the 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: data directory in your Nominatim sources:
cd Nominatim/data cd Nominatim/data
wget https://nominatim.org/data/tiger2018-nominatim-preprocessed.tar.gz wget https://nominatim.org/data/tiger2017-nominatim-preprocessed.tar.gz
tar xf tiger2018-nominatim-preprocessed.tar.gz tar xf tiger2017-nominatim-preprocessed.tar.gz
`data-source/us-tiger/README.md` explains how the data got preprocessed. 3. Import the data into your Nominatim database:
2. Import the data into your Nominatim database:
./utils/setup.php --import-tiger-data ./utils/setup.php --import-tiger-data
3. Enable use of the Tiger data in your `settings/local.php` by adding: 4. Enable use of the Tiger data in your `settings/local.php` by adding:
@define('CONST_Use_US_Tiger_Data', true); @define('CONST_Use_US_Tiger_Data', true);
4. Apply the new settings: 5. Apply the new settings:
```sh ```sh
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions ./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 ## Updates

View File

@@ -4,43 +4,7 @@ This page describes database migrations necessary to update existing databases
to newer versions of Nominatim. to newer versions of Nominatim.
SQL statements should be executed from the postgres commandline. Execute SQL statements should be executed from the postgres commandline. Execute
`psql nominatim` to enter command line mode. `psql nominiatim` to enter command line mode.
## 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
```
## 3.1.0 -> 3.2.0 ## 3.1.0 -> 3.2.0
@@ -51,17 +15,17 @@ SQL statements to create the indexes:
``` ```
CREATE INDEX idx_placex_geometry_reverse_lookupPoint 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) 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 class not in ('railway','tunnel','bridge','man_made')
AND rank_address >= 26 AND indexed_status = 0 AND linked_place_id is null; AND rank_address >= 26 AND indexed_status = 0 AND linked_place_id is null;
CREATE INDEX idx_placex_geometry_reverse_lookupPolygon 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') WHERE St_GeometryType(geometry) in ('ST_Polygon', 'ST_MultiPolygon')
AND rank_address between 4 and 25 AND type != 'postcode' 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; AND name is not null AND indexed_status = 0 AND linked_place_id is null;
CREATE INDEX idx_placex_geometry_reverse_placeNode 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 WHERE osm_type = 'N' AND rank_search between 5 and 25
AND class = 'place' AND type != 'postcode' AND class = 'place' AND type != 'postcode'
AND name is not null AND indexed_status = 0 AND linked_place_id is null; AND name is not null AND indexed_status = 0 AND linked_place_id is null;
@@ -75,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. 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; 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 ## 3.0.0 -> 3.1.0
### Postcode Table ### Postcode Table

View File

@@ -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, the address of the closest object found, you might sometimes get one result,
sometimes the other for the closest point. 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

@@ -46,7 +46,7 @@ a single place (for reverse) of the following format:
The possible fields are: The possible fields are:
* `place_id` - reference to the Nominatim internal database ID (see notes below) * `place_id` - reference to the Nominatim internal database ID
* `osm_type`, `osm_id` - reference to the OSM object * `osm_type`, `osm_id` - reference to the OSM object
* `boundingbox` - area of corner coordinates * `boundingbox` - area of corner coordinates
* `lat`, `lon` - latitude and longitude of the centroid of the object * `lat`, `lon` - latitude and longitude of the centroid of the object
@@ -75,7 +75,7 @@ a bounding box (`bbox`).
The feature list has the following fields: The feature list has the following fields:
* `place_id` - reference to the Nominatim internal database ID (see notes below) * `place_id` - reference to the Nominatim internal database ID
* `osm_type`, `osm_id` - reference to the OSM object * `osm_type`, `osm_id` - reference to the OSM object
* `category`, `type` - key and value of the main OSM tag * `category`, `type` - key and value of the main OSM tag
* `display_name` - full comma-separated address * `display_name` - full comma-separated address
@@ -148,7 +148,7 @@ attribution to OSM and the original querystring.
The place information can be found in the `result` element. The attributes of that element contain: 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 below) * `place_id` - reference to the Nominatim internal database ID
* `osm_type`, `osm_id` - reference to the OSM object * `osm_type`, `osm_id` - reference to the OSM object
* `ref` - content of `ref` tag if it exists * `ref` - content of `ref` tag if it exists
* `lat`, `lon` - latitude and longitude of the centroid of the object * `lat`, `lon` - latitude and longitude of the centroid of the object
@@ -203,7 +203,7 @@ generic information about the query:
The place information can be found in the `place` elements, of which there may The place information can be found in the `place` elements, of which there may
be more than one. The attributes of that element contain: be more than one. The attributes of that element contain:
* `place_id` - reference to the Nominatim internal database ID (see notes below) * `place_id` - reference to the Nominatim internal database ID
* `osm_type`, `osm_id` - reference to the OSM object * `osm_type`, `osm_id` - reference to the OSM object
* `ref` - content of `ref` tag if it exists * `ref` - content of `ref` tag if it exists
* `lat`, `lon` - latitude and longitude of the centroid of the object * `lat`, `lon` - latitude and longitude of the centroid of the object
@@ -220,27 +220,3 @@ as subelements with the type of the address part.
Additional information requested with `extratags=1` and `namedetails=1` can Additional information requested with `extratags=1` and `namedetails=1` can
be found in extra elements as sub-element of each place. 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 id.
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`.

View File

@@ -80,8 +80,7 @@ In terms of address details the zoom levels are as follows:
8 | county 8 | county
10 | city 10 | city
14 | suburb 14 | suburb
16 | major streets 16 | street
17 | major and minor streets
18 | building 18 | building

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,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
*uique 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 again
means that thy match against 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 matches. 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`
At 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`
At 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`
At 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,89 +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 place 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 to search and address rank, or a tuple
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 defintions. 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

@@ -20,13 +20,6 @@ pages:
- 'Troubleshooting' : 'admin/Faq.md' - 'Troubleshooting' : 'admin/Faq.md'
- 'Developers Guide': - 'Developers Guide':
- 'Overview' : 'develop/overview.md' - 'Overview' : 'develop/overview.md'
- 'OSM Data Import' : 'develop/Import.md'
- 'Place Ranking' : 'develop/Ranking.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'
- 'Appendix': - 'Appendix':
- 'Installation on CentOS 7' : 'appendix/Install-on-Centos-7.md' - 'Installation on CentOS 7' : 'appendix/Install-on-Centos-7.md'
- 'Installation on Ubuntu 16' : 'appendix/Install-on-Ubuntu-16.md' - 'Installation on Ubuntu 16' : 'appendix/Install-on-Ubuntu-16.md'

View File

@@ -17,21 +17,21 @@ class AddressDetails
$mLangPref = 'ARRAY['.join(',', array_map('getDBQuoted', $mLangPref)).']'; $mLangPref = 'ARRAY['.join(',', array_map('getDBQuoted', $mLangPref)).']';
} }
if (!isset($sHousenumber)) { if (!$sHousenumber) {
$sHousenumber = -1; $sHousenumber = -1;
} }
$sSQL = 'SELECT *,'; $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 .= ' 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) 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) public function getAddressDetails($bAll = false)
@@ -40,7 +40,7 @@ class AddressDetails
return $this->aAddressLines; return $this->aAddressLines;
} }
return array_filter($this->aAddressLines, array(__CLASS__, 'isAddress')); return array_filter($this->aAddressLines, 'AddressDetails::isAddress');
} }
public function getLocaleAddress() public function getLocaleAddress()
@@ -49,7 +49,7 @@ class AddressDetails
$sPrevResult = ''; $sPrevResult = '';
foreach ($this->aAddressLines as $aLine) { foreach ($this->aAddressLines as $aLine) {
if ($aLine['isaddress'] && $sPrevResult != $aLine['localname']) { if ($aLine['isaddress'] == 't' && $sPrevResult != $aLine['localname']) {
$sPrevResult = $aLine['localname']; $sPrevResult = $aLine['localname'];
$aParts[] = $sPrevResult; $aParts[] = $sPrevResult;
} }
@@ -103,7 +103,7 @@ class AddressDetails
public function getAdminLevels() public function getAdminLevels()
{ {
$aAddress = array(); $aAddress = array();
foreach (array_reverse($this->aAddressLines) as $aLine) { foreach ($this->aAddressLines as $aLine) {
if (self::isAddress($aLine) if (self::isAddress($aLine)
&& isset($aLine['admin_level']) && isset($aLine['admin_level'])
&& $aLine['admin_level'] < 15 && $aLine['admin_level'] < 15

View File

@@ -1,298 +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)) { // 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);
}
/**
* Since the DSN includes the database name, checks if the connection works.
*
* @return boolean
*/
public function databaseExists()
{
$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]);
}
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;
}
}

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

@@ -527,8 +527,8 @@ class Geocode
$sNormQuery = $this->normTerm($this->sQuery); $sNormQuery = $this->normTerm($this->sQuery);
Debug::printVar('Normalized query', $sNormQuery); Debug::printVar('Normalized query', $sNormQuery);
$sLanguagePrefArraySQL = $this->oDB->getArraySQL( $sLanguagePrefArraySQL = getArraySQL(
$this->oDB->getDBQuotedList($this->aLangPrefOrder) array_map('getDBQuoted', $this->aLangPrefOrder)
); );
$sQuery = $this->sQuery; $sQuery = $this->sQuery;
@@ -546,6 +546,7 @@ class Geocode
// Do we have anything that looks like a lat/lon pair? // Do we have anything that looks like a lat/lon pair?
$sQuery = $oCtx->setNearPointFromQuery($sQuery); $sQuery = $oCtx->setNearPointFromQuery($sQuery);
$aResults = array();
if ($sQuery || $this->aStructuredQuery) { if ($sQuery || $this->aStructuredQuery) {
// Start with a single blank search // Start with a single blank search
$aSearches = array(new SearchDescription($oCtx)); $aSearches = array(new SearchDescription($oCtx));
@@ -581,9 +582,8 @@ class Geocode
if ($sSpecialTerm && !$aSearches[0]->hasOperator()) { if ($sSpecialTerm && !$aSearches[0]->hasOperator()) {
$sSpecialTerm = pg_escape_string($sSpecialTerm); $sSpecialTerm = pg_escape_string($sSpecialTerm);
$sToken = $this->oDB->getOne( $sToken = chksql(
'SELECT make_standard_name(:term)', $this->oDB->getOne("SELECT make_standard_name('$sSpecialTerm')"),
array(':term' => $sSpecialTerm),
'Cannot decode query. Wrong encoding?' 'Cannot decode query. Wrong encoding?'
); );
$sSQL = 'SELECT class, type FROM word '; $sSQL = 'SELECT class, type FROM word ';
@@ -591,7 +591,7 @@ class Geocode
$sSQL .= ' AND class is not null AND class not in (\'place\')'; $sSQL .= ' AND class is not null AND class not in (\'place\')';
Debug::printSQL($sSQL); Debug::printSQL($sSQL);
$aSearchWords = $this->oDB->getAll($sSQL); $aSearchWords = chksql($this->oDB->getAll($sSQL));
$aNewSearches = array(); $aNewSearches = array();
foreach ($aSearches as $oSearch) { foreach ($aSearches as $oSearch) {
foreach ($aSearchWords as $aSearchTerm) { foreach ($aSearchWords as $aSearchTerm) {
@@ -629,9 +629,8 @@ class Geocode
$aTokens = array(); $aTokens = array();
$aPhrases = array(); $aPhrases = array();
foreach ($aInPhrases as $iPhrase => $sPhrase) { foreach ($aInPhrases as $iPhrase => $sPhrase) {
$sPhrase = $this->oDB->getOne( $sPhrase = chksql(
'SELECT make_standard_name(:phrase)', $this->oDB->getOne('SELECT make_standard_name('.getDBQuoted($sPhrase).')'),
array(':phrase' => $sPhrase),
'Cannot normalize query string (is it a UTF-8 string?)' 'Cannot normalize query string (is it a UTF-8 string?)'
); );
if (trim($sPhrase)) { if (trim($sPhrase)) {
@@ -649,7 +648,7 @@ class Geocode
if (!empty($aTokens)) { if (!empty($aTokens)) {
$sSQL = 'SELECT word_id, word_token, word, class, type, country_code, operator, search_name_count'; $sSQL = 'SELECT word_id, word_token, word, class, type, country_code, operator, search_name_count';
$sSQL .= ' FROM word '; $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); Debug::printSQL($sSQL);
@@ -747,10 +746,8 @@ class Geocode
// Start the search process // Start the search process
$iGroupLoop = 0; $iGroupLoop = 0;
$iQueryLoop = 0; $iQueryLoop = 0;
$aNextResults = array();
foreach ($aGroupedSearches as $iGroupedRank => $aSearches) { foreach ($aGroupedSearches as $iGroupedRank => $aSearches) {
$iGroupLoop++; $iGroupLoop++;
$aResults = $aNextResults;
foreach ($aSearches as $oSearch) { foreach ($aSearches as $oSearch) {
$iQueryLoop++; $iQueryLoop++;
@@ -760,42 +757,16 @@ class Geocode
$oValidTokens->debugTokenByWordIdList() $oValidTokens->debugTokenByWordIdList()
); );
$aNewResults = $oSearch->query( $aResults += $oSearch->query(
$this->oDB, $this->oDB,
$this->iMinAddressRank, $this->iMinAddressRank,
$this->iMaxAddressRank, $this->iMaxAddressRank,
$this->iLimit $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 ($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)) { if (!empty($aResults) && ($this->iMinAddressRank != 0 || $this->iMaxAddressRank != 30)) {
// Need to verify passes rank limits before dropping out of the loop (yuk!) // Need to verify passes rank limits before dropping out of the loop (yuk!)
// reduces the number of place ids, like a filter // reduces the number of place ids, like a filter
@@ -832,7 +803,7 @@ class Geocode
if ($aFilterSql) { if ($aFilterSql) {
$sSQL = join(' UNION ', $aFilterSql); $sSQL = join(' UNION ', $aFilterSql);
Debug::printSQL($sSQL); Debug::printSQL($sSQL);
$aFilteredIDs = $this->oDB->getCol($sSQL); $aFilteredIDs = chksql($this->oDB->getCol($sSQL));
} }
$tempIDs = array(); $tempIDs = array();

View File

@@ -91,7 +91,7 @@ class ParameterParser
$sLangString = $this->getString('accept-language', $sFallback); $sLangString = $this->getString('accept-language', $sFallback);
if ($sLangString) { 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) { foreach ($aLanguagesParse as $iLang => $aLanguage) {
$aLanguages[$aLanguage[1]] = isset($aLanguage[5])?(float)$aLanguage[5]:1 - ($iLang/100); $aLanguages[$aLanguage[1]] = isset($aLanguage[5])?(float)$aLanguage[5]:1 - ($iLang/100);
if (!isset($aLanguages[$aLanguage[2]])) $aLanguages[$aLanguage[2]] = $aLanguages[$aLanguage[1]]/10; if (!isset($aLanguages[$aLanguage[2]])) $aLanguages[$aLanguage[2]] = $aLanguages[$aLanguage[1]]/10;

View File

@@ -52,7 +52,7 @@ class PlaceLookup
{ {
$aLangs = $oParams->getPreferredLanguages(); $aLangs = $oParams->getPreferredLanguages();
$this->aLangPrefOrderSql = $this->aLangPrefOrderSql =
'ARRAY['.join(',', $this->oDB->getDBQuotedList($aLangs)).']'; 'ARRAY['.join(',', array_map('getDBQuoted', $aLangs)).']';
$this->bExtraTags = $oParams->getBool('extratags', false); $this->bExtraTags = $oParams->getBool('extratags', false);
$this->bNameDetails = $oParams->getBool('namedetails', false); $this->bNameDetails = $oParams->getBool('namedetails', false);
@@ -132,9 +132,8 @@ class PlaceLookup
public function setLanguagePreference($aLangPrefOrder) public function setLanguagePreference($aLangPrefOrder)
{ {
$this->aLangPrefOrderSql = $this->oDB->getArraySQL( $this->aLangPrefOrderSql =
$this->oDB->getDBQuotedList($aLangPrefOrder) 'ARRAY['.join(',', array_map('getDBQuoted', $aLangPrefOrder)).']';
);
} }
private function addressImportanceSql($sGeometry, $sPlaceId) private function addressImportanceSql($sGeometry, $sPlaceId)
@@ -163,8 +162,8 @@ class PlaceLookup
public function lookupOSMID($sType, $iID) public function lookupOSMID($sType, $iID)
{ {
$sSQL = 'select place_id from placex where osm_type = :type and osm_id = :id'; $sSQL = "select place_id from placex where osm_type = '".$sType."' and osm_id = ".$iID;
$iPlaceID = $this->oDB->getOne($sSQL, array(':type' => $sType, ':id' => $iID)); $iPlaceID = chksql($this->oDB->getOne($sSQL));
if (!$iPlaceID) { if (!$iPlaceID) {
return null; return null;
@@ -425,10 +424,9 @@ class PlaceLookup
$sSQL = join(' UNION ', $aSubSelects); $sSQL = join(' UNION ', $aSubSelects);
Debug::printSQL($sSQL); 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) { foreach ($aPlaces as &$aPlace) {
$aPlace['importance'] = (float) $aPlace['importance'];
if ($this->bAddressDetails) { if ($this->bAddressDetails) {
// to get addressdetails for tiger data, the housenumber is needed // to get addressdetails for tiger data, the housenumber is needed
$aPlace['address'] = new AddressDetails( $aPlace['address'] = new AddressDetails(
@@ -515,9 +513,9 @@ class PlaceLookup
$sSQL .= $sFrom; $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) { if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null) {
$aOutlineResult['lat'] = $aPointPolygon['centrelat']; $aOutlineResult['lat'] = $aPointPolygon['centrelat'];
$aOutlineResult['lon'] = $aPointPolygon['centrelon']; $aOutlineResult['lon'] = $aPointPolygon['centrelon'];

View File

@@ -68,32 +68,4 @@ class Result
return $sHousenumbers; 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, 13 => 18,
14 => 22, // Suburb 14 => 22, // Suburb
15 => 22, 15 => 22,
16 => 26, // major street 16 => 26, // Street, TODO: major street?
17 => 27, // minor street 17 => 26,
18 => 30, // or >, Building 18 => 30, // or >, Building
19 => 30, // or >, Building 19 => 30, // or >, Building
); );
@@ -63,9 +63,8 @@ class ReverseGeocode
$sSQL .= ' and indexed_status = 0 and startnumber is not NULL '; $sSQL .= ' and indexed_status = 0 and startnumber is not NULL ';
$sSQL .= ' ORDER BY distance ASC limit 1'; $sSQL .= ' ORDER BY distance ASC limit 1';
return $this->oDB->getRow( return chksql(
$sSQL, $this->oDB->getRow($sSQL),
null,
'Could not determine closest housenumber on an osm interpolation line.' '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 = 'SELECT country_code FROM country_osm_grid';
$sSQL .= ' WHERE ST_CONTAINS(geometry, '.$sPointSQL.') LIMIT 1'; $sSQL .= ' WHERE ST_CONTAINS(geometry, '.$sPointSQL.') LIMIT 1';
$sCountryCode = $this->oDB->getOne( $sCountryCode = chksql(
$sSQL, $this->oDB->getOne($sSQL),
null,
'Could not determine country polygon containing the point.' 'Could not determine country polygon containing the point.'
); );
if ($sCountryCode) { if ($sCountryCode) {
@@ -117,7 +115,10 @@ class ReverseGeocode
$sSQL .= ' LIMIT 1'; $sSQL .= ' LIMIT 1';
if (CONST_Debug) var_dump($sSQL); 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) { if ($aPlace) {
return new Result($aPlace['place_id']); return new Result($aPlace['place_id']);
} }
@@ -133,7 +134,10 @@ class ReverseGeocode
$sSQL .= ' ORDER BY distance ASC'; $sSQL .= ' ORDER BY distance ASC';
if (CONST_Debug) var_dump($sSQL); 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) { if ($aPlace) {
return new Result($aPlace['place_id']); return new Result($aPlace['place_id']);
} }
@@ -174,8 +178,10 @@ class ReverseGeocode
$sSQL .= ' WHERE ST_CONTAINS(geometry, '.$sPointSQL.' )'; $sSQL .= ' WHERE ST_CONTAINS(geometry, '.$sPointSQL.' )';
$sSQL .= ' ORDER BY rank_address DESC LIMIT 1'; $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 ($aPoly) {
// if a polygon is found, search for placenodes begins ... // if a polygon is found, search for placenodes begins ...
$iParentPlaceID = $aPoly['parent_place_id']; $iParentPlaceID = $aPoly['parent_place_id'];
@@ -207,7 +213,10 @@ class ReverseGeocode
$sSQL .= ' LIMIT 1'; $sSQL .= ' LIMIT 1';
if (CONST_Debug) var_dump($sSQL); 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) { if ($aPlacNode) {
return $aPlacNode; return $aPlacNode;
} }
@@ -246,18 +255,24 @@ class ReverseGeocode
$sSQL .= ' placex'; $sSQL .= ' placex';
$sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, '.$fSearchDiam.')'; $sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, '.$fSearchDiam.')';
$sSQL .= ' AND'; $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 .= ' and (name is not null or housenumber is not null';
$sSQL .= ' or rank_address between 26 and 27)'; $sSQL .= ' or rank_address between 26 and 27)';
$sSQL .= ' and (rank_address between 26 and 27'; $sSQL .= ' and class not in (\'railway\',\'tunnel\',\'bridge\',\'man_made\')';
$sSQL .= ' or ST_GeometryType(geometry) != \'ST_LineString\')';
$sSQL .= ' and class not in (\'boundary\')';
$sSQL .= ' and indexed_status = 0 and linked_place_id is null'; $sSQL .= ' and indexed_status = 0 and linked_place_id is null';
$sSQL .= ' and (ST_GeometryType(geometry) not in (\'ST_Polygon\',\'ST_MultiPolygon\') '; $sSQL .= ' and (ST_GeometryType(geometry) not in (\'ST_Polygon\',\'ST_MultiPolygon\') ';
$sSQL .= ' OR ST_DWithin('.$sPointSQL.', centroid, '.$fSearchDiam.'))'; $sSQL .= ' OR ST_DWithin('.$sPointSQL.', centroid, '.$fSearchDiam.'))';
$sSQL .= ' ORDER BY distance ASC limit 1'; $sSQL .= ' ORDER BY distance ASC limit 1';
if (CONST_Debug) var_dump($sSQL); 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 (CONST_Debug) var_dump($aPlace);
if ($aPlace) { if ($aPlace) {
@@ -299,14 +314,16 @@ class ReverseGeocode
// radius ? // radius ?
$sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, 0.001)'; $sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, 0.001)';
$sSQL .= ' AND parent_place_id = '.$iPlaceID; $sSQL .= ' AND parent_place_id = '.$iPlaceID;
$sSQL .= ' and rank_address > 28'; $sSQL .= ' and rank_address != 28';
$sSQL .= ' and ST_GeometryType(geometry) != \'ST_LineString\'';
$sSQL .= ' and (name is not null or housenumber is not null)'; $sSQL .= ' and (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 .= ' and indexed_status = 0 and linked_place_id is null';
$sSQL .= ' ORDER BY distance ASC limit 1'; $sSQL .= ' ORDER BY distance ASC limit 1';
if (CONST_Debug) var_dump($sSQL); 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 ($aStreet) {
if (CONST_Debug) var_dump($aStreet); if (CONST_Debug) var_dump($aStreet);
$oResult = new Result($aStreet['place_id']); $oResult = new Result($aStreet['place_id']);
@@ -327,7 +344,10 @@ class ReverseGeocode
$sSQL .= ' AND ST_DWithin('.$sPointSQL.', linegeo, 0.001)'; $sSQL .= ' AND ST_DWithin('.$sPointSQL.', linegeo, 0.001)';
$sSQL .= ' ORDER BY distance ASC limit 1'; $sSQL .= ' ORDER BY distance ASC limit 1';
if (CONST_Debug) var_dump($sSQL); 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 ($aPlaceTiger) {
if (CONST_Debug) var_dump('found Tiger housenumber', $aPlaceTiger); if (CONST_Debug) var_dump('found Tiger housenumber', $aPlaceTiger);
$oResult = new Result($aPlaceTiger['place_id'], Result::TABLE_TIGER); $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 * The viewbox may be bounded which means that no search results
* must be outside the viewbox. * 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 string[] $aRoutePoints List of x,y coordinates along a route.
* @param float $fRouteWidth Buffer around the route to use. * @param float $fRouteWidth Buffer around the route to use.
* @param bool $bBounded True if the viewbox bounded. * @param bool $bBounded True if the viewbox bounded.
@@ -146,11 +146,11 @@ class SearchContext
$this->sqlViewboxCentre .= ")'::geometry,4326)"; $this->sqlViewboxCentre .= ")'::geometry,4326)";
$sSQL = 'ST_BUFFER('.$this->sqlViewboxCentre.','.($fRouteWidth/69).')'; $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"; $this->sqlViewboxSmall = "'".$sGeom."'::geometry";
$sSQL = 'ST_BUFFER('.$this->sqlViewboxCentre.','.($fRouteWidth/30).')'; $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"; $this->sqlViewboxLarge = "'".$sGeom."'::geometry";
} }

View File

@@ -237,8 +237,7 @@ class SearchDescription
$oSearch->sHouseNumber = $oSearchTerm->sToken; $oSearch->sHouseNumber = $oSearchTerm->sToken;
// sanity check: if the housenumber is not mainly made // sanity check: if the housenumber is not mainly made
// up of numbers, add a penalty // up of numbers, add a penalty
if (preg_match('/\\d/', $oSearch->sHouseNumber) === 0 if (preg_match_all('/[^0-9]/', $oSearch->sHouseNumber, $aMatches) > 2) {
|| preg_match_all('/[^0-9]/', $oSearch->sHouseNumber, $aMatches) > 2) {
$oSearch->iSearchRank++; $oSearch->iSearchRank++;
} }
if (empty($oSearchTerm->iId)) { if (empty($oSearchTerm->iId)) {
@@ -288,7 +287,7 @@ class SearchDescription
if (!empty($this->aName) || !($bFirstPhrase || $sPhraseType == '')) { if (!empty($this->aName) || !($bFirstPhrase || $sPhraseType == '')) {
if (($sPhraseType == '' || !$bFirstPhrase) && !$bHasPartial) { if (($sPhraseType == '' || !$bFirstPhrase) && !$bHasPartial) {
$oSearch = clone $this; $oSearch = clone $this;
$oSearch->iSearchRank += 2; $oSearch->iSearchRank++;
$oSearch->aAddress[$iWordID] = $iWordID; $oSearch->aAddress[$iWordID] = $iWordID;
$aNewSearches[] = $oSearch; $aNewSearches[] = $oSearch;
} else { } else {
@@ -404,7 +403,7 @@ class SearchDescription
/** /**
* Query database for places that match this search. * 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 $iMinRank Minimum address rank to restrict search to.
* @param integer $iMaxRank Maximum address rank to restrict search to. * @param integer $iMaxRank Maximum address rank to restrict search to.
* @param integer $iLimit Maximum number of results. * @param integer $iLimit Maximum number of results.
@@ -449,18 +448,11 @@ class SearchDescription
//now search for housenumber, if housenumber provided //now search for housenumber, if housenumber provided
if ($this->sHouseNumber && !empty($aResults)) { if ($this->sHouseNumber && !empty($aResults)) {
// Downgrade the rank of the street results, they are missing $aNamedPlaceIDs = $aResults;
// the housenumber. $aResults = $this->queryHouseNumber($oDB, $aNamedPlaceIDs);
foreach ($aResults as $oRes) {
$oRes->iResultRank++;
}
$aHnResults = $this->queryHouseNumber($oDB, $aResults); if (empty($aResults) && $this->looksLikeFullAddress()) {
$aResults = $aNamedPlaceIDs;
if (!empty($aHnResults)) {
foreach ($aHnResults as $oRes) {
$aResults[$oRes->iId] = $oRes;
}
} }
} }
@@ -477,13 +469,16 @@ class SearchDescription
if ($sPlaceIds) { if ($sPlaceIds) {
$sSQL = 'SELECT place_id FROM placex'; $sSQL = 'SELECT place_id FROM placex';
$sSQL .= ' WHERE place_id in ('.$sPlaceIds.')'; $sSQL .= ' WHERE place_id in ('.$sPlaceIds.')';
$sSQL .= " AND postcode != '".$this->sPostcode."'"; $sSQL .= " AND postcode = '".$this->sPostcode."'";
Debug::printSQL($sSQL); Debug::printSQL($sSQL);
$aFilteredPlaceIDs = $oDB->getCol($sSQL); $aFilteredPlaceIDs = chksql($oDB->getCol($sSQL));
if ($aFilteredPlaceIDs) { if ($aFilteredPlaceIDs) {
$aNewResults = array();
foreach ($aFilteredPlaceIDs as $iPlaceId) { 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); Debug::printSQL($sSQL);
$iPlaceId = $oDB->getOne($sSQL);
$aResults = array(); $aResults = array();
if ($iPlaceId) { foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$aResults[$iPlaceId] = new Result($iPlaceId); $aResults[$iPlaceId] = new Result($iPlaceId);
} }
@@ -523,7 +516,8 @@ class SearchDescription
$aDBResults = array(); $aDBResults = array();
$sPoiTable = $this->poiTable(); $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'; $sSQL = 'SELECT place_id FROM '.$sPoiTable.' ct';
if ($this->oContext->sqlCountryList) { if ($this->oContext->sqlCountryList) {
$sSQL .= ' JOIN placex USING (place_id)'; $sSQL .= ' JOIN placex USING (place_id)';
@@ -543,14 +537,14 @@ class SearchDescription
} elseif ($this->oContext->hasNearPoint()) { } elseif ($this->oContext->hasNearPoint()) {
$sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('ct.centroid').' ASC'; $sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('ct.centroid').' ASC';
} }
$sSQL .= " LIMIT $iLimit"; $sSQL .= " limit $iLimit";
Debug::printSQL($sSQL); Debug::printSQL($sSQL);
$aDBResults = $oDB->getCol($sSQL); $aDBResults = chksql($oDB->getCol($sSQL));
} }
if ($this->oContext->hasNearPoint()) { if ($this->oContext->hasNearPoint()) {
$sSQL = 'SELECT place_id FROM placex WHERE '; $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 '.$this->oContext->withinSQL('geometry');
$sSQL .= ' AND linked_place_id is null'; $sSQL .= ' AND linked_place_id is null';
if ($this->oContext->sqlCountryList) { if ($this->oContext->sqlCountryList) {
@@ -559,10 +553,7 @@ class SearchDescription
$sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('centroid').' ASC'; $sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('centroid').' ASC';
$sSQL .= " LIMIT $iLimit"; $sSQL .= " LIMIT $iLimit";
Debug::printSQL($sSQL); Debug::printSQL($sSQL);
$aDBResults = $oDB->getCol( $aDBResults = chksql($oDB->getCol($sSQL));
$sSQL,
array(':class' => $this->sClass, ':type' => $this->sType)
);
} }
$aResults = array(); $aResults = array();
@@ -581,23 +572,20 @@ class SearchDescription
$sSQL .= ', search_name s '; $sSQL .= ', search_name s ';
$sSQL .= 'WHERE s.place_id = p.parent_place_id '; $sSQL .= 'WHERE s.place_id = p.parent_place_id ';
$sSQL .= 'AND array_cat(s.nameaddress_vector, s.name_vector)'; $sSQL .= 'AND array_cat(s.nameaddress_vector, s.name_vector)';
$sSQL .= ' @> '.$oDB->getArraySQL($this->aAddress).' AND '; $sSQL .= ' @> '.getArraySQL($this->aAddress).' AND ';
} else { } else {
$sSQL .= 'WHERE '; $sSQL .= 'WHERE ';
} }
$sSQL .= "p.postcode = '".reset($this->aName)."'"; $sSQL .= "p.postcode = '".reset($this->aName)."'";
$sSQL .= $this->countryCodeSQL(' AND p.country_code'); $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 .= $this->oContext->excludeSQL(' AND p.place_id');
$sSQL .= " LIMIT $iLimit"; $sSQL .= " LIMIT $iLimit";
Debug::printSQL($sSQL); Debug::printSQL($sSQL);
$aResults = array(); $aResults = array();
foreach ($oDB->getCol($sSQL) as $iPlaceId) { foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$aResults[$iPlaceId] = new Result($iPlaceId, Result::TABLE_POSTCODE); $aResults[$iPlaceId] = new Result($iPlaceId, Result::TABLE_POSTCODE);
} }
@@ -640,14 +628,14 @@ class SearchDescription
} }
if (!empty($this->aName)) { if (!empty($this->aName)) {
$aTerms[] = 'name_vector @> '.$oDB->getArraySQL($this->aName); $aTerms[] = 'name_vector @> '.getArraySQL($this->aName);
} }
if (!empty($this->aAddress)) { if (!empty($this->aAddress)) {
// For infrequent name terms disable index usage for address // For infrequent name terms disable index usage for address
if ($this->bRareName) { 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 { } else {
$aTerms[] = 'nameaddress_vector @> '.$oDB->getArraySQL($this->aAddress); $aTerms[] = 'nameaddress_vector @> '.getArraySQL($this->aAddress);
} }
} }
@@ -702,7 +690,7 @@ class SearchDescription
if (!empty($this->aFullNameAddress)) { if (!empty($this->aFullNameAddress)) {
$sExactMatchSQL = ' ( '; $sExactMatchSQL = ' ( ';
$sExactMatchSQL .= ' SELECT count(*) FROM ( '; $sExactMatchSQL .= ' SELECT count(*) FROM ( ';
$sExactMatchSQL .= ' SELECT unnest('.$oDB->getArraySQL($this->aFullNameAddress).')'; $sExactMatchSQL .= ' SELECT unnest('.getArraySQL($this->aFullNameAddress).')';
$sExactMatchSQL .= ' INTERSECT '; $sExactMatchSQL .= ' INTERSECT ';
$sExactMatchSQL .= ' SELECT unnest(nameaddress_vector)'; $sExactMatchSQL .= ' SELECT unnest(nameaddress_vector)';
$sExactMatchSQL .= ' ) s'; $sExactMatchSQL .= ' ) s';
@@ -713,7 +701,7 @@ class SearchDescription
} }
if ($this->sHouseNumber || $this->sClass) { if ($this->sHouseNumber || $this->sClass) {
$iLimit = 40; $iLimit = 20;
} }
$aResults = array(); $aResults = array();
@@ -727,7 +715,10 @@ class SearchDescription
Debug::printSQL($sSQL); 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) { foreach ($aDBResults as $aResult) {
$oResult = new Result($aResult['place_id']); $oResult = new Result($aResult['place_id']);
@@ -757,7 +748,7 @@ class SearchDescription
Debug::printSQL($sSQL); Debug::printSQL($sSQL);
// XXX should inherit the exactMatches from its parent // 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); $aResults[$iPlaceId] = new Result($iPlaceId);
} }
@@ -783,7 +774,7 @@ class SearchDescription
Debug::printSQL($sSQL); Debug::printSQL($sSQL);
foreach ($oDB->getCol($sSQL) as $iPlaceId) { foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$oResult = new Result($iPlaceId, Result::TABLE_OSMLINE); $oResult = new Result($iPlaceId, Result::TABLE_OSMLINE);
$oResult->iHouseNumber = $iHousenumber; $oResult->iHouseNumber = $iHousenumber;
$aResults[$iPlaceId] = $oResult; $aResults[$iPlaceId] = $oResult;
@@ -799,7 +790,7 @@ class SearchDescription
Debug::printSQL($sSQL); Debug::printSQL($sSQL);
foreach ($oDB->getCol($sSQL) as $iPlaceId) { foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$aResults[$iPlaceId] = new Result($iPlaceId, Result::TABLE_AUX); $aResults[$iPlaceId] = new Result($iPlaceId, Result::TABLE_AUX);
} }
} }
@@ -820,7 +811,7 @@ class SearchDescription
Debug::printSQL($sSQL); Debug::printSQL($sSQL);
foreach ($oDB->getCol($sSQL) as $iPlaceId) { foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$oResult = new Result($iPlaceId, Result::TABLE_TIGER); $oResult = new Result($iPlaceId, Result::TABLE_TIGER);
$oResult->iHouseNumber = $iHousenumber; $oResult->iHouseNumber = $iHousenumber;
$aResults[$iPlaceId] = $oResult; $aResults[$iPlaceId] = $oResult;
@@ -854,7 +845,7 @@ class SearchDescription
Debug::printSQL($sSQL); Debug::printSQL($sSQL);
foreach ($oDB->getCol($sSQL) as $iPlaceId) { foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$aResults[$iPlaceId] = new Result($iPlaceId); $aResults[$iPlaceId] = new Result($iPlaceId);
} }
} }
@@ -862,11 +853,12 @@ class SearchDescription
// NEAR and IN are handled the same // NEAR and IN are handled the same
if ($this->iOperator == Operator::TYPE || $this->iOperator == Operator::NEAR) { if ($this->iOperator == Operator::TYPE || $this->iOperator == Operator::NEAR) {
$sClassTable = $this->poiTable(); $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)"; $sSQL = "SELECT min(rank_search) FROM placex WHERE place_id in ($sPlaceIDs)";
Debug::printSQL($sSQL); 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 // For state / country level searches the normal radius search doesn't work very well
$sPlaceGeom = false; $sPlaceGeom = false;
@@ -879,7 +871,7 @@ class SearchDescription
$sSQL .= ' ORDER BY rank_search ASC '; $sSQL .= ' ORDER BY rank_search ASC ';
$sSQL .= ' LIMIT 1'; $sSQL .= ' LIMIT 1';
Debug::printSQL($sSQL); Debug::printSQL($sSQL);
$sPlaceGeom = $oDB->getOne($sSQL); $sPlaceGeom = chksql($oDB->getOne($sSQL));
} }
if ($sPlaceGeom) { if ($sPlaceGeom) {
@@ -889,7 +881,7 @@ class SearchDescription
$sSQL = 'SELECT place_id FROM placex'; $sSQL = 'SELECT place_id FROM placex';
$sSQL .= " WHERE place_id in ($sPlaceIDs) and rank_search < $iMaxRank"; $sSQL .= " WHERE place_id in ($sPlaceIDs) and rank_search < $iMaxRank";
Debug::printSQL($sSQL); Debug::printSQL($sSQL);
$aPlaceIDs = $oDB->getCol($sSQL); $aPlaceIDs = chksql($oDB->getCol($sSQL));
$sPlaceIDs = join(',', $aPlaceIDs); $sPlaceIDs = join(',', $aPlaceIDs);
} }
@@ -935,7 +927,7 @@ class SearchDescription
Debug::printSQL($sSQL); Debug::printSQL($sSQL);
foreach ($oDB->getCol($sSQL) as $iPlaceId) { foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$aResults[$iPlaceId] = new Result($iPlaceId); $aResults[$iPlaceId] = new Result($iPlaceId);
} }
} else { } else {
@@ -967,7 +959,7 @@ class SearchDescription
Debug::printSQL($sSQL); Debug::printSQL($sSQL);
foreach ($oDB->getCol($sSQL) as $iPlaceId) { foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
$aResults[$iPlaceId] = new Result($iPlaceId); $aResults[$iPlaceId] = new Result($iPlaceId);
} }
} }

View File

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

View File

@@ -71,7 +71,7 @@ class TokenList
/** /**
* Add token information from the word table in the database. * 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[] $aTokens List of tokens to look up in the database.
* @param string[] $aCountryCodes List of country restrictions. * @param string[] $aCountryCodes List of country restrictions.
* @param string $sNormQuery Normalized query string. * @param string $sNormQuery Normalized query string.
@@ -85,11 +85,11 @@ class TokenList
$sSQL = 'SELECT word_id, word_token, word, class, type, country_code,'; $sSQL = 'SELECT word_id, word_token, word, class, type, country_code,';
$sSQL .= ' operator, coalesce(search_name_count, 0) as count'; $sSQL .= ' operator, coalesce(search_name_count, 0) as count';
$sSQL .= ' FROM word WHERE word_token in ('; $sSQL .= ' FROM word WHERE word_token in (';
$sSQL .= join(',', $oDB->getDBQuotedList($aTokens)).')'; $sSQL .= join(',', array_map('getDBQuoted', $aTokens)).')';
Debug::printSQL($sSQL); 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) { foreach ($aDBWords as $aWord) {
$oToken = null; $oToken = null;

View File

@@ -120,6 +120,15 @@ function showUsage($aSpec, $bExit = false, $sError = false)
exit; exit;
} }
function chksql($oSql, $sMsg = false)
{
if (PEAR::isError($oSql)) {
fail($sMsg || $oSql->getMessage(), $oSql->userinfo);
}
return $oSql;
}
function info($sMsg) function info($sMsg)
{ {
echo date('Y-m-d H:i:s == ').$sMsg."\n"; echo date('Y-m-d H:i:s == ').$sMsg."\n";
@@ -146,7 +155,7 @@ function repeatWarnings()
function runSQLScript($sScript, $bfatal = true, $bVerbose = false, $bIgnoreErrors = false) function runSQLScript($sScript, $bfatal = true, $bVerbose = false, $bIgnoreErrors = false)
{ {
// Convert database DSN to psql parameters // 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; if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
$sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database']; $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) { if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {

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,47 +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) $sSqlError = $oSql->getMessage();
{
http_response_code($exception->getCode());
header('Content-type: text/html; charset=UTF-8');
include(CONST_BasePath.'/lib/template/error-html.php');
}
function exception_handler_json($exception) echo <<<INTERNALFAIL
{ <html>
http_response_code($exception->getCode()); <head><title>Internal Server Error</title></head>
header('Content-type: application/json; charset=utf-8'); <body>
include(CONST_BasePath.'/lib/template/error-json.php'); <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) if (CONST_Debug) {
{ var_dump($oSql);
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');
}
function set_exception_handler_by_format($sFormat = 'html')
{
if ($sFormat == 'html') {
set_exception_handler('exception_handler_html');
} elseif ($sFormat == 'xml') {
set_exception_handler('exception_handler_xml');
} else { } else {
set_exception_handler('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();
/*************************************************************************** /***************************************************************************
@@ -64,6 +96,6 @@ if (CONST_NoAccessControl) {
header('Access-Control-Allow-Headers: '.$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']); header('Access-Control-Allow-Headers: '.$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
} }
} }
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS') exit; if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') exit;
if (CONST_Debug) header('Content-type: text/html; charset=utf-8'); if (CONST_Debug) header('Content-type: text/html; charset=utf-8');

View File

@@ -1,7 +1,7 @@
<?php <?php
require_once(CONST_BasePath.'/lib/lib.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()) { if (get_magic_quotes_gpc()) {
echo "Please disable magic quotes in your php.ini configuration\n"; 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; if (!$sUserError) $sUserError = $sError;
error_log('ERROR: '.$sError); error_log('ERROR: '.$sError);
var_dump($sUserError)."\n"; echo $sUserError."\n";
exit(-1); exit(-1);
} }
@@ -61,26 +61,23 @@ function byImportance($a, $b)
function javascript_renderData($xVal, $iOptions = 0) function javascript_renderData($xVal, $iOptions = 0)
{ {
$sCallback = isset($_GET['json_callback']) ? $_GET['json_callback'] : ''; $iOptions |= JSON_UNESCAPED_UNICODE;
if ($sCallback && !preg_match('/^[$_\p{L}][$_\p{L}\p{Nd}.[\]]*$/u', $sCallback)) {
// Unset, we call javascript_renderData again during exception handling
unset($_GET['json_callback']);
throw new Exception('Invalid json_callback value', 400);
}
$iOptions |= JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
if (isset($_GET['pretty']) && in_array(strtolower($_GET['pretty']), array('1', 'true'))) { if (isset($_GET['pretty']) && in_array(strtolower($_GET['pretty']), array('1', 'true'))) {
$iOptions |= JSON_PRETTY_PRINT; $iOptions |= JSON_PRETTY_PRINT;
} }
$jsonout = json_encode($xVal, $iOptions); $jsonout = json_encode($xVal, $iOptions);
if ($sCallback) { if (!isset($_GET['json_callback'])) {
header('Content-Type: application/javascript; charset=UTF-8');
echo $_GET['json_callback'].'('.$jsonout.')';
} else {
header('Content-Type: application/json; charset=UTF-8'); header('Content-Type: application/json; charset=UTF-8');
echo $jsonout; 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');
}
} }
} }
@@ -228,25 +225,3 @@ function closestHouseNumber($aRow)
return max(min($aRow['endnumber'], $iHn), $aRow['startnumber']); 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,18 +36,8 @@ function logStart(&$oDB, $sType = '', $sQuery = '', $aLanguageList = array())
$sUserAgent = $_SERVER['HTTP_USER_AGENT']; $sUserAgent = $_SERVER['HTTP_USER_AGENT'];
else $sUserAgent = ''; else $sUserAgent = '';
$sSQL = 'insert into new_query_log (type,starttime,query,ipaddress,useragent,language,format,searchterm)'; $sSQL = 'insert into new_query_log (type,starttime,query,ipaddress,useragent,language,format,searchterm)';
$sSQL .= ' values ('. $sSQL .= ' values ('.getDBQuoted($sType).','.getDBQuoted($hLog[0]).','.getDBQuoted($hLog[2]);
$sSQL .= join(',', $oDB->getDBQuotedList(array( $sSQL .= ','.getDBQuoted($hLog[1]).','.getDBQuoted($sUserAgent).','.getDBQuoted(join(',', $aLanguageList)).','.getDBQuoted($sOutputFormat).','.getDBQuoted($hLog[3]).')';
$sType,
$hLog[0],
$hLog[2],
$hLog[1],
$sUserAgent,
join(',', $aLanguageList),
$sOutputFormat,
$hLog[3]
)));
$sSQL .= ')';
$oDB->query($sSQL); $oDB->query($sSQL);
} }
@@ -63,10 +53,10 @@ function logEnd(&$oDB, $hLog, $iNumResults)
if (!$aEndTime[1]) $aEndTime[1] = '0'; if (!$aEndTime[1]) $aEndTime[1] = '0';
$sEndTime = date('Y-m-d H:i:s', $aEndTime[0]).'.'.$aEndTime[1]; $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 = 'update new_query_log set endtime = '.getDBQuoted($sEndTime).', results = '.$iNumResults;
$sSQL .= ' where starttime = '.$oDB->getDBQuoted($hLog[0]); $sSQL .= ' where starttime = '.getDBQuoted($hLog[0]);
$sSQL .= ' and ipaddress = '.$oDB->getDBQuoted($hLog[1]); $sSQL .= ' and ipaddress = '.getDBQuoted($hLog[1]);
$sSQL .= ' and query = '.$oDB->getDBQuoted($hLog[2]); $sSQL .= ' and query = '.getDBQuoted($hLog[2]);
$oDB->query($sSQL); $oDB->query($sSQL);
} }

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,928 +0,0 @@
<?php
namespace Nominatim\Setup;
require_once(CONST_BasePath.'/lib/setup/AddressLevelParser.php');
class SetupFunctions
{
protected $iCacheMemory;
protected $iInstances;
protected $sModulePath;
protected $aDSNInfo;
protected $bVerbose;
protected $sIgnoreErrors;
protected $bEnableDiffUpdates;
protected $bEnableDebugStatements;
protected $bNoPartitions;
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);
}
// Assume we can steal all the cache memory in the box (unless told otherwise)
if (isset($aCMDResult['osm2pgsql-cache'])) {
$this->iCacheMemory = $aCMDResult['osm2pgsql-cache'];
} else {
$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->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;
}
}
public function createDB()
{
info('Create DB');
$oDB = new \Nominatim\DB;
if ($oDB->databaseExists()) {
fail('database already exists ('.CONST_Database_DSN.')');
}
$sCreateDBCmd = 'createdb -E UTF-8 -p '.$this->aDSNInfo['port'].' '.$this->aDSNInfo['database'];
if (isset($this->aDSNInfo['username'])) {
$sCreateDBCmd .= ' -U '.$this->aDSNInfo['username'];
}
if (isset($this->aDSNInfo['hostspec'])) {
$sCreateDBCmd .= ' -h '.$this->aDSNInfo['hostspec'];
}
$result = $this->runWithPgEnv($sCreateDBCmd);
if ($result != 0) fail('Error executing external command: '.$sCreateDBCmd);
}
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.01) {
fail('Minimum supported version of Postgresql is 9.1.');
}
$this->pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
$this->pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
// For extratags and namedetails the hstore_to_json converter is
// needed which is only available from Postgresql 9.3+. For older
// versions add a dummy function that returns nothing.
$iNumFunc = $this->oDB->getOne("select count(*) from pg_proc where proname = 'hstore_to_json'");
if ($iNumFunc == 0) {
$this->pgsqlRunScript("create function hstore_to_json(dummy hstore) returns text AS 'select null::text' language sql immutable");
warn('Postgresql is too old. extratags and namedetails API not available.');
}
$fPostgisVersion = $this->oDB->getPostgisVersion();
echo 'Postgis version found: '.$fPostgisVersion."\n";
if ($fPostgisVersion < 2.1) {
// Functions were renamed in 2.1 and throw an annoying deprecation warning
$this->pgsqlRunScript('ALTER FUNCTION st_line_interpolate_point(geometry, double precision) RENAME TO ST_LineInterpolatePoint');
$this->pgsqlRunScript('ALTER FUNCTION ST_Line_Locate_Point(geometry, geometry) RENAME TO ST_LineLocatePoint');
}
if ($fPostgisVersion < 2.2) {
$this->pgsqlRunScript('ALTER FUNCTION ST_Distance_Spheroid(geometry, geometry, spheroid) RENAME TO ST_DistanceSpheroid');
}
$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_BasePath.'/data/country_osm_grid.sql.gz');
$this->pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
$sPostcodeFilename = CONST_BasePath.'/data/gb_postcode_data.sql.gz';
if (file_exists($sPostcodeFilename)) {
$this->pgsqlRunScriptFile($sPostcodeFilename);
} else {
warn('optional external UK postcode table file ('.$sPostcodeFilename.') not found. Skipping.');
}
if (CONST_Use_Extra_US_Postcodes) {
$this->pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');
}
if ($this->bNoPartitions) {
$this->pgsqlRunScript('update country_name set partition = 0');
}
// the following will be needed by createFunctions later but
// is only defined in the subsequently called createTables
// Create dummies here that will be overwritten by the proper
// versions in create-tables.
$this->pgsqlRunScript('CREATE TABLE IF NOT EXISTS place_boundingbox ()');
$this->pgsqlRunScript('CREATE TYPE wikipedia_article_match AS ()', false);
}
public function importData($sOSMFile)
{
info('Import data');
$osm2pgsql = CONST_Osm2pgsql_Binary;
if (!file_exists($osm2pgsql)) {
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 '$osm2pgsql'");
}
$osm2pgsql .= ' -S '.CONST_Import_Style;
if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
$osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
}
if (CONST_Tablespace_Osm2pgsql_Data)
$osm2pgsql .= ' --tablespace-slim-data '.CONST_Tablespace_Osm2pgsql_Data;
if (CONST_Tablespace_Osm2pgsql_Index)
$osm2pgsql .= ' --tablespace-slim-index '.CONST_Tablespace_Osm2pgsql_Index;
if (CONST_Tablespace_Place_Data)
$osm2pgsql .= ' --tablespace-main-data '.CONST_Tablespace_Place_Data;
if (CONST_Tablespace_Place_Index)
$osm2pgsql .= ' --tablespace-main-index '.CONST_Tablespace_Place_Index;
$osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
$osm2pgsql .= ' -C '.$this->iCacheMemory;
$osm2pgsql .= ' -P '.$this->aDSNInfo['port'];
if (isset($this->aDSNInfo['username'])) {
$osm2pgsql .= ' -U '.$this->aDSNInfo['username'];
}
if (isset($this->aDSNInfo['hostspec'])) {
$osm2pgsql .= ' -H '.$this->aDSNInfo['hostspec'];
}
$osm2pgsql .= ' -d '.$this->aDSNInfo['database'].' '.$sOSMFile;
$this->runWithPgEnv($osm2pgsql);
if (!$this->sIgnoreErrors && !$this->oDB->getRow('select * from place limit 1')) {
fail('No Data');
}
}
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 = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
$sTemplate = $this->replaceTablespace(
'{ts:address-data}',
CONST_Tablespace_Address_Data,
$sTemplate
);
$sTemplate = $this->replaceTablespace(
'{ts:address-index}',
CONST_Tablespace_Address_Index,
$sTemplate
);
$sTemplate = $this->replaceTablespace(
'{ts:search-data}',
CONST_Tablespace_Search_Data,
$sTemplate
);
$sTemplate = $this->replaceTablespace(
'{ts:search-index}',
CONST_Tablespace_Search_Index,
$sTemplate
);
$sTemplate = $this->replaceTablespace(
'{ts:aux-data}',
CONST_Tablespace_Aux_Data,
$sTemplate
);
$sTemplate = $this->replaceTablespace(
'{ts:aux-index}',
CONST_Tablespace_Aux_Index,
$sTemplate
);
$this->pgsqlRunScript($sTemplate, false);
if ($bReverseOnly) {
$this->pgExec('DROP TABLE search_name');
}
$oAlParser = new AddressLevelParser(CONST_Address_Level_Config);
$oAlParser->createTable($this->oDB, 'address_levels');
}
public function createPartitionTables()
{
info('Create Partition Tables');
$sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
$sTemplate = $this->replaceTablespace(
'{ts:address-data}',
CONST_Tablespace_Address_Data,
$sTemplate
);
$sTemplate = $this->replaceTablespace(
'{ts:address-index}',
CONST_Tablespace_Address_Index,
$sTemplate
);
$sTemplate = $this->replaceTablespace(
'{ts:search-data}',
CONST_Tablespace_Search_Data,
$sTemplate
);
$sTemplate = $this->replaceTablespace(
'{ts:search-index}',
CONST_Tablespace_Search_Index,
$sTemplate
);
$sTemplate = $this->replaceTablespace(
'{ts:aux-data}',
CONST_Tablespace_Aux_Data,
$sTemplate
);
$sTemplate = $this->replaceTablespace(
'{ts:aux-index}',
CONST_Tablespace_Aux_Index,
$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.'/wikipedia_article.sql.bin';
$sWikiRedirectsFile = CONST_Wikipedia_Data_Path.'/wikipedia_redirect.sql.bin';
if (file_exists($sWikiArticlesFile)) {
info('Importing wikipedia articles');
$this->pgsqlRunDropAndRestore($sWikiArticlesFile);
} else {
warn('wikipedia article dump file not found - places will have default importance');
}
if (file_exists($sWikiRedirectsFile)) {
info('Importing wikipedia redirects');
$this->pgsqlRunDropAndRestore($sWikiRedirectsFile);
} else {
warn('wikipedia redirect dump file not found - some place importance values may be missing');
}
}
public function loadData($bDisableTokenPrecalc)
{
info('Drop old Data');
$this->pgExec('TRUNCATE word');
echo '.';
$this->pgExec('TRUNCATE placex');
echo '.';
$this->pgExec('TRUNCATE location_property_osmline');
echo '.';
$this->pgExec('TRUNCATE place_addressline');
echo '.';
$this->pgExec('TRUNCATE place_boundingbox');
echo '.';
$this->pgExec('TRUNCATE location_area');
echo '.';
if (!$this->dbReverseOnly()) {
$this->pgExec('TRUNCATE search_name');
echo '.';
}
$this->pgExec('TRUNCATE search_name_blank');
echo '.';
$this->pgExec('DROP SEQUENCE seq_place');
echo '.';
$this->pgExec('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->pgExec('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->pgExec($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');
$sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
$sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
$sTemplate = $this->replaceTablespace(
'{ts:aux-data}',
CONST_Tablespace_Aux_Data,
$sTemplate
);
$sTemplate = $this->replaceTablespace(
'{ts:aux-index}',
CONST_Tablespace_Aux_Index,
$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 (glob(CONST_Tiger_Data_Path.'/*.sql') 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 = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
$sTemplate = $this->replaceTablespace(
'{ts:aux-data}',
CONST_Tablespace_Aux_Data,
$sTemplate
);
$sTemplate = $this->replaceTablespace(
'{ts:aux-index}',
CONST_Tablespace_Aux_Index,
$sTemplate
);
$this->pgsqlRunScript($sTemplate, false);
}
public function calculatePostcodes($bCMDResultAll)
{
info('Calculate Postcodes');
$this->pgExec('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->pgExec($sSQL);
if (CONST_Use_Extra_US_Postcodes) {
// 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->pgExec($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->pgExec($sSQL);
if (!$bCMDResultAll) {
$sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
$sSQL .= 'and word NOT IN (SELECT postcode FROM location_postcode)';
$this->pgExec($sSQL);
}
$sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
$sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
$this->pgExec($sSQL);
}
public function index($bIndexNoanalyse)
{
$sOutputFile = '';
$sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$this->aDSNInfo['database'].' -P '
.$this->aDSNInfo['port'].' -t '.$this->iInstances.$sOutputFile;
if (isset($this->aDSNInfo['hostspec'])) {
$sBaseCmd .= ' -H '.$this->aDSNInfo['hostspec'];
}
if (isset($this->aDSNInfo['username'])) {
$sBaseCmd .= ' -U '.$this->aDSNInfo['username'];
}
info('Index ranks 0 - 4');
$iStatus = $this->runWithPgEnv($sBaseCmd.' -R 4');
if ($iStatus != 0) {
fail('error status ' . $iStatus . ' running nominatim!');
}
if (!$bIndexNoanalyse) $this->pgsqlRunScript('ANALYSE');
info('Index ranks 5 - 25');
$iStatus = $this->runWithPgEnv($sBaseCmd.' -r 5 -R 25');
if ($iStatus != 0) {
fail('error status ' . $iStatus . ' running nominatim!');
}
if (!$bIndexNoanalyse) $this->pgsqlRunScript('ANALYSE');
info('Index ranks 26 - 30');
$iStatus = $this->runWithPgEnv($sBaseCmd.' -r 26');
if ($iStatus != 0) {
fail('error status ' . $iStatus . ' running nominatim!');
}
info('Index postcodes');
$sSQL = 'UPDATE location_postcode SET indexed_status = 0';
$this->pgExec($sSQL);
}
public function createSearchIndices()
{
info('Create Search indices');
$sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
if (!$this->dbReverseOnly()) {
$sTemplate .= file_get_contents(CONST_BasePath.'/sql/indices_search.src.sql');
}
$sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
$sTemplate = $this->replaceTablespace(
'{ts:address-index}',
CONST_Tablespace_Address_Index,
$sTemplate
);
$sTemplate = $this->replaceTablespace(
'{ts:search-index}',
CONST_Tablespace_Search_Index,
$sTemplate
);
$sTemplate = $this->replaceTablespace(
'{ts:aux-index}',
CONST_Tablespace_Aux_Index,
$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->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'");
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) {
if ($this->bVerbose) echo "Dropping table $sDrop\n";
$this->oDB->exec("DROP TABLE $sDrop CASCADE");
// ignore warnings/errors as they might be caused by a table having
// been deleted already by CASCADE
}
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 pgsqlRunDropAndRestore($sDumpFile)
{
$sCMD = 'pg_restore -p '.$this->aDSNInfo['port'].' -d '.$this->aDSNInfo['database'].' --no-owner -Fc --clean '.$sDumpFile;
if ($this->oDB->getPostgresVersion() >= 9.04) {
$sCMD .= ' --if-exists';
}
if (isset($this->aDSNInfo['hostspec'])) {
$sCMD .= ' -h '.$this->aDSNInfo['hostspec'];
}
if (isset($this->aDSNInfo['username'])) {
$sCMD .= ' -U '.$this->aDSNInfo['username'];
}
$this->runWithPgEnv($sCMD);
}
private function pgsqlRunScript($sScript, $bfatal = true)
{
runSQLScript(
$sScript,
$bfatal,
$this->bVerbose,
$this->sIgnoreErrors
);
}
private function createSqlFunctions()
{
$sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.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);
$sCMD = 'psql -p '.$this->aDSNInfo['port'].' -d '.$this->aDSNInfo['database'];
if (!$this->bVerbose) {
$sCMD .= ' -q';
}
if (isset($this->aDSNInfo['hostspec'])) {
$sCMD .= ' -h '.$this->aDSNInfo['hostspec'];
}
if (isset($this->aDSNInfo['username'])) {
$sCMD .= ' -U '.$this->aDSNInfo['username'];
}
$aProcEnv = null;
if (isset($this->aDSNInfo['password'])) {
$aProcEnv = array_merge(array('PGPASSWORD' => $this->aDSNInfo['password']), $_ENV);
}
$ahGzipPipes = null;
if (preg_match('/\\.gz$/', $sFilename)) {
$aDescriptors = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('file', '/dev/null', 'a')
);
$hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
if (!is_resource($hGzipProcess)) fail('unable to start zcat');
$aReadPipe = $ahGzipPipes[1];
fclose($ahGzipPipes[0]);
} else {
$sCMD .= ' -f '.$sFilename;
$aReadPipe = array('pipe', 'r');
}
$aDescriptors = array(
0 => $aReadPipe,
1 => array('pipe', 'w'),
2 => array('file', '/dev/null', 'a')
);
$ahPipes = null;
$hProcess = proc_open($sCMD, $aDescriptors, $ahPipes, null, $aProcEnv);
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 replaceTablespace($sTemplate, $sTablespace, $sSql)
{
if ($sTablespace) {
$sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"', $sSql);
} else {
$sSql = str_replace($sTemplate, '', $sSql);
}
return $sSql;
}
private function runWithPgEnv($sCmd)
{
if ($this->bVerbose) {
echo "Execute: $sCmd\n";
}
$aProcEnv = null;
if (isset($this->aDSNInfo['password'])) {
$aProcEnv = array_merge(array('PGPASSWORD' => $this->aDSNInfo['password']), $_ENV);
}
return runWithEnv($sCmd, $aProcEnv);
}
/**
* Execute the SQL command on the open database.
*
* @param string $sSQL SQL command to execute.
*
* @return null
*
* @pre connect() must have been called.
*/
private function pgExec($sSQL)
{
$this->oDB->exec($sSQL);
}
/**
* 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

@@ -61,7 +61,7 @@
function _one_row($aAddressLine){ function _one_row($aAddressLine){
$bNotUsed = isset($aAddressLine['isaddress']) && !$aAddressLine['isaddress']; $bNotUsed = (isset($aAddressLine['isaddress']) && $aAddressLine['isaddress'] == 'f');
echo '<tr class="' . ($bNotUsed?'notused':'') . '">'."\n"; echo '<tr class="' . ($bNotUsed?'notused':'') . '">'."\n";
echo ' <td class="name">'.(trim($aAddressLine['localname'])?$aAddressLine['localname']:'<span class="noname">No Name</span>')."</td>\n"; echo ' <td class="name">'.(trim($aAddressLine['localname'])?$aAddressLine['localname']:'<span class="noname">No Name</span>')."</td>\n";
@@ -119,7 +119,7 @@
if ($aPointDetails['calculated_importance']) { if ($aPointDetails['calculated_importance']) {
kv('Importance' , $aPointDetails['calculated_importance'].($aPointDetails['importance']?'':' (estimated)') ); 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('Centre Point' , $aPointDetails['lat'].','.$aPointDetails['lon'] );
kv('OSM' , osmLink($aPointDetails) ); kv('OSM' , osmLink($aPointDetails) );
if ($aPointDetails['wikipedia']) if ($aPointDetails['wikipedia'])

View File

@@ -33,7 +33,7 @@ if ($aPointDetails['icon']) {
$aPlaceDetails['rank_address'] = (int) $aPointDetails['rank_address']; $aPlaceDetails['rank_address'] = (int) $aPointDetails['rank_address'];
$aPlaceDetails['rank_search'] = (int) $aPointDetails['rank_search']; $aPlaceDetails['rank_search'] = (int) $aPointDetails['rank_search'];
$aPlaceDetails['isarea'] = $aPointDetails['isarea']; $aPlaceDetails['isarea'] = ($aPointDetails['isarea'] == 't');
$aPlaceDetails['centroid'] = array( $aPlaceDetails['centroid'] = array(
'type' => 'Point', 'type' => 'Point',
'coordinates' => array( (float) $aPointDetails['lon'], (float) $aPointDetails['lat'] ) 'coordinates' => array( (float) $aPointDetails['lon'], (float) $aPointDetails['lat'] )

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

@@ -7,14 +7,14 @@
convertWarningsToExceptions="true" convertWarningsToExceptions="true"
processIsolation="false" processIsolation="false"
stopOnFailure="false" stopOnFailure="false"
bootstrap="./bootstrap.php" syntaxCheck="true"
beStrictAboutTestsThatDoNotTestAnything="true" bootstrap="test/php/bootstrap.php"
> >
<php> <php>
</php> </php>
<testsuites> <testsuites>
<testsuite name="Nominatim PHP Test Suite"> <testsuite name="Nominatim PHP Test Suite">
<directory>./Nominatim</directory> <directory>./test/php/Nominatim</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<filter> <filter>

View File

@@ -1,85 +0,0 @@
[
{ "tags" : {
"place" : {
"sea" : [2, 0],
"continent" : [2, 0],
"country" : [4, 4],
"state" : [8, 8],
"region" : [18, 0],
"county" : 12,
"city" : 16,
"island" : [17, 0],
"town" : [18, 16],
"village" : [19, 16],
"hamlet" : [19, 16],
"municipality" : [19, 16],
"district" : [19, 16],
"unincorporated_area" : [19, 16],
"borough" : [19, 16],
"suburb" : 20,
"croft" : 20,
"subdivision" : 20,
"isolated_dwelling" : 20,
"farm" : [20, 0],
"locality" : [20, 0],
"islet" : [20, 0],
"mountain_pass" : [20, 0],
"neighbourhood" : 22,
"houses" : [28, 0]
},
"boundary" : {
"administrative2" : 4,
"administrative3" : 6,
"administrative4" : 8,
"administrative5" : 10,
"administrative6" : 12,
"administrative7" : 14,
"administrative8" : 16,
"administrative9" : 18,
"administrative10" : 20,
"administrative11" : 22,
"administrative12" : 24
},
"landuse" : {
"residential" : 22,
"farm" : 22,
"farmyard" : 22,
"industrial" : 22,
"commercial" : 22,
"allotments" : 22,
"retail" : 22,
"" : [22, 0]
},
"leisure" : {
"park" : [24, 0]
},
"natural" : {
"peak" : [18, 0],
"volcano" : [18, 0],
"mountain_range" : [18, 0],
"sea" : [4, 0]
},
"waterway" : {
"" : [17, 0]
},
"highway" : {
"" : 26,
"service" : 27,
"cycleway" : 27,
"path" : 27,
"footway" : 27,
"steps" : 27,
"bridleway" : 27,
"motorway_link" : 27,
"primary_link" : 27,
"trunk_link" : 27,
"secondary_link" : 27,
"tertiary_link" : 27
},
"mountain_pass" : {
"" : [20, 0]
}
}
}
]

View File

@@ -7,7 +7,7 @@ if (isset($_GET['debug']) && $_GET['debug']) @define('CONST_Debug', true);
// General settings // General settings
@define('CONST_Debug', false); @define('CONST_Debug', false);
@define('CONST_Database_DSN', 'pgsql:dbname=nominatim'); // or add ;host=...;port=...;user=...;password=... @define('CONST_Database_DSN', 'pgsql://@/nominatim'); // <driver>://<username>:<password>@<host>:<port>/<database>
@define('CONST_Database_Web_User', 'www-data'); @define('CONST_Database_Web_User', 'www-data');
@define('CONST_Database_Module_Path', CONST_InstallPath.'/module'); @define('CONST_Database_Module_Path', CONST_InstallPath.'/module');
@define('CONST_Max_Word_Frequency', '50000'); @define('CONST_Max_Word_Frequency', '50000');
@@ -49,9 +49,6 @@ if (isset($_GET['debug']) && $_GET['debug']) @define('CONST_Debug', true);
@define('CONST_Pyosmium_Binary', '@PYOSMIUM_PATH@'); @define('CONST_Pyosmium_Binary', '@PYOSMIUM_PATH@');
@define('CONST_Tiger_Data_Path', CONST_ExtraDataPath.'/tiger'); @define('CONST_Tiger_Data_Path', CONST_ExtraDataPath.'/tiger');
@define('CONST_Wikipedia_Data_Path', CONST_ExtraDataPath); @define('CONST_Wikipedia_Data_Path', CONST_ExtraDataPath);
@define('CONST_Phrase_Config', CONST_BasePath.'/settings/phrase_settings.php');
@define('CONST_Address_Level_Config', CONST_BasePath.'/settings/address-levels.json');
@define('CONST_Import_Style', CONST_BasePath.'/settings/import-full.style');
// osm2pgsql settings // osm2pgsql settings
@define('CONST_Osm2pgsql_Flatnode_File', null); @define('CONST_Osm2pgsql_Flatnode_File', null);

View File

@@ -1,116 +0,0 @@
[
{
"keys" : [ "" ],
"values" : {
"no" : "skip"
}
},
{
"keys" : ["name:prefix", "name:suffix", "name:botanical", "*wikidata"],
"values" : {
"" : "skip"
}
},
{
"keys" : ["ref", "int_ref", "nat_ref", "reg_ref", "loc_ref", "old_ref",
"iata", "icao", "pcode"],
"values" : {
"" : "ref"
}
},
{
"keys" : ["name", "name:*", "int_name", "int_name:*", "nat_name", "nat_name:*",
"reg_name", "reg_name:*", "loc_name", "loc_name:*",
"old_name", "old_name:*", "alt_name", "alt_name:*", "alt_name_*",
"official_name", "official_name:*", "place_name", "place_name:*",
"short_name", "short_name:*", "brand"],
"values" : {
"" : "name"
}
},
{
"keys" : ["landuse"],
"values" : {
"cemetry" : "skip",
"" : "fallback,with_name"
}
},
{
"keys" : ["boundary"],
"values" : {
"administrative" : "main"
}
},
{
"keys" : ["place"],
"values" : {
"" : "main"
}
},
{
"keys" : ["addr:housename"],
"values" : {
"" : "name,house"
}
},
{
"keys" : ["addr:housenumber", "addr:conscriptionnumber", "addr:streetnumber"],
"values" : {
"" : "address,house"
}
},
{
"keys" : ["addr:interpolation"],
"values" : {
"" : "interpolation,address"
}
},
{
"keys" : ["postal_code", "postcode", "addr:postcode",
"tiger:zip_left", "tiger:zip_right"],
"values" : {
"" : "postcode,fallback"
}
},
{
"keys" : ["country_code", "ISO3166-1", "is_in:country_code", "is_in_country",
"addr:country", "addr:country", "addr:country_code"],
"values" : {
"" : "country"
}
},
{
"keys" : ["addr:*", "is_in:*", "tiger:county"],
"values" : {
"" : "address"
}
},
{
"keys" : ["highway"],
"values" : {
"motorway" : "main",
"trunk" : "main",
"primary" : "main",
"secondary" : "main",
"tertiary" : "main",
"unclassified" : "main",
"residential" : "main",
"living_street" : "main",
"pedestrian" : "main",
"road" : "main",
"service" : "main,with_name",
"cycleway" : "main,with_name",
"path" : "main,with_name",
"footway" : "main,with_name",
"steps" : "main,with_name",
"bridleway" : "main,with_name",
"track" : "main,with_name",
"byway": "main,with_name",
"motorway_link" : "main,with_name",
"trunk_link" : "main,with_name",
"primary_link" : "main,with_name",
"secondary_link" : "main,with_name",
"tertiary_link" : "main,with_name"
}
}
]

View File

@@ -1,70 +0,0 @@
[
{
"keys" : ["name:prefix", "name:suffix", "name:botanical", "*wikidata"],
"values" : {
"" : "skip"
}
},
{
"keys" : ["ref", "int_ref", "nat_ref", "reg_ref", "loc_ref", "old_ref",
"iata", "icao", "pcode"],
"values" : {
"" : "ref"
}
},
{
"keys" : ["name", "name:*", "int_name", "int_name:*", "nat_name", "nat_name:*",
"reg_name", "reg_name:*", "loc_name", "loc_name:*",
"old_name", "old_name:*", "alt_name", "alt_name:*", "alt_name_*",
"official_name", "official_name:*", "place_name", "place_name:*",
"short_name", "short_name:*", "brand"],
"values" : {
"" : "name"
}
},
{
"keys" : ["landuse"],
"values" : {
"cemetry" : "skip",
"" : "fallback,with_name"
}
},
{
"keys" : ["boundary"],
"values" : {
"administrative" : "main"
}
},
{
"keys" : ["place"],
"values" : {
"" : "main"
}
},
{
"keys" : ["country_code", "ISO3166-1", "is_in:country_code", "is_in_country",
"addr:country", "addr:country", "addr:country_code"],
"values" : {
"" : "country"
}
},
{
"keys" : ["addr:*", "is_in:*", "tiger:county"],
"values" : {
"" : "address"
}
},
{
"keys" : ["postal_code", "postcode", "addr:postcode",
"tiger:zip_left", "tiger:zip_right"],
"values" : {
"" : "postcode"
}
},
{
"keys" : ["capital"],
"values" : {
"" : "extra"
}
}
]

View File

@@ -1,239 +0,0 @@
[
{
"keys" : ["*source"],
"values" : {
"" : "skip"
}
},
{
"keys" : ["name:prefix", "name:suffix", "name:botanical", "wikidata",
"*:wikidata"],
"values" : {
"" : "extra"
}
},
{
"keys" : ["ref", "int_ref", "nat_ref", "reg_ref", "loc_ref", "old_ref",
"iata", "icao", "pcode", "pcode:*"],
"values" : {
"" : "ref"
}
},
{
"keys" : ["name", "name:*", "int_name", "int_name:*", "nat_name", "nat_name:*",
"reg_name", "reg_name:*", "loc_name", "loc_name:*",
"old_name", "old_name:*", "alt_name", "alt_name:*", "alt_name_*",
"official_name", "official_name:*", "place_name", "place_name:*",
"short_name", "short_name:*", "brand"],
"values" : {
"" : "name"
}
},
{
"keys" : ["addr:housename"],
"values" : {
"" : "name,house"
}
},
{
"keys" : ["emergency"],
"values" : {
"fire_hydrant" : "skip",
"yes" : "skip",
"no" : "skip",
"" : "main"
}
},
{
"keys" : ["historic", "military"],
"values" : {
"no" : "skip",
"yes" : "skip",
"" : "main"
}
},
{
"keys" : ["natural"],
"values" : {
"yes" : "skip",
"no" : "skip",
"coastline" : "skip",
"" : "main,with_name"
}
},
{
"keys" : ["landuse"],
"values" : {
"cemetry" : "main,with_name",
"" : "main,fallback,with_name"
}
},
{
"keys" : ["highway"],
"values" : {
"no" : "skip",
"turning_circle" : "skip",
"mini_roundabout" : "skip",
"noexit" : "skip",
"crossing" : "skip",
"traffic_signals" : "main,with_name",
"service" : "main,with_name",
"cycleway" : "main,with_name",
"path" : "main,with_name",
"footway" : "main,with_name",
"steps" : "main,with_name",
"bridleway" : "main,with_name",
"track" : "main,with_name",
"byway": "main,with_name",
"motorway_link" : "main,with_name",
"trunk_link" : "main,with_name",
"primary_link" : "main,with_name",
"secondary_link" : "main,with_name",
"tertiary_link" : "main,with_name",
"" : "main"
}
},
{
"keys" : ["railway"],
"values" : {
"level_crossing" : "skip",
"no" : "skip",
"" : "main,with_name"
}
},
{
"keys" : ["man_made"],
"values" : {
"survey_point" : "skip",
"cutline" : "skip",
"" : "main"
}
},
{
"keys" : ["aerialway"],
"values" : {
"pylon" : "skip",
"no" : "skip",
"" : "main"
}
},
{
"keys" : ["boundary"],
"values" : {
"" : "main,with_name"
}
},
{
"keys" : ["amenity"],
"values" : {
"restaurant" : "main,operator",
"fuel" : "main,operator"
}
},
{
"keys" : ["aeroway", "amenity", "club", "craft", "leisure",
"office", "mountain_pass"],
"values" : {
"no" : "skip",
"" : "main"
}
},
{
"keys" : ["shop"],
"values" : {
"no" : "skip",
"" : "main,operator"
}
},
{
"keys" : ["tourism"],
"values" : {
"yes" : "skip",
"no" : "skip",
"" : "main,operator"
}
},
{
"keys" : ["bridge", "tunnel"],
"values" : {
"" : "main,with_name_key"
}
},
{
"keys" : ["waterway"],
"values" : {
"riverbank" : "skip",
"" : "main,with_name"
}
},
{
"keys" : ["place"],
"values" : {
"" : "main"
}
},
{
"keys" : ["junction"],
"values" : {
"" : "main,fallback,with_name"
}
},
{
"keys" : ["postal_code", "postcode", "addr:postcode",
"tiger:zip_left", "tiger:zip_right"],
"values" : {
"" : "postcode,fallback"
}
},
{
"keys" : ["country_code", "ISO3166-1", "is_in:country_code", "is_in_country",
"addr:country", "addr:country", "addr:country_code"],
"values" : {
"" : "country"
}
},
{
"keys" : ["addr:housenumber", "addr:conscriptionnumber", "addr:streetnumber"],
"values" : {
"" : "address,house"
}
},
{
"keys" : ["addr:interpolation"],
"values" : {
"" : "interpolation,address"
}
},
{
"keys" : ["addr:*", "is_in:*", "tiger:county", "is_in"],
"values" : {
"" : "address"
}
},
{
"keys" : ["building"],
"values" : {
"no" : "skip",
"" : "main,fallback,with_name"
}
},
{
"keys" : ["tracktype", "traffic_calming", "service", "cuisine", "capital",
"dispensing", "religion", "denomination", "sport",
"internet_access", "lanes", "surface", "smoothness", "width",
"est_width", "incline", "opening_hours", "collection_times",
"service_times", "disused", "wheelchair", "sac_scale",
"trail_visibility", "mtb:scale", "mtb:description", "wood",
"drive_through", "drive_in", "access", "vehicle", "bicyle",
"foot", "goods", "hgv", "motor_vehicle", "motor_car", "oneway",
"date_on", "date_off", "day_on", "day_off", "hour_on", "hour_off",
"maxweight", "maxheight", "maxspeed", "fee", "toll", "charge",
"population", "description", "image", "attribution", "fax",
"email", "url", "website", "phone", "real_ale", "smoking",
"food", "camera", "brewery", "locality", "wikipedia",
"wikipedia:*", "access:*", "contact:*", "drink:*", "toll:*"],
"values" : {
"" : "extra"
}
}
]

View File

@@ -1,85 +0,0 @@
[
{
"keys" : ["name:prefix", "name:suffix", "name:botanical", "*wikidata"],
"values" : {
"" : "skip"
}
},
{
"keys" : ["ref", "int_ref", "nat_ref", "reg_ref", "loc_ref", "old_ref",
"iata", "icao", "pcode"],
"values" : {
"" : "ref"
}
},
{
"keys" : ["name", "name:*", "int_name", "int_name:*", "nat_name", "nat_name:*",
"reg_name", "reg_name:*", "loc_name", "loc_name:*",
"old_name", "old_name:*", "alt_name", "alt_name:*", "alt_name_*",
"official_name", "official_name:*", "place_name", "place_name:*",
"short_name", "short_name:*", "brand"],
"values" : {
"" : "name"
}
},
{
"keys" : ["landuse"],
"values" : {
"cemetry" : "skip",
"" : "fallback,with_name"
}
},
{
"keys" : ["boundary"],
"values" : {
"administrative" : "main"
}
},
{
"keys" : ["place"],
"values" : {
"" : "main"
}
},
{
"keys" : ["country_code", "ISO3166-1", "is_in:country_code", "is_in_country",
"addr:country", "addr:country", "addr:country_code"],
"values" : {
"" : "country"
}
},
{
"keys" : ["addr:*", "is_in:*", "tiger:county"],
"values" : {
"" : "address"
}
},
{
"keys" : ["highway"],
"values" : {
"motorway" : "main",
"trunk" : "main",
"primary" : "main",
"secondary" : "main",
"tertiary" : "main",
"unclassified" : "main",
"residential" : "main",
"living_street" : "main",
"pedestrian" : "main",
"road" : "main",
"service" : "main,with_name",
"cycleway" : "main,with_name",
"path" : "main,with_name",
"footway" : "main,with_name",
"steps" : "main,with_name",
"bridleway" : "main,with_name",
"track" : "main,with_name",
"byway": "main,with_name",
"motorway_link" : "main,with_name",
"trunk_link" : "main,with_name",
"primary_link" : "main,with_name",
"secondary_link" : "main,with_name",
"tertiary_link" : "main,with_name"
}
}
]

4
settings/settings.php Normal file
View File

@@ -0,0 +1,4 @@
<?php
echo "ERROR: Scripts must be run from build directory.\n";
exit;

View File

@@ -561,6 +561,14 @@ BEGIN
RETURN nearcountry.country_code; RETURN nearcountry.country_code;
END LOOP; END LOOP;
-- RAISE WARNING 'natural earth: %', ST_AsText(place_centre);
-- Natural earth data
FOR nearcountry IN select country_code from country_naturalearthdata where st_covers(geometry, place_centre) limit 1
LOOP
RETURN nearcountry.country_code;
END LOOP;
-- RAISE WARNING 'near osm fallback: %', ST_AsText(place_centre); -- RAISE WARNING 'near osm fallback: %', ST_AsText(place_centre);
-- --
@@ -569,6 +577,14 @@ BEGIN
RETURN nearcountry.country_code; RETURN nearcountry.country_code;
END LOOP; END LOOP;
-- RAISE WARNING 'near natural earth: %', ST_AsText(place_centre);
-- Natural earth data
FOR nearcountry IN select country_code from country_naturalearthdata where st_dwithin(geometry, place_centre, 0.5) limit 1
LOOP
RETURN nearcountry.country_code;
END LOOP;
RETURN NULL; RETURN NULL;
END; END;
$$ $$
@@ -801,12 +817,11 @@ DECLARE
i INTEGER; i INTEGER;
postcode TEXT; postcode TEXT;
result BOOLEAN; result BOOLEAN;
is_area BOOLEAN;
country_code VARCHAR(2); country_code VARCHAR(2);
default_language VARCHAR(10); default_language VARCHAR(10);
diameter FLOAT; diameter FLOAT;
classtable TEXT; classtable TEXT;
classtype TEXT; line RECORD;
BEGIN BEGIN
--DEBUG: RAISE WARNING '% % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type; --DEBUG: RAISE WARNING '% % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
@@ -833,74 +848,144 @@ BEGIN
IF NEW.osm_type = 'X' THEN IF NEW.osm_type = 'X' THEN
-- E'X'ternal records should already be in the right format so do nothing -- E'X'ternal records should already be in the right format so do nothing
ELSE ELSE
is_area := ST_GeometryType(NEW.geometry) IN ('ST_Polygon','ST_MultiPolygon'); NEW.rank_search := 30;
NEW.rank_address := NEW.rank_search;
IF NEW.class in ('place','boundary') -- By doing in postgres we have the country available to us - currently only used for postcode
AND NEW.type in ('postcode','postal_code') THEN IF NEW.class in ('place','boundary') AND NEW.type in ('postcode','postal_code') THEN
IF NEW.address IS NULL OR NOT NEW.address ? 'postcode' THEN IF NEW.address IS NULL OR NOT NEW.address ? 'postcode' THEN
-- most likely just a part of a multipolygon postcode boundary, throw it away -- most likely just a part of a multipolygon postcode boundary, throw it away
RETURN NULL; RETURN NULL;
END IF; END IF;
NEW.name := hstore('ref', NEW.address->'postcode'); NEW.name := hstore('ref', NEW.address->'postcode');
SELECT * FROM get_postcode_rank(NEW.country_code, NEW.address->'postcode') SELECT * FROM get_postcode_rank(NEW.country_code, NEW.address->'postcode')
INTO NEW.rank_search, NEW.rank_address; INTO NEW.rank_search, NEW.rank_address;
IF NOT is_area THEN IF NOT ST_GeometryType(NEW.geometry) IN ('ST_Polygon','ST_MultiPolygon') THEN
NEW.rank_address := 0; NEW.rank_address := 0;
END IF; END IF;
ELSEIF NEW.class = 'boundary' AND NOT is_area THEN
return NULL;
ELSEIF NEW.class = 'boundary' AND NEW.type = 'administrative'
AND NEW.admin_level <= 4 AND NEW.osm_type = 'W' THEN
return NULL;
ELSEIF NEW.class = 'railway' AND NEW.type in ('rail') THEN
return NULL;
ELSEIF NEW.osm_type = 'N' AND NEW.class = 'highway' THEN
NEW.rank_search = 30;
NEW.rank_address = 0;
ELSEIF NEW.class = 'landuse' AND NOT is_area THEN
NEW.rank_search = 30;
NEW.rank_address = 0;
ELSE
-- do table lookup stuff
IF NEW.class = 'boundary' and NEW.type = 'administrative' THEN
classtype = NEW.type || NEW.admin_level::TEXT;
ELSE
classtype = NEW.type;
END IF;
SELECT l.rank_search, l.rank_address FROM address_levels l
WHERE (l.country_code = NEW.country_code or l.country_code is NULL)
AND l.class = NEW.class AND (l.type = classtype or l.type is NULL)
ORDER BY l.country_code, l.class, l.type LIMIT 1
INTO NEW.rank_search, NEW.rank_address;
IF NEW.rank_search is NULL THEN ELSEIF NEW.class = 'place' THEN
IF NEW.type in ('continent') THEN
NEW.rank_search := 2;
NEW.rank_address := NEW.rank_search;
NEW.country_code := NULL;
ELSEIF NEW.type in ('sea') THEN
NEW.rank_search := 2;
NEW.rank_address := 0;
NEW.country_code := NULL;
ELSEIF NEW.type in ('country') THEN
NEW.rank_search := 4;
NEW.rank_address := NEW.rank_search;
ELSEIF NEW.type in ('state') THEN
NEW.rank_search := 8;
NEW.rank_address := NEW.rank_search;
ELSEIF NEW.type in ('region') THEN
NEW.rank_search := 18; -- dropped from previous value of 10
NEW.rank_address := 0; -- So badly miss-used that better to just drop it!
ELSEIF NEW.type in ('county') THEN
NEW.rank_search := 12;
NEW.rank_address := NEW.rank_search;
ELSEIF NEW.type in ('city') THEN
NEW.rank_search := 16;
NEW.rank_address := NEW.rank_search;
ELSEIF NEW.type in ('island') THEN
NEW.rank_search := 17;
NEW.rank_address := 0;
ELSEIF NEW.type in ('town') THEN
NEW.rank_search := 18;
NEW.rank_address := 16;
ELSEIF NEW.type in ('village','hamlet','municipality','district','unincorporated_area','borough') THEN
NEW.rank_search := 19;
NEW.rank_address := 16;
ELSEIF NEW.type in ('suburb','croft','subdivision','isolated_dwelling') THEN
NEW.rank_search := 20;
NEW.rank_address := NEW.rank_search;
ELSEIF NEW.type in ('farm','locality','islet','mountain_pass') THEN
NEW.rank_search := 20;
NEW.rank_address := 0;
-- Irish townlands, tagged as place=locality and locality=townland
IF (NEW.extratags -> 'locality') = 'townland' THEN
NEW.rank_address := 20;
END IF;
ELSEIF NEW.type in ('neighbourhood') THEN
NEW.rank_search := 22;
NEW.rank_address := 22;
ELSEIF NEW.type in ('house','building') THEN
NEW.rank_search := 30; NEW.rank_search := 30;
END IF; NEW.rank_address := NEW.rank_search;
ELSEIF NEW.type in ('houses') THEN
IF NEW.rank_address is NULL THEN -- can't guarantee all required nodes loaded yet due to caching in osm2pgsql
NEW.rank_address := 30; NEW.rank_search := 28;
END IF;
END IF;
-- some postcorrections
IF NEW.class = 'place' THEN
IF NEW.type in ('continent', 'sea', 'country', 'state') AND NEW.osm_type = 'N' THEN
NEW.rank_address := 0; NEW.rank_address := 0;
END IF; END IF;
ELSEIF NEW.class = 'waterway' AND NEW.osm_type = 'R' THEN
-- Slightly promote waterway relations so that they are processed ELSEIF NEW.class = 'boundary' THEN
-- before their members. IF ST_GeometryType(NEW.geometry) NOT IN ('ST_Polygon','ST_MultiPolygon') THEN
NEW.rank_search := NEW.rank_search - 1; -- RAISE WARNING 'invalid boundary %',NEW.osm_id;
return NULL;
END IF;
NEW.rank_search := NEW.admin_level * 2;
IF NEW.type = 'administrative' THEN
NEW.rank_address := NEW.rank_search;
ELSE
NEW.rank_address := 0;
END IF;
ELSEIF NEW.class = 'landuse' AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon') THEN
NEW.rank_search := 22;
IF NEW.type in ('residential', 'farm', 'farmyard', 'industrial', 'commercial', 'allotments', 'retail') THEN
NEW.rank_address := NEW.rank_search;
ELSE
NEW.rank_address := 0;
END IF;
ELSEIF NEW.class = 'leisure' and NEW.type in ('park') THEN
NEW.rank_search := 24;
NEW.rank_address := 0;
ELSEIF NEW.class = 'natural' and NEW.type in ('peak','volcano','mountain_range') THEN
NEW.rank_search := 18;
NEW.rank_address := 0;
ELSEIF NEW.class = 'natural' and NEW.type = 'sea' THEN
NEW.rank_search := 4;
NEW.rank_address := NEW.rank_search;
-- any feature more than 5 square miles is probably worth indexing
ELSEIF ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon') AND ST_Area(NEW.geometry) > 0.1 THEN
NEW.rank_search := 22;
NEW.rank_address := 0;
ELSEIF NEW.class = 'railway' AND NEW.type in ('rail') THEN
RETURN NULL;
ELSEIF NEW.class = 'waterway' THEN
IF NEW.osm_type = 'R' THEN
NEW.rank_search := 16;
ELSE
NEW.rank_search := 17;
END IF;
NEW.rank_address := 0;
ELSEIF NEW.class = 'highway' AND NEW.osm_type != 'N' AND NEW.type in ('service','cycleway','path','footway','steps','bridleway','motorway_link','primary_link','trunk_link','secondary_link','tertiary_link') THEN
NEW.rank_search := 27;
NEW.rank_address := NEW.rank_search;
ELSEIF NEW.class = 'highway' AND NEW.osm_type != 'N' THEN
NEW.rank_search := 26;
NEW.rank_address := NEW.rank_search;
ELSEIF NEW.class = 'mountain_pass' THEN
NEW.rank_search := 20;
NEW.rank_address := 0;
END IF; END IF;
IF (NEW.extratags -> 'capital') = 'yes' THEN END IF;
NEW.rank_search := NEW.rank_search - 1;
END IF;
IF NEW.rank_search > 30 THEN
NEW.rank_search := 30;
END IF;
IF NEW.rank_address > 30 THEN
NEW.rank_address := 30;
END IF;
IF (NEW.extratags -> 'capital') = 'yes' THEN
NEW.rank_search := NEW.rank_search - 1;
END IF; END IF;
-- a country code make no sense below rank 4 (country) -- a country code make no sense below rank 4 (country)
@@ -1221,9 +1306,6 @@ BEGIN
NEW.indexed_date = now(); NEW.indexed_date = now();
IF NOT %REVERSE-ONLY% THEN
DELETE from search_name WHERE place_id = NEW.place_id;
END IF;
result := deleteSearchName(NEW.partition, NEW.place_id); result := deleteSearchName(NEW.partition, NEW.place_id);
DELETE FROM place_addressline WHERE place_id = NEW.place_id; DELETE FROM place_addressline WHERE place_id = NEW.place_id;
result := deleteRoad(NEW.partition, NEW.place_id); result := deleteRoad(NEW.partition, NEW.place_id);
@@ -1311,6 +1393,10 @@ BEGIN
--DEBUG: RAISE WARNING 'Waterway processed'; --DEBUG: RAISE WARNING 'Waterway processed';
END IF; END IF;
-- Adding ourselves to the list simplifies address calculations later
INSERT INTO place_addressline (place_id, address_place_id, fromarea, isaddress, distance, cached_rank_address)
VALUES (NEW.place_id, NEW.place_id, true, true, 0, NEW.rank_address);
-- What level are we searching from -- What level are we searching from
search_maxrank := NEW.rank_search; search_maxrank := NEW.rank_search;
@@ -1486,9 +1572,8 @@ BEGIN
IF NEW.parent_place_id IS NOT NULL THEN IF NEW.parent_place_id IS NOT NULL THEN
-- Get the details of the parent road -- Get the details of the parent road
SELECT p.country_code, p.postcode FROM placex p select s.country_code, s.name_vector, s.nameaddress_vector from search_name s
WHERE p.place_id = NEW.parent_place_id INTO location; where s.place_id = NEW.parent_place_id INTO location;
NEW.country_code := location.country_code; NEW.country_code := location.country_code;
--DEBUG: RAISE WARNING 'Got parent details from search name'; --DEBUG: RAISE WARNING 'Got parent details from search name';
@@ -1497,7 +1582,7 @@ BEGIN
IF NEW.address is not null AND NEW.address ? 'postcode' THEN IF NEW.address is not null AND NEW.address ? 'postcode' THEN
NEW.postcode = upper(trim(NEW.address->'postcode')); NEW.postcode = upper(trim(NEW.address->'postcode'));
ELSE ELSE
NEW.postcode := location.postcode; SELECT postcode FROM placex WHERE place_id = NEW.parent_place_id INTO NEW.postcode;
END IF; END IF;
IF NEW.postcode is null THEN IF NEW.postcode is null THEN
NEW.postcode := get_nearest_postcode(NEW.country_code, place_centroid); NEW.postcode := get_nearest_postcode(NEW.country_code, place_centroid);
@@ -1510,34 +1595,21 @@ BEGIN
return NEW; return NEW;
END IF; END IF;
-- Merge address from parent
nameaddress_vector := array_merge(nameaddress_vector, location.nameaddress_vector);
nameaddress_vector := array_merge(nameaddress_vector, location.name_vector);
-- Performance, it would be more acurate to do all the rest of the import -- Performance, it would be more acurate to do all the rest of the import
-- process but it takes too long -- process but it takes too long
-- Just be happy with inheriting from parent road only -- Just be happy with inheriting from parent road only
IF NEW.rank_search <= 25 and NEW.rank_address > 0 THEN IF NEW.rank_search <= 25 and NEW.rank_address > 0 THEN
result := add_location(NEW.place_id, NEW.country_code, NEW.partition, name_vector, NEW.rank_search, NEW.rank_address, upper(trim(NEW.address->'postcode')), NEW.geometry); result := add_location(NEW.place_id, NEW.country_code, NEW.partition, name_vector, NEW.rank_search, NEW.rank_address, upper(trim(NEW.address->'postcode')), NEW.geometry);
--DEBUG: RAISE WARNING 'Place added to location table'; --DEBUG: RAISE WARNING 'Place added to location table';
END IF; END IF;
result := insertSearchName(NEW.partition, NEW.place_id, name_vector, result := insertSearchName(NEW.partition, NEW.place_id, NEW.country_code, name_vector, nameaddress_vector, NEW.rank_search, NEW.rank_address, NEW.importance, place_centroid, NEW.geometry);
NEW.rank_search, NEW.rank_address, NEW.geometry); --DEBUG: RAISE WARNING 'Place added to search table';
IF NOT %REVERSE-ONLY% THEN
-- Merge address from parent
SELECT s.name_vector, s.nameaddress_vector FROM search_name s
WHERE s.place_id = NEW.parent_place_id INTO location;
nameaddress_vector := array_merge(nameaddress_vector,
location.nameaddress_vector);
nameaddress_vector := array_merge(nameaddress_vector, location.name_vector);
INSERT INTO search_name (place_id, search_rank, address_rank,
importance, country_code, name_vector,
nameaddress_vector, centroid)
VALUES (NEW.place_id, NEW.rank_search, NEW.rank_address,
NEW.importance, NEW.country_code, name_vector,
nameaddress_vector, place_centroid);
--DEBUG: RAISE WARNING 'Place added to search table';
END IF;
return NEW; return NEW;
END IF; END IF;
@@ -1723,11 +1795,9 @@ BEGIN
IF address_street_word_id IS NOT NULL AND NOT(ARRAY[address_street_word_id] <@ isin_tokens) THEN IF address_street_word_id IS NOT NULL AND NOT(ARRAY[address_street_word_id] <@ isin_tokens) THEN
isin_tokens := isin_tokens || address_street_word_id; isin_tokens := isin_tokens || address_street_word_id;
END IF; END IF;
IF NOT %REVERSE-ONLY% THEN address_street_word_id := get_word_id(make_standard_name(addr_item.value));
address_street_word_id := get_word_id(make_standard_name(addr_item.value)); IF address_street_word_id IS NOT NULL THEN
IF address_street_word_id IS NOT NULL THEN nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]);
nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]);
END IF;
END IF; END IF;
END IF; END IF;
IF addr_item.key = 'is_in' THEN IF addr_item.key = 'is_in' THEN
@@ -1741,20 +1811,16 @@ BEGIN
END IF; END IF;
-- merge word into address vector -- merge word into address vector
IF NOT %REVERSE-ONLY% THEN address_street_word_id := get_word_id(make_standard_name(isin[i]));
address_street_word_id := get_word_id(make_standard_name(isin[i])); IF address_street_word_id IS NOT NULL THEN
IF address_street_word_id IS NOT NULL THEN nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]);
nameaddress_vector := array_merge(nameaddress_vector, ARRAY[address_street_word_id]);
END IF;
END IF; END IF;
END LOOP; END LOOP;
END IF; END IF;
END IF; END IF;
END LOOP; END LOOP;
END IF; END IF;
IF NOT %REVERSE-ONLY% THEN nameaddress_vector := array_merge(nameaddress_vector, isin_tokens);
nameaddress_vector := array_merge(nameaddress_vector, isin_tokens);
END IF;
-- RAISE WARNING 'ISIN: %', isin_tokens; -- RAISE WARNING 'ISIN: %', isin_tokens;
@@ -1803,7 +1869,7 @@ BEGIN
-- RAISE WARNING '% isaddress: %', location.place_id, location_isaddress; -- RAISE WARNING '% isaddress: %', location.place_id, location_isaddress;
-- Add it to the list of search terms -- Add it to the list of search terms
IF NOT %REVERSE-ONLY% AND location.rank_search > 4 THEN IF location.rank_search > 4 THEN
nameaddress_vector := array_merge(nameaddress_vector, location.keywords::integer[]); nameaddress_vector := array_merge(nameaddress_vector, location.keywords::integer[]);
END IF; END IF;
INSERT INTO place_addressline (place_id, address_place_id, fromarea, isaddress, distance, cached_rank_address) INSERT INTO place_addressline (place_id, address_place_id, fromarea, isaddress, distance, cached_rank_address)
@@ -1857,18 +1923,8 @@ BEGIN
--DEBUG: RAISE WARNING 'insert into road location table (full)'; --DEBUG: RAISE WARNING 'insert into road location table (full)';
END IF; END IF;
result := insertSearchName(NEW.partition, NEW.place_id, name_vector, result := insertSearchName(NEW.partition, NEW.place_id, NEW.country_code, name_vector, nameaddress_vector, NEW.rank_search, NEW.rank_address, NEW.importance, place_centroid, NEW.geometry);
NEW.rank_search, NEW.rank_address, NEW.geometry); --DEBUG: RAISE WARNING 'added to serach name (full)';
--DEBUG: RAISE WARNING 'added to search name (full)';
IF NOT %REVERSE-ONLY% THEN
INSERT INTO search_name (place_id, search_rank, address_rank,
importance, country_code, name_vector,
nameaddress_vector, centroid)
VALUES (NEW.place_id, NEW.rank_search, NEW.rank_address,
NEW.importance, NEW.country_code, name_vector,
nameaddress_vector, place_centroid);
END IF;
END IF; END IF;
@@ -1927,9 +1983,6 @@ BEGIN
--DEBUG: RAISE WARNING 'placex_delete:09 % %',OLD.osm_type,OLD.osm_id; --DEBUG: RAISE WARNING 'placex_delete:09 % %',OLD.osm_type,OLD.osm_id;
IF OLD.name is not null THEN IF OLD.name is not null THEN
IF NOT %REVERSE-ONLY% THEN
DELETE from search_name WHERE place_id = OLD.place_id;
END IF;
b := deleteSearchName(OLD.partition, OLD.place_id); b := deleteSearchName(OLD.partition, OLD.place_id);
END IF; END IF;
@@ -2301,9 +2354,6 @@ create type addressline as (
distance FLOAT distance FLOAT
); );
-- Compute the list of address parts for the given place.
--
-- If in_housenumber is greator or equal 0, look for an interpolation.
CREATE OR REPLACE FUNCTION get_addressdata(in_place_id BIGINT, in_housenumber INTEGER) RETURNS setof addressline CREATE OR REPLACE FUNCTION get_addressdata(in_place_id BIGINT, in_housenumber INTEGER) RETURNS setof addressline
AS $$ AS $$
DECLARE DECLARE
@@ -2318,72 +2368,53 @@ DECLARE
searchhousename HSTORE; searchhousename HSTORE;
searchrankaddress INTEGER; searchrankaddress INTEGER;
searchpostcode TEXT; searchpostcode TEXT;
postcode_isaddress BOOL;
searchclass TEXT; searchclass TEXT;
searchtype TEXT; searchtype TEXT;
countryname HSTORE; countryname HSTORE;
hadcountry BOOLEAN;
BEGIN BEGIN
-- The place ein question might not have a direct entry in place_addressline.
-- Look for the parent of such places then and save if in for_place_id.
postcode_isaddress := true;
-- first query osmline (interpolation lines) -- first query osmline (interpolation lines)
IF in_housenumber >= 0 THEN select parent_place_id, country_code, 30, postcode, null, 'place', 'house' from location_property_osmline
SELECT parent_place_id, country_code, in_housenumber::text, 30, postcode, WHERE place_id = in_place_id AND in_housenumber>=startnumber AND in_housenumber <= endnumber
null, 'place', 'house' INTO for_place_id,searchcountrycode, searchrankaddress, searchpostcode, searchhousename, searchclass, searchtype;
FROM location_property_osmline IF for_place_id IS NOT NULL THEN
WHERE place_id = in_place_id AND in_housenumber>=startnumber searchhousenumber = in_housenumber::text;
AND in_housenumber <= endnumber
INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress,
searchpostcode, searchhousename, searchclass, searchtype;
END IF; END IF;
--then query tiger data --then query tiger data
-- %NOTIGERDATA% IF 0 THEN -- %NOTIGERDATA% IF 0 THEN
IF for_place_id IS NULL AND in_housenumber >= 0 THEN IF for_place_id IS NULL THEN
SELECT parent_place_id, 'us', in_housenumber::text, 30, postcode, null, select parent_place_id,'us', 30, postcode, null, 'place', 'house' from location_property_tiger
'place', 'house' WHERE place_id = in_place_id AND in_housenumber>=startnumber AND in_housenumber <= endnumber
FROM location_property_tiger INTO for_place_id,searchcountrycode, searchrankaddress, searchpostcode, searchhousename, searchclass, searchtype;
WHERE place_id = in_place_id AND in_housenumber >= startnumber IF for_place_id IS NOT NULL THEN
AND in_housenumber <= endnumber searchhousenumber = in_housenumber::text;
INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress, END IF;
searchpostcode, searchhousename, searchclass, searchtype;
END IF; END IF;
-- %NOTIGERDATA% END IF; -- %NOTIGERDATA% END IF;
-- %NOAUXDATA% IF 0 THEN -- %NOAUXDATA% IF 0 THEN
IF for_place_id IS NULL THEN IF for_place_id IS NULL THEN
SELECT parent_place_id, 'us', housenumber, 30, postcode, null, 'place', 'house' select parent_place_id,'us', housenumber, 30, postcode, null, 'place', 'house' from location_property_aux
FROM location_property_aux
WHERE place_id = in_place_id WHERE place_id = in_place_id
INTO for_place_id,searchcountrycode, searchhousenumber, searchrankaddress, INTO for_place_id,searchcountrycode, searchhousenumber, searchrankaddress, searchpostcode, searchhousename, searchclass, searchtype;
searchpostcode, searchhousename, searchclass, searchtype;
END IF; END IF;
-- %NOAUXDATA% END IF; -- %NOAUXDATA% END IF;
-- postcode table -- postcode table
IF for_place_id IS NULL THEN IF for_place_id IS NULL THEN
SELECT parent_place_id, country_code, rank_address, postcode, 'place', 'postcode' select parent_place_id, country_code, rank_address, postcode, 'place', 'postcode'
FROM location_postcode FROM location_postcode
WHERE place_id = in_place_id WHERE place_id = in_place_id
INTO for_place_id, searchcountrycode, searchrankaddress, searchpostcode, INTO for_place_id, searchcountrycode, searchrankaddress, searchpostcode, searchclass, searchtype;
searchclass, searchtype;
END IF; END IF;
-- POI objects in the placex table
IF for_place_id IS NULL THEN IF for_place_id IS NULL THEN
SELECT parent_place_id, country_code, housenumber, rank_search, postcode, select parent_place_id, country_code, housenumber, rank_search, postcode, name, class, type from placex
name, class, type WHERE place_id = in_place_id and rank_search > 27
FROM placex INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress, searchpostcode, searchhousename, searchclass, searchtype;
WHERE place_id = in_place_id and rank_search > 27
INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress,
searchpostcode, searchhousename, searchclass, searchtype;
END IF; END IF;
-- If for_place_id is still NULL at this point then the object has its own
-- entry in place_address line. However, still check if there is not linked
-- place we should be using instead.
IF for_place_id IS NULL THEN IF for_place_id IS NULL THEN
select coalesce(linked_place_id, place_id), country_code, select coalesce(linked_place_id, place_id), country_code,
housenumber, rank_search, postcode, null housenumber, rank_search, postcode, null
@@ -2393,105 +2424,103 @@ BEGIN
--RAISE WARNING '% % % %',searchcountrycode, searchhousenumber, searchrankaddress, searchpostcode; --RAISE WARNING '% % % %',searchcountrycode, searchhousenumber, searchrankaddress, searchpostcode;
found := 1000; -- the lowest rank_address included found := 1000;
hadcountry := false;
-- Return the record for the base entry. FOR location IN
FOR location IN select placex.place_id, osm_type, osm_id, name,
SELECT placex.place_id, osm_type, osm_id, name, class, type, admin_level, true as isaddress,
class, type, admin_level, CASE WHEN rank_address = 0 THEN 100 WHEN rank_address = 11 THEN 5 ELSE rank_address END as rank_address,
type not in ('postcode', 'postal_code') as isaddress, 0 as distance, country_code, postcode
CASE WHEN rank_address = 0 THEN 100 from placex
WHEN rank_address = 11 THEN 5 where place_id = for_place_id
ELSE rank_address END as rank_address,
0 as distance, country_code, postcode
FROM placex
WHERE place_id = for_place_id
LOOP
--RAISE WARNING '%',location;
IF searchcountrycode IS NULL AND location.country_code IS NOT NULL THEN
searchcountrycode := location.country_code;
END IF;
IF location.rank_address < 4 THEN
-- no country locations for ranks higher than country
searchcountrycode := NULL;
END IF;
countrylocation := ROW(location.place_id, location.osm_type, location.osm_id,
location.name, location.class, location.type,
location.admin_level, true, location.isaddress,
location.rank_address, location.distance)::addressline;
RETURN NEXT countrylocation;
found := location.rank_address;
END LOOP;
FOR location IN
SELECT placex.place_id, osm_type, osm_id, name,
CASE WHEN extratags ? 'place' THEN 'place' ELSE class END as class,
CASE WHEN extratags ? 'place' THEN extratags->'place' ELSE type END as type,
admin_level, fromarea, isaddress,
CASE WHEN rank_address = 11 THEN 5 ELSE rank_address END as rank_address,
distance, country_code, postcode
FROM place_addressline join placex on (address_place_id = placex.place_id)
WHERE place_addressline.place_id = for_place_id
AND (cached_rank_address >= 4 AND cached_rank_address < searchrankaddress)
AND linked_place_id is null
AND (placex.country_code IS NULL OR searchcountrycode IS NULL
OR placex.country_code = searchcountrycode)
ORDER BY rank_address desc, isaddress desc, fromarea desc,
distance asc, rank_search desc
LOOP LOOP
--RAISE WARNING '%',location; --RAISE WARNING '%',location;
IF searchcountrycode IS NULL AND location.country_code IS NOT NULL THEN IF searchcountrycode IS NULL AND location.country_code IS NOT NULL THEN
searchcountrycode := location.country_code; searchcountrycode := location.country_code;
END IF; END IF;
IF location.type in ('postcode', 'postal_code') THEN IF location.type in ('postcode', 'postal_code') THEN
postcode_isaddress := false; location.isaddress := FALSE;
IF location.osm_type != 'R' THEN ELSEIF location.rank_address = 4 THEN
location.isaddress := FALSE; hadcountry := true;
END IF;
IF location.rank_address < 4 AND NOT hadcountry THEN
select name from country_name where country_code = searchcountrycode limit 1 INTO countryname;
IF countryname IS NOT NULL THEN
countrylocation := ROW(null, null, null, countryname, 'place', 'country', null, true, true, 4, 0)::addressline;
RETURN NEXT countrylocation;
END IF; END IF;
END IF; END IF;
countrylocation := ROW(location.place_id, location.osm_type, location.osm_id, countrylocation := ROW(location.place_id, location.osm_type, location.osm_id, location.name, location.class,
location.name, location.class, location.type, location.type, location.admin_level, true, location.isaddress, location.rank_address,
location.admin_level, location.fromarea, location.distance)::addressline;
location.isaddress, location.rank_address, RETURN NEXT countrylocation;
found := location.rank_address;
END LOOP;
FOR location IN
select placex.place_id, osm_type, osm_id, name,
CASE WHEN extratags ? 'place' THEN 'place' ELSE class END as class,
CASE WHEN extratags ? 'place' THEN extratags->'place' ELSE type END as type,
admin_level, fromarea, isaddress,
CASE WHEN address_place_id = for_place_id AND rank_address = 0 THEN 100 WHEN rank_address = 11 THEN 5 ELSE rank_address END as rank_address,
distance,country_code,postcode
from place_addressline join placex on (address_place_id = placex.place_id)
where place_addressline.place_id = for_place_id
and (cached_rank_address > 0 AND cached_rank_address < searchrankaddress)
and address_place_id != for_place_id and linked_place_id is null
and (placex.country_code IS NULL OR searchcountrycode IS NULL OR placex.country_code = searchcountrycode)
order by rank_address desc,isaddress desc,fromarea desc,distance asc,rank_search desc
LOOP
--RAISE WARNING '%',location;
IF searchcountrycode IS NULL AND location.country_code IS NOT NULL THEN
searchcountrycode := location.country_code;
END IF;
IF location.type in ('postcode', 'postal_code') THEN
location.isaddress := FALSE;
END IF;
IF location.rank_address = 4 AND location.isaddress THEN
hadcountry := true;
END IF;
IF location.rank_address < 4 AND NOT hadcountry THEN
select name from country_name where country_code = searchcountrycode limit 1 INTO countryname;
IF countryname IS NOT NULL THEN
countrylocation := ROW(null, null, null, countryname, 'place', 'country', null, true, true, 4, 0)::addressline;
RETURN NEXT countrylocation;
END IF;
END IF;
countrylocation := ROW(location.place_id, location.osm_type, location.osm_id, location.name, location.class,
location.type, location.admin_level, location.fromarea, location.isaddress, location.rank_address,
location.distance)::addressline; location.distance)::addressline;
RETURN NEXT countrylocation; RETURN NEXT countrylocation;
found := location.rank_address; found := location.rank_address;
END LOOP; END LOOP;
-- If no country was included yet, add the name information from country_name.
IF found > 4 THEN IF found > 4 THEN
SELECT name FROM country_name select name from country_name where country_code = searchcountrycode limit 1 INTO countryname;
WHERE country_code = searchcountrycode LIMIT 1 INTO countryname;
--RAISE WARNING '% % %',found,searchcountrycode,countryname; --RAISE WARNING '% % %',found,searchcountrycode,countryname;
IF countryname IS NOT NULL THEN IF countryname IS NOT NULL THEN
location := ROW(null, null, null, countryname, 'place', 'country', location := ROW(null, null, null, countryname, 'place', 'country', null, true, true, 4, 0)::addressline;
null, true, true, 4, 0)::addressline;
RETURN NEXT location; RETURN NEXT location;
END IF; END IF;
END IF; END IF;
-- Finally add some artificial rows.
IF searchcountrycode IS NOT NULL THEN IF searchcountrycode IS NOT NULL THEN
location := ROW(null, null, null, hstore('ref', searchcountrycode), location := ROW(null, null, null, hstore('ref', searchcountrycode), 'place', 'country_code', null, true, false, 4, 0)::addressline;
'place', 'country_code', null, true, false, 4, 0)::addressline;
RETURN NEXT location; RETURN NEXT location;
END IF; END IF;
IF searchhousename IS NOT NULL THEN IF searchhousename IS NOT NULL THEN
location := ROW(in_place_id, null, null, searchhousename, searchclass, location := ROW(in_place_id, null, null, searchhousename, searchclass, searchtype, null, true, true, 29, 0)::addressline;
searchtype, null, true, true, 29, 0)::addressline;
RETURN NEXT location; RETURN NEXT location;
END IF; END IF;
IF searchhousenumber IS NOT NULL THEN IF searchhousenumber IS NOT NULL THEN
location := ROW(in_place_id, null, null, hstore('ref', searchhousenumber), location := ROW(in_place_id, null, null, hstore('ref', searchhousenumber), 'place', 'house_number', null, true, true, 28, 0)::addressline;
'place', 'house_number', null, true, true, 28, 0)::addressline;
RETURN NEXT location; RETURN NEXT location;
END IF; END IF;
IF searchpostcode IS NOT NULL THEN IF searchpostcode IS NOT NULL THEN
location := ROW(null, null, null, hstore('ref', searchpostcode), 'place', location := ROW(null, null, null, hstore('ref', searchpostcode), 'place', 'postcode', null, true, true, 5, 0)::addressline;
'postcode', null, false, postcode_isaddress, 5, 0)::addressline;
RETURN NEXT location; RETURN NEXT location;
END IF; END IF;
@@ -2501,6 +2530,96 @@ $$
LANGUAGE plpgsql; LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION get_searchrank_label(rank INTEGER) RETURNS TEXT
AS $$
DECLARE
BEGIN
IF rank < 2 THEN
RETURN 'Continent';
ELSEIF rank < 4 THEN
RETURN 'Sea';
ELSEIF rank < 8 THEN
RETURN 'Country';
ELSEIF rank < 12 THEN
RETURN 'State';
ELSEIF rank < 16 THEN
RETURN 'County';
ELSEIF rank = 16 THEN
RETURN 'City';
ELSEIF rank = 17 THEN
RETURN 'Town / Island';
ELSEIF rank = 18 THEN
RETURN 'Village / Hamlet';
ELSEIF rank = 20 THEN
RETURN 'Suburb';
ELSEIF rank = 21 THEN
RETURN 'Postcode Area';
ELSEIF rank = 22 THEN
RETURN 'Croft / Farm / Locality / Islet';
ELSEIF rank = 23 THEN
RETURN 'Postcode Area';
ELSEIF rank = 25 THEN
RETURN 'Postcode Point';
ELSEIF rank = 26 THEN
RETURN 'Street / Major Landmark';
ELSEIF rank = 27 THEN
RETURN 'Minory Street / Path';
ELSEIF rank = 28 THEN
RETURN 'House / Building';
ELSE
RETURN 'Other: '||rank;
END IF;
END;
$$
LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION get_addressrank_label(rank INTEGER) RETURNS TEXT
AS $$
DECLARE
BEGIN
IF rank = 0 THEN
RETURN 'None';
ELSEIF rank < 2 THEN
RETURN 'Continent';
ELSEIF rank < 4 THEN
RETURN 'Sea';
ELSEIF rank = 5 THEN
RETURN 'Postcode';
ELSEIF rank < 8 THEN
RETURN 'Country';
ELSEIF rank < 12 THEN
RETURN 'State';
ELSEIF rank < 16 THEN
RETURN 'County';
ELSEIF rank = 16 THEN
RETURN 'City';
ELSEIF rank = 17 THEN
RETURN 'Town / Village / Hamlet';
ELSEIF rank = 20 THEN
RETURN 'Suburb';
ELSEIF rank = 21 THEN
RETURN 'Postcode Area';
ELSEIF rank = 22 THEN
RETURN 'Croft / Farm / Locality / Islet';
ELSEIF rank = 23 THEN
RETURN 'Postcode Area';
ELSEIF rank = 25 THEN
RETURN 'Postcode Point';
ELSEIF rank = 26 THEN
RETURN 'Street / Major Landmark';
ELSEIF rank = 27 THEN
RETURN 'Minory Street / Path';
ELSEIF rank = 28 THEN
RETURN 'House / Building';
ELSE
RETURN 'Other: '||rank;
END IF;
END;
$$
LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION aux_create_property(pointgeo GEOMETRY, in_housenumber TEXT, CREATE OR REPLACE FUNCTION aux_create_property(pointgeo GEOMETRY, in_housenumber TEXT,
in_street TEXT, in_isin TEXT, in_postcode TEXT, in_countrycode char(2)) RETURNS INTEGER in_street TEXT, in_isin TEXT, in_postcode TEXT, in_countrycode char(2)) RETURNS INTEGER
AS $$ AS $$

View File

@@ -3,6 +3,10 @@
CREATE INDEX idx_word_word_id on word USING BTREE (word_id) {ts:search-index}; CREATE INDEX idx_word_word_id on word USING BTREE (word_id) {ts:search-index};
CREATE INDEX idx_search_name_nameaddress_vector ON search_name USING GIN (nameaddress_vector) WITH (fastupdate = off) {ts:search-index};
CREATE INDEX idx_search_name_name_vector ON search_name USING GIN (name_vector) WITH (fastupdate = off) {ts:search-index};
CREATE INDEX idx_search_name_centroid ON search_name USING GIST (centroid) {ts:search-index};
CREATE INDEX idx_place_addressline_address_place_id on place_addressline USING BTREE (address_place_id) {ts:search-index}; CREATE INDEX idx_place_addressline_address_place_id on place_addressline USING BTREE (address_place_id) {ts:search-index};
DROP INDEX IF EXISTS idx_placex_rank_search; DROP INDEX IF EXISTS idx_placex_rank_search;
@@ -32,7 +36,6 @@ GRANT SELECT ON table country_osm_grid to "{www-user}";
CREATE INDEX idx_location_area_country_place_id ON location_area_country USING BTREE (place_id) {ts:address-index}; CREATE INDEX idx_location_area_country_place_id ON location_area_country USING BTREE (place_id) {ts:address-index};
CREATE INDEX idx_osmline_parent_place_id ON location_property_osmline USING BTREE (parent_place_id) {ts:search-index}; CREATE INDEX idx_osmline_parent_place_id ON location_property_osmline USING BTREE (parent_place_id) {ts:search-index};
CREATE INDEX idx_osmline_parent_osm_id ON location_property_osmline USING BTREE (osm_id) {ts:search-index};
DROP INDEX IF EXISTS place_id_idx; DROP INDEX IF EXISTS place_id_idx;
CREATE UNIQUE INDEX idx_place_osm_unique on place using btree(osm_id,osm_type,class,type) {ts:address-index}; CREATE UNIQUE INDEX idx_place_osm_unique on place using btree(osm_id,osm_type,class,type) {ts:address-index};

View File

@@ -1,6 +0,0 @@
-- Indices used for /search API.
-- These indices are created only after the indexing process is done.
CREATE INDEX idx_search_name_nameaddress_vector ON search_name USING GIN (nameaddress_vector) WITH (fastupdate = off) {ts:search-index};
CREATE INDEX idx_search_name_name_vector ON search_name USING GIN (name_vector) WITH (fastupdate = off) {ts:search-index};
CREATE INDEX idx_search_name_centroid ON search_name USING GIST (centroid) {ts:search-index};

View File

@@ -142,11 +142,17 @@ LANGUAGE plpgsql;
create or replace function insertSearchName( create or replace function insertSearchName(
in_partition INTEGER, in_place_id BIGINT, in_name_vector INTEGER[], in_partition INTEGER, in_place_id BIGINT, in_country_code VARCHAR(2),
in_rank_search INTEGER, in_rank_address INTEGER, in_geometry GEOMETRY) in_name_vector INTEGER[], in_nameaddress_vector INTEGER[],
RETURNS BOOLEAN AS $$ in_rank_search INTEGER, in_rank_address INTEGER, in_importance FLOAT,
in_centroid GEOMETRY, in_geometry GEOMETRY) RETURNS BOOLEAN AS $$
DECLARE DECLARE
BEGIN BEGIN
DELETE FROM search_name WHERE place_id = in_place_id;
INSERT INTO search_name (place_id, search_rank, address_rank, importance, country_code, name_vector, nameaddress_vector, centroid)
values (in_place_id, in_rank_search, in_rank_address, in_importance, in_country_code, in_name_vector, in_nameaddress_vector, in_centroid);
-- start -- start
IF in_partition = -partition- THEN IF in_partition = -partition- THEN
DELETE FROM search_name_-partition- values WHERE place_id = in_place_id; DELETE FROM search_name_-partition- values WHERE place_id = in_place_id;
@@ -167,6 +173,9 @@ LANGUAGE plpgsql;
create or replace function deleteSearchName(in_partition INTEGER, in_place_id BIGINT) RETURNS BOOLEAN AS $$ create or replace function deleteSearchName(in_partition INTEGER, in_place_id BIGINT) RETURNS BOOLEAN AS $$
DECLARE DECLARE
BEGIN BEGIN
DELETE from search_name WHERE place_id = in_place_id;
-- start -- start
IF in_partition = -partition- THEN IF in_partition = -partition- THEN
DELETE from search_name_-partition- WHERE place_id = in_place_id; DELETE from search_name_-partition- WHERE place_id = in_place_id;

View File

@@ -10,7 +10,7 @@ drop table if exists import_osmosis_log;
CREATE TABLE import_osmosis_log ( CREATE TABLE import_osmosis_log (
batchend timestamp, batchend timestamp,
batchseq integer, batchseq integer,
batchsize bigint, batchsize integer,
starttime timestamp, starttime timestamp,
endtime timestamp, endtime timestamp,
event text event text

View File

@@ -48,11 +48,5 @@ INSERT INTO location_postcode
SELECT nextval('seq_place'), 1, country_code, pc, centroid SELECT nextval('seq_place'), 1, country_code, pc, centroid
FROM tmp_new_postcode_locations new; FROM tmp_new_postcode_locations new;
-- Remove unused word entries
DELETE FROM word
WHERE class = 'place' AND type = 'postcode'
AND NOT EXISTS (SELECT 0 FROM location_postcode p
WHERE p.postcode = word.word);
-- Finally index the newly inserted postcodes -- Finally index the newly inserted postcodes
UPDATE location_postcode SET indexed_status = 0 WHERE indexed_status > 0; UPDATE location_postcode SET indexed_status = 0 WHERE indexed_status > 0;

View File

@@ -1,9 +0,0 @@
all: bdd php
bdd:
cd bdd && behave -DREMOVE_TEMPLATE=1
php:
cd php && phpunit ./
.PHONY: bdd php

View File

@@ -66,19 +66,18 @@ To run the functional tests, do
cd test/bdd cd test/bdd
behave behave
The tests can be configured with a set of environment variables (`behave -D key=val`): The tests can be configured with a set of environment variables:
* `BUILDDIR` - build directory of Nominatim installation to test * `BUILD_DIR` - build directory of Nominatim installation to test
* `TEMPLATE_DB` - name of template database used as a skeleton for * `TEMPLATE_DB` - name of template database used as a skeleton for
the test databases (db tests) the test databases (db tests)
* `TEST_DB` - name of test database (db tests) * `TEST_DB` - name of test database (db tests)
* `API_TEST_DB` - name of the database containing the API test data (api tests) * `ABI_TEST_DB` - name of the database containing the API test data (api tests)
* `DB_HOST` - (optional) hostname of database host * `DB_HOST` - (optional) hostname of database host
* `DB_PORT` - (optional) port of database on host
* `DB_USER` - (optional) username of database login * `DB_USER` - (optional) username of database login
* `DB_PASS` - (optional) password for database login * `DB_PASS` - (optional) password for database login
* `SERVER_MODULE_PATH` - (optional) path on the Postgres server to Nominatim * `SERVER_MODULE_PATH` - (optional) path on the Postgres server to Nominatim
module shared library file * module shared library file
* `TEST_SETTINGS_TEMPLATE` - file to write temporary Nominatim settings to * `TEST_SETTINGS_TEMPLATE` - file to write temporary Nominatim settings to
* `REMOVE_TEMPLATE` - if true, the template database will not be reused during * `REMOVE_TEMPLATE` - if true, the template database will not be reused during
the next run. Reusing the base templates speeds up tests the next run. Reusing the base templates speeds up tests
@@ -118,8 +117,8 @@ planets are likely to work as well but you may see isolated test
failures where the data has changed. To recreate the input data failures where the data has changed. To recreate the input data
for the test database run: for the test database run:
wget https://ftp5.gwdg.de/pub/misc/openstreetmap/planet.openstreetmap.org/pbf/planet-180924.osm.pbf wget https://free.nchc.org.tw/osm.planet/pbf/planet-160725.osm.pbf
osmconvert planet-180924.osm.pbf -B=test/testdb/testdb.polys -o=testdb.pbf osmconvert planet-160725.osm.pbf -B=test/testdb/testdb.polys -o=testdb.pbf
Before importing make sure to add the following to your local settings: Before importing make sure to add the following to your local settings:

View File

@@ -34,10 +34,3 @@ Feature: Object details
| 1 | | 1 |
Then the result is valid html Then the result is valid html
# ticket #1343
Scenario: Details of a country with keywords
When sending details query for R287072
| keywords |
| 1 |
Then the result is valid html

View File

@@ -1,13 +0,0 @@
@APIDB
Feature: Places by osm_type and osm_id Tests
Simple tests for errors in various response formats.
Scenario Outline: Force error by providing too many ids
When sending <format> lookup query for N1,N2,N3,N4,N5,N6,N7,N8,N9,N10,N11,N12,N13,N14,N15,N16,N17,N18,N19,N20,N21,N22,N23,N24,N25,N26,N27,N28,N29,N30,N31,N32,N33,N34,N35,N36,N37,N38,N39,N40,N41,N42,N43,N44,N45,N46,N47,N48,N49,N50,N51
Then a <format> user error is returned
Examples:
| format |
| xml |
| json |
| geojson |

View File

@@ -1,6 +1,6 @@
@APIDB @APIDB
Feature: Places by osm_type and osm_id Tests Feature: Places by osm_type and osm_id Tests
Simple tests for response format. Simple tests for internal server errors and response format.
Scenario Outline: address lookup for existing node, way, relation Scenario Outline: address lookup for existing node, way, relation
When sending <format> lookup query for N3284625766,W6065798,,R123924,X99,N0 When sending <format> lookup query for N3284625766,W6065798,,R123924,X99,N0

View File

@@ -5,7 +5,7 @@ Feature: Localization of reverse search results
When sending json reverse coordinates 18.1147,-15.95 When sending json reverse coordinates 18.1147,-15.95
Then result addresses contain Then result addresses contain
| ID | country | | ID | country |
| 0 | موريتانيا | | 0 | Mauritanie موريتانيا |
Scenario: accept-language parameter Scenario: accept-language parameter
When sending json reverse coordinates 18.1147,-15.95 When sending json reverse coordinates 18.1147,-15.95

View File

@@ -10,7 +10,7 @@ Feature: Reverse geocoding
| way | place | house | | way | place | house |
And result addresses contain And result addresses contain
| house_number | road | postcode | country_code | | house_number | road | postcode | country_code |
| 909 | West 1st Street | 57274 | us | | 906 | West 1st Street | 57274 | us |
@Tiger @Tiger
Scenario: No TIGER house number for zoom < 18 Scenario: No TIGER house number for zoom < 18
@@ -31,7 +31,7 @@ Feature: Reverse geocoding
| way | place | house | | way | place | house |
And result addresses contain And result addresses contain
| house_number | road | | house_number | road |
| 1416 | Juan Antonio Lavalleja | | 1410 | Juan Antonio Lavalleja |
Scenario: Address with non-numerical house number Scenario: Address with non-numerical house number
When sending jsonv2 reverse coordinates 53.579805460944,9.9475670458196 When sending jsonv2 reverse coordinates 53.579805460944,9.9475670458196
@@ -50,7 +50,7 @@ Feature: Reverse geocoding
When sending jsonv2 reverse coordinates 54.046489113,8.5546870529 When sending jsonv2 reverse coordinates 54.046489113,8.5546870529
Then results contain Then results contain
| display_name | | display_name |
| Hamburg, Deutschland | | Freie und Hansestadt Hamburg, Deutschland |
Scenario: When slightly outside town, the town is not shown Scenario: When slightly outside town, the town is not shown
When sending jsonv2 reverse coordinates -32.122,-56.114 When sending jsonv2 reverse coordinates -32.122,-56.114

View File

@@ -51,7 +51,7 @@ Feature: Search queries
| en | | en |
Then results contain Then results contain
| display_name | | display_name |
| Plei Ya Rê, Vietnam | | Plei Ya Rê, Kon Tum province, Vietnam |
Scenario: Address details with unknown class types Scenario: Address details with unknown class types
When sending json search query "Hundeauslauf, Hamburg" with address When sending json search query "Hundeauslauf, Hamburg" with address

View File

@@ -26,18 +26,6 @@ Feature: Searches with postcodes
| country_code | | country_code |
| li | | li |
Scenario: Postcode search with bounded viewbox restriction
When sending json search query "9486" with address
| bounded | viewbox |
| 1 | 9.55,47.20,9.58,47.22 |
Then result addresses contain
| postcode |
| 9486 |
When sending json search query "9486" with address
| bounded | viewbox |
| 1 | 5.00,20.00,6.00,21.00 |
Then exactly 0 results are returned
Scenario: Postcode search with structured query Scenario: Postcode search with structured query
When sending json search query "" with address When sending json search query "" with address
| postalcode | country | | postalcode | country |

View File

@@ -19,30 +19,30 @@ Feature: Search queries
| accept-language | | accept-language |
| de | | de |
Then address of result 0 is Then address of result 0 is
| type | value | | type | value |
| house_number | 86 | | house_number | 86 |
| road | Schellingstraße | | road | Schellingstraße |
| neighbourhood | Auenviertel | | suburb | Eilbek |
| suburb | Eilbek | | postcode | 22089 |
| postcode | 22089 |
| city_district | Wandsbek | | city_district | Wandsbek |
| country | Deutschland | | state | Hamburg |
| country_code | de | | country | Deutschland |
| country_code | de |
Scenario: House number interpolation odd Scenario: House number interpolation odd
When sending json search query "Schellingstr 73, Hamburg" with address When sending json search query "Schellingstr 73, Hamburg" with address
| accept-language | | accept-language |
| de | | de |
Then address of result 0 is Then address of result 0 is
| type | value | | type | value |
| house_number | 73 | | house_number | 73 |
| road | Schellingstraße | | road | Schellingstraße |
| neighbourhood | Auenviertel | | suburb | Eilbek |
| suburb | Eilbek | | postcode | 22089 |
| postcode | 22089 |
| city_district | Wandsbek | | city_district | Wandsbek |
| country | Deutschland | | state | Hamburg |
| country_code | de | | country | Deutschland |
| country_code | de |
Scenario: With missing housenumber search falls back to road Scenario: With missing housenumber search falls back to road
When sending json search query "342 rocha, santa lucia" with address When sending json search query "342 rocha, santa lucia" with address

View File

@@ -194,7 +194,7 @@ Feature: Simple Tests
When sending json search query "Tokyo" When sending json search query "Tokyo"
| param | value | | param | value |
|json_callback | <data> | |json_callback | <data> |
Then a json user error is returned Then a HTTP 400 is returned
Examples: Examples:
| data | | data |

View File

@@ -5,7 +5,7 @@ Feature: Status queries against unknown database
Scenario: Failed status as text Scenario: Failed status as text
When sending text status query When sending text status query
Then a HTTP 500 is returned Then a HTTP 500 is returned
And the page contents equals "ERROR: Database connection failed" And the page contents equals "ERROR: No database"
Scenario: Failed status as json Scenario: Failed status as json
When sending json status query When sending json status query
@@ -13,5 +13,5 @@ Feature: Status queries against unknown database
And the result is valid json And the result is valid json
And results contain And results contain
| status | message | | status | message |
| 700 | Database connection failed | | 700 | No database |
And result has not attributes data_updated And result has not attributes data_updated

View File

@@ -344,23 +344,3 @@ Feature: Import of address interpolations
When importing When importing
Then W1 expands to no interpolation Then W1 expands to no interpolation
Scenario: Two point interpolation starting at 0
Given the places
| osm | class | type | housenr | geometry |
| N1 | place | house | 0 | 1 1 |
| N2 | place | house | 2 | 1 1.001 |
And the places
| osm | class | type | addr+interpolation | geometry |
| W1 | place | houses | even | 1 1, 1 1.001 |
And the ways
| id | nodes |
| 1 | 1,2 |
When importing
Then W1 expands to interpolation
| start | end | geometry |
| 0 | 2 | 1 1, 1 1.001 |
When sending jsonv2 reverse coordinates 1,1
Then results contain
| ID | osm_type | osm_id | type | display_name |
| 0 | way | 1 | house | 0 |

View File

@@ -26,8 +26,8 @@ Feature: Import into placex
| R1 | boundary | administrative | 2 | de | (-100 40, -101 40, -101 41, -100 41, -100 40) | | R1 | boundary | administrative | 2 | de | (-100 40, -101 40, -101 41, -100 41, -100 40) |
When importing When importing
Then placex contains Then placex contains
| object | rank_search| addr+country | country_code | | object | addr+country | country_code |
| R1 | 4 | de | de | | R1 | de | de |
Scenario: Illegal country code tag for countries is ignored Scenario: Illegal country code tag for countries is ignored
Given the named places Given the named places
@@ -157,6 +157,9 @@ Feature: Import into placex
| N36 | place | house | | N36 | place | house |
| N37 | place | building | | N37 | place | building |
| N38 | place | houses | | N38 | place | houses |
And the named places
| osm | class | type | extra+locality |
| N100 | place | locality | townland |
And the named places And the named places
| osm | class | type | extra+capital | | osm | class | type | extra+capital |
| N101 | place | city | yes | | N101 | place | city | yes |
@@ -165,10 +168,10 @@ Feature: Import into placex
| object | rank_search | rank_address | | object | rank_search | rank_address |
| N1 | 30 | 30 | | N1 | 30 | 30 |
| N11 | 30 | 30 | | N11 | 30 | 30 |
| N12 | 2 | 0 | | N12 | 2 | 2 |
| N13 | 2 | 0 | | N13 | 2 | 0 |
| N14 | 4 | 0 | | N14 | 4 | 4 |
| N15 | 8 | 0 | | N15 | 8 | 8 |
| N16 | 18 | 0 | | N16 | 18 | 0 |
| N17 | 12 | 12 | | N17 | 12 | 12 |
| N18 | 16 | 16 | | N18 | 16 | 16 |
@@ -188,6 +191,7 @@ Feature: Import into placex
| N32 | 20 | 0 | | N32 | 20 | 0 |
| N33 | 20 | 0 | | N33 | 20 | 0 |
| N34 | 20 | 0 | | N34 | 20 | 0 |
| N100 | 20 | 20 |
| N101 | 15 | 16 | | N101 | 15 | 16 |
| N35 | 22 | 22 | | N35 | 22 | 22 |
| N36 | 30 | 30 | | N36 | 30 | 30 |
@@ -207,10 +211,6 @@ Feature: Import into placex
| R21 | boundary | administrative | 32 | (3 3, 4 4, 3 4, 3 3) | | R21 | boundary | administrative | 32 | (3 3, 4 4, 3 4, 3 3) |
| R22 | boundary | nature_park | 6 | (0 0, 1 0, 0 1, 0 0) | | R22 | boundary | nature_park | 6 | (0 0, 1 0, 0 1, 0 0) |
| R23 | boundary | natural_reserve| 10 | (0 0, 1 1, 1 0, 0 0) | | R23 | boundary | natural_reserve| 10 | (0 0, 1 1, 1 0, 0 0) |
And the named places
| osm | class | type | geometry |
| R40 | place | country | (1 1, 2 2, 1 2, 1 1) |
| R41 | place | state | (3 3, 4 4, 3 4, 3 3) |
When importing When importing
Then placex has no entry for N1 Then placex has no entry for N1
And placex has no entry for W10 And placex has no entry for W10
@@ -218,10 +218,8 @@ Feature: Import into placex
| object | rank_search | rank_address | | object | rank_search | rank_address |
| R20 | 4 | 4 | | R20 | 4 | 4 |
| R21 | 30 | 30 | | R21 | 30 | 30 |
| R22 | 30 | 30 | | R22 | 12 | 0 |
| R23 | 30 | 30 | | R23 | 20 | 0 |
| R40 | 4 | 4 |
| R41 | 8 | 8 |
Scenario: search and address ranks for highways correctly assigned Scenario: search and address ranks for highways correctly assigned
Given the scene roads-with-pois Given the scene roads-with-pois
@@ -239,7 +237,7 @@ Feature: Import into placex
When importing When importing
Then placex contains Then placex contains
| object | rank_search | rank_address | | object | rank_search | rank_address |
| N1 | 30 | 0 | | N1 | 30 | 30 |
| W1 | 26 | 26 | | W1 | 26 | 26 |
| W2 | 26 | 26 | | W2 | 26 | 26 |
| W3 | 26 | 26 | | W3 | 26 | 26 |
@@ -260,11 +258,11 @@ Feature: Import into placex
When importing When importing
Then placex contains Then placex contains
| object | rank_search | rank_address | | object | rank_search | rank_address |
| N2 | 30 | 0 | | N2 | 30 | 30 |
| W2 | 30 | 0 | | W2 | 30 | 30 |
| W4 | 22 | 22 | | W4 | 22 | 22 |
| R2 | 22 | 22 | | R2 | 22 | 22 |
| R3 | 22 | 0 | | R3 | 22 | 0 |
Scenario: rank and inclusion of naturals Scenario: rank and inclusion of naturals
Given the named places Given the named places
@@ -288,27 +286,8 @@ Feature: Import into placex
| N5 | 30 | 30 | | N5 | 30 | 30 |
| W2 | 18 | 0 | | W2 | 18 | 0 |
| R3 | 18 | 0 | | R3 | 18 | 0 |
| R4 | 30 | 30 | | R4 | 22 | 0 |
| R5 | 4 | 0 | | R5 | 4 | 4 |
| R6 | 4 | 0 | | R6 | 4 | 4 |
| W3 | 30 | 30 | | W3 | 30 | 30 |
Scenario: boundary ways for countries and states are ignored
Given the named places
| osm | class | type | admin | geometry |
| W4 | boundary | administrative | 2 | poly-area:0.1 |
| R4 | boundary | administrative | 2 | poly-area:0.1 |
| W5 | boundary | administrative | 3 | poly-area:0.1 |
| R5 | boundary | administrative | 3 | poly-area:0.1 |
| W6 | boundary | administrative | 4 | poly-area:0.1 |
| R6 | boundary | administrative | 4 | poly-area:0.1 |
| W7 | boundary | administrative | 5 | poly-area:0.1 |
| R7 | boundary | administrative | 5 | poly-area:0.1 |
When importing
Then placex contains exactly
| object |
| R4 |
| R5 |
| R6 |
| W7 |
| R7 |

View File

@@ -125,15 +125,3 @@ Feature: Import of postcodes
Then placex contains Then placex contains
| object | postcode | | object | postcode |
| W93 | 112 DE 34 | | W93 | 112 DE 34 |
Scenario: Postcodes are added to the postcode and word table
Given the places
| osm | class | type | addr+postcode | addr+housenumber | geometry |
| N34 | place | house | 01982 | 111 |country:de |
When importing
Then location_postcode contains exactly
| country | postcode | geometry |
| de | 01982 | country:de |
And word contains
| word | class | type |
| 01982 | place | postcode |

View File

@@ -1,58 +0,0 @@
@DB
Feature: Update of postcode
Tests for updating of data related to postcodes
Scenario: A new postcode appears in the postcode and word table
Given the places
| osm | class | type | addr+postcode | addr+housenumber | geometry |
| N34 | place | house | 01982 | 111 |country:de |
When importing
Then location_postcode contains exactly
| country | postcode | geometry |
| de | 01982 | country:de |
When updating places
| osm | class | type | addr+postcode | addr+housenumber | geometry |
| N35 | place | house | 4567 | 5 |country:ch |
And updating postcodes
Then location_postcode contains exactly
| country | postcode | geometry |
| de | 01982 | country:de |
| ch | 4567 | country:ch |
And word contains
| word | class | type |
| 01982 | place | postcode |
| 4567 | place | postcode |
Scenario: When the last postcode is deleted, it is deleted from postcode and word
Given the places
| osm | class | type | addr+postcode | addr+housenumber | geometry |
| N34 | place | house | 01982 | 111 |country:de |
| N35 | place | house | 4567 | 5 |country:ch |
When importing
And marking for delete N34
And updating postcodes
Then location_postcode contains exactly
| country | postcode | geometry |
| ch | 4567 | country:ch |
And word contains not
| word | class | type |
| 01982 | place | postcode |
And word contains
| word | class | type |
| 4567 | place | postcode |
Scenario: A postcode is not deleted from postcode and word when it exist in another country
Given the places
| osm | class | type | addr+postcode | addr+housenumber | geometry |
| N34 | place | house | 01982 | 111 |country:de |
| N35 | place | house | 01982 | 5 |country:ch |
When importing
And marking for delete N34
And updating postcodes
Then location_postcode contains exactly
| country | postcode | geometry |
| ch | 01982 | country:ch |
And word contains
| word | class | type |
| 01982 | place | postcode |

View File

@@ -34,7 +34,7 @@ Feature: Update of simple objects
When importing When importing
Then placex contains Then placex contains
| object | rank_address | | object | rank_address |
| R1 | 30 | | R1 | 0 |
| W1 | 30 | | W1 | 30 |
When marking for delete R1,W1 When marking for delete R1,W1
Then placex has no entry for W1 Then placex has no entry for W1
@@ -103,4 +103,4 @@ Feature: Update of simple objects
| W1 | boundary | historic | Haha | 5 | (1, 2, 4, 3, 1) | | W1 | boundary | historic | Haha | 5 | (1, 2, 4, 3, 1) |
Then placex contains Then placex contains
| object | rank_address | | object | rank_address |
| W1 | 30 | | W1 | 0 |

View File

@@ -15,7 +15,6 @@ userconfig = {
'REMOVE_TEMPLATE' : False, 'REMOVE_TEMPLATE' : False,
'KEEP_TEST_DB' : False, 'KEEP_TEST_DB' : False,
'DB_HOST' : None, 'DB_HOST' : None,
'DB_PORT' : None,
'DB_USER' : None, 'DB_USER' : None,
'DB_PASS' : None, 'DB_PASS' : None,
'TEMPLATE_DB' : 'test_template_nominatim', 'TEMPLATE_DB' : 'test_template_nominatim',
@@ -36,7 +35,6 @@ class NominatimEnvironment(object):
self.build_dir = os.path.abspath(config['BUILDDIR']) self.build_dir = os.path.abspath(config['BUILDDIR'])
self.src_dir = os.path.abspath(os.path.join(os.path.split(__file__)[0], "../..")) self.src_dir = os.path.abspath(os.path.join(os.path.split(__file__)[0], "../.."))
self.db_host = config['DB_HOST'] self.db_host = config['DB_HOST']
self.db_port = config['DB_PORT']
self.db_user = config['DB_USER'] self.db_user = config['DB_USER']
self.db_pass = config['DB_PASS'] self.db_pass = config['DB_PASS']
self.template_db = config['TEMPLATE_DB'] self.template_db = config['TEMPLATE_DB']
@@ -56,8 +54,6 @@ class NominatimEnvironment(object):
dbargs = {'database': dbname} dbargs = {'database': dbname}
if self.db_host: if self.db_host:
dbargs['host'] = self.db_host dbargs['host'] = self.db_host
if self.db_port:
dbargs['port'] = self.db_port
if self.db_user: if self.db_user:
dbargs['user'] = self.db_user dbargs['user'] = self.db_user
if self.db_pass: if self.db_pass:
@@ -73,14 +69,11 @@ class NominatimEnvironment(object):
def write_nominatim_config(self, dbname): def write_nominatim_config(self, dbname):
f = open(self.local_settings_file, 'w') f = open(self.local_settings_file, 'w')
# https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php f.write("<?php\n @define('CONST_Database_DSN', 'pgsql://%s:%s@%s/%s');\n" %
f.write("<?php\n @define('CONST_Database_DSN', 'pgsql:dbname=%s%s%s%s%s');\n" % (self.db_user if self.db_user else '',
(dbname, self.db_pass if self.db_pass else '',
(';host=' + self.db_host) if self.db_host else '', self.db_host if self.db_host else '',
(';port=' + self.db_port) if self.db_port else '', dbname))
(';user=' + self.db_user) if self.db_user else '',
(';password=' + self.db_pass) if self.db_pass else ''
))
f.write("@define('CONST_Osm2pgsql_Flatnode_File', null);\n") f.write("@define('CONST_Osm2pgsql_Flatnode_File', null);\n")
f.close() f.close()

View File

@@ -51,10 +51,8 @@ Feature: Tag evaluation
""" """
n1 Thighway=yes,operator=Foo,name=null n1 Thighway=yes,operator=Foo,name=null
n2 Tshop=grocery,operator=Foo n2 Tshop=grocery,operator=Foo
n3 Tamenity=restaurant,operator=Foo n3 Tamenity=hospital,operator=Foo
n4 Ttourism=hotel,operator=Foo n4 Ttourism=hotel,operator=Foo
n5 Tamenity=hospital,operator=Foo,name=Meme
n6 Tamenity=fuel,operator=Foo
""" """
Then place contains Then place contains
| object | name | | object | name |
@@ -62,8 +60,6 @@ Feature: Tag evaluation
| N2 | 'operator' : 'Foo' | | N2 | 'operator' : 'Foo' |
| N3 | 'operator' : 'Foo' | | N3 | 'operator' : 'Foo' |
| N4 | 'operator' : 'Foo' | | N4 | 'operator' : 'Foo' |
| N5 | 'name' : 'Meme' |
| N6 | 'operator' : 'Foo' |
Scenario Outline: Ignored name tags Scenario Outline: Ignored name tags
When loading osm data When loading osm data
@@ -231,6 +227,14 @@ Feature: Tag evaluation
| boundary | administrative | | boundary | administrative |
| waterway | stream | | waterway | stream |
Scenario: Footways are not included if they are sidewalks
When loading osm data
"""
n2 Thighway=footway,name=To%20%Hell,footway=sidewalk
n23 Thighway=footway,name=x
"""
Then place has no entry for N2
Scenario: named junctions are included if there is no other tag Scenario: named junctions are included if there is no other tag
When loading osm data When loading osm data
""" """
@@ -530,9 +534,9 @@ Feature: Tag evaluation
Then place contains Then place contains
| object | class | type | | object | class | type |
| N10 | tourism | hotel | | N10 | tourism | hotel |
| N12 | building| shed | | N12 | building| yes |
| N13 | building| yes | | N13 | building| yes |
| N14 | place | postcode | | N14 | building| yes |
And place has no entry for N10:building And place has no entry for N10:building
And place has no entry for N11 And place has no entry for N11
@@ -544,20 +548,3 @@ Feature: Tag evaluation
Then place contains Then place contains
| object | class | type | address | | object | class | type | address |
| N290393920 | place | house| 'city' : 'Perpignan', 'country' : 'FR', 'housenumber' : '43\\', 'postcode' : '66000', 'street' : 'Rue Pierre Constant d`Ivry' | | N290393920 | place | house| 'city' : 'Perpignan', 'country' : 'FR', 'housenumber' : '43\\', 'postcode' : '66000', 'street' : 'Rue Pierre Constant d`Ivry' |
Scenario: odd interpolation
When loading osm data
"""
n4 Taddr:housenumber=3 x0 y0
n5 Taddr:housenumber=15 x0 y0.00001
w12 Taddr:interpolation=odd Nn4,n5
w13 Taddr:interpolation=even Nn4,n5
w14 Taddr:interpolation=-3 Nn4,n5
"""
Then place contains
| object | class | type | address |
| N4 | place | house | 'housenumber' : '3' |
| N5 | place | house | 'housenumber' : '15' |
| W12 | place | houses | 'interpolation' : 'odd' |
| W13 | place | houses | 'interpolation' : 'even' |
| W14 | place | houses | 'interpolation' : '-3' |

View File

@@ -329,10 +329,6 @@ def update_place_table(context):
check_database_integrity(context) check_database_integrity(context)
@when("updating postcodes")
def update_postcodes(context):
context.nominatim.run_update_script('calculate-postcodes')
@when("marking for delete (?P<oids>.*)") @when("marking for delete (?P<oids>.*)")
def delete_places(context, oids): def delete_places(context, oids):
context.nominatim.run_setup_script( context.nominatim.run_setup_script(
@@ -481,43 +477,6 @@ def check_search_name_contents(context, exclude):
context.db.commit() context.db.commit()
@then("location_postcode contains exactly")
def check_location_postcode(context):
cur = context.db.cursor(cursor_factory=psycopg2.extras.DictCursor)
cur.execute("SELECT *, ST_AsText(geometry) as geomtxt FROM location_postcode")
eq_(cur.rowcount, len(list(context.table)),
"Postcode table has %d rows, expected %d rows."
% (cur.rowcount, len(list(context.table))))
table = list(cur)
for row in context.table:
for i in range(len(table)):
if table[i]['country_code'] != row['country'] \
or table[i]['postcode'] != row['postcode']:
continue
for h in row.headings:
if h not in ('country', 'postcode'):
assert_db_column(table[i], h, row[h], context)
@then("word contains(?P<exclude> not)?")
def check_word_table(context, exclude):
cur = context.db.cursor(cursor_factory=psycopg2.extras.DictCursor)
for row in context.table:
wheres = []
values = []
for h in row.headings:
wheres.append("%s = %%s" % h)
values.append(row[h])
cur.execute("SELECT * from word WHERE %s" % ' AND '.join(wheres), values)
if exclude:
eq_(0, cur.rowcount,
"Row still in word table: %s" % '/'.join(values))
else:
assert_greater(cur.rowcount, 0,
"Row not in word table: %s" % '/'.join(values))
@then("place_addressline contains") @then("place_addressline contains")
def check_place_addressline(context): def check_place_addressline(context):
cur = context.db.cursor(cursor_factory=psycopg2.extras.DictCursor) cur = context.db.cursor(cursor_factory=psycopg2.extras.DictCursor)

View File

@@ -71,7 +71,7 @@ class GenericResponse(object):
pass pass
elif h == 'osm': elif h == 'osm':
assert_equal(res['osm_type'], row[h][0]) assert_equal(res['osm_type'], row[h][0])
assert_equal(res['osm_id'], int(row[h][1:])) assert_equal(res['osm_id'], row[h][1:])
elif h == 'centroid': elif h == 'centroid':
x, y = row[h].split(' ') x, y = row[h].split(' ')
assert_almost_equal(float(y), float(res['lat'])) assert_almost_equal(float(y), float(res['lat']))
@@ -494,18 +494,6 @@ def step_impl(context, fmt):
context.execute_steps("Then a HTTP 200 is returned") context.execute_steps("Then a HTTP 200 is returned")
eq_(context.response.format, fmt) eq_(context.response.format, fmt)
@then(u'a (?P<fmt>\w+) user error is returned')
def check_page_error(context, fmt):
context.execute_steps("Then a HTTP 400 is returned")
eq_(context.response.format, fmt)
if fmt == 'html':
assert_is_not_none(re.search(r'<html( |>).+</html>', context.response.page, re.DOTALL))
elif fmt == 'xml':
assert_is_not_none(re.search(r'<error>.+</error>', context.response.page, re.DOTALL))
else:
assert_is_not_none(re.search(r'({"error":)', context.response.page, re.DOTALL))
@then(u'result header contains') @then(u'result header contains')
def check_header_attr(context): def check_header_attr(context):
for line in context.table: for line in context.table:

View File

@@ -1,108 +0,0 @@
<?php
namespace Nominatim;
require_once(CONST_BasePath.'/lib/init-website.php');
require_once(CONST_BasePath.'/lib/AddressDetails.php');
class AddressDetailsTest extends \PHPUnit\Framework\TestCase
{
protected function setUp()
{
// How the fixture got created
//
// 1) search for '10 downing street'
// https://nominatim.openstreetmap.org/details.php?osmtype=R&osmid=1879842
//
// 2) find place_id in the local database
// SELECT place_id, name FROM placex WHERE osm_type='R' AND osm_id=1879842;
//
// 3) set postgresql to non-align output, e.g. psql -A or \a in the CLI
//
// 4) query
// SELECT row_to_json(row,true) FROM (
// SELECT *, get_name_by_language(name, ARRAY['name:en']) as localname
// FROM get_addressdata(194663412,10)
// ORDER BY rank_address DESC, isaddress DESC
// ) AS row;
//
// 5) copy&paste into file. Add commas between records
//
$json = file_get_contents(CONST_BasePath.'/test/php/fixtures/address_details_10_downing_street.json');
$data = json_decode($json, true);
$this->oDbStub = $this->getMockBuilder(\DB::class)
->setMethods(array('getAll'))
->getMock();
$this->oDbStub->method('getAll')
->willReturn($data);
}
public function testGetLocaleAddress()
{
$oAD = new AddressDetails($this->oDbStub, 194663412, 10, 'en');
$expected = join(', ', array(
'10 Downing Street',
'10',
'Downing Street',
'St. James\'s',
'Covent Garden',
'Westminster',
'London',
'Greater London',
'England',
'SW1A 2AA',
'United Kingdom'
));
$this->assertEquals($expected, $oAD->getLocaleAddress());
}
public function testGetAddressDetails()
{
$oAD = new AddressDetails($this->oDbStub, 194663412, 10, 'en');
$this->assertEquals(18, count($oAD->getAddressDetails(true)));
$this->assertEquals(12, count($oAD->getAddressDetails(false)));
}
public function testGetAddressNames()
{
$oAD = new AddressDetails($this->oDbStub, 194663412, 10, 'en');
$expected = array(
'attraction' => '10 Downing Street',
'house_number' => '10',
'road' => 'Downing Street',
'neighbourhood' => 'St. James\'s',
'suburb' => 'Covent Garden',
'city' => 'London',
'state_district' => 'Greater London',
'state' => 'England',
'postcode' => 'SW1A 2AA',
'country' => 'United Kingdom',
'country_code' => 'gb'
);
$this->assertEquals($expected, $oAD->getAddressNames());
}
public function testGetAdminLevels()
{
$oAD = new AddressDetails($this->oDbStub, 194663412, 10, 'en');
$expected = array(
'level8' => 'Westminster',
'level6' => 'London',
'level5' => 'Greater London',
'level4' => 'England',
'level2' => 'United Kingdom'
);
$this->assertEquals($expected, $oAD->getAdminLevels());
}
public function testDebugInfo()
{
$oAD = new AddressDetails($this->oDbStub, 194663412, 10, 'en');
$this->assertTrue(is_array($oAD->debugInfo()));
$this->assertEquals(18, count($oAD->debugInfo()));
}
}

View File

@@ -1,94 +0,0 @@
<?php
namespace Nominatim;
require_once(CONST_BasePath.'/lib/ClassTypes.php');
class ClassTypesTest extends \PHPUnit\Framework\TestCase
{
public function testGetInfo()
{
// 1) Admin level set
// city Dublin
// https://nominatim.openstreetmap.org/details.php?osmtype=R&osmid=1109531
$aPlace = array(
'admin_level' => 7,
'class' => 'boundary',
'type' => 'administrative',
'rank_address' => 14
);
$this->assertEquals('County', ClassTypes\getInfo($aPlace)['label']);
$this->assertEquals('County', ClassTypes\getFallbackInfo($aPlace)['label']);
$this->assertEquals('County', ClassTypes\getProperty($aPlace, 'label'));
// 2) No admin level
// Eiffel Tower
// https://nominatim.openstreetmap.org/details.php?osmtype=W&osmid=5013364
$aPlace = array(
'class' => 'tourism',
'type' => 'attraction',
'rank_address' => 29
);
$this->assertEquals('Attraction', ClassTypes\getInfo($aPlace)['label']);
$this->assertEquals(array('simplelabel' => 'address29'), ClassTypes\getFallbackInfo($aPlace));
$this->assertEquals('Attraction', ClassTypes\getProperty($aPlace, 'label'));
// 3) Unknown type
// La Maison du Toutou, Paris
// https://nominatim.openstreetmap.org/details.php?osmtype=W&osmid=164011651
$aPlace = array(
'class' => 'shop',
'type' => 'pet_grooming',
'rank_address' => 29
);
$this->assertEquals(false, ClassTypes\getInfo($aPlace));
$this->assertEquals(array('simplelabel' => 'address29'), ClassTypes\getFallbackInfo($aPlace));
$this->assertEquals(false, ClassTypes\getProperty($aPlace, 'label'));
$this->assertEquals('mydefault', ClassTypes\getProperty($aPlace, 'label', 'mydefault'));
}
public function testGetClassTypesWithImportance()
{
$aClasses = ClassTypes\getListWithImportance();
$this->assertGreaterThan(
200,
count($aClasses)
);
$this->assertEquals(
array(
'label' => 'Country',
'frequency' => 0,
'icon' => 'poi_boundary_administrative',
'defzoom' => 6,
'defdiameter' => 15,
'importance' => 3
),
$aClasses['place:country']
);
}
public function testGetResultDiameter()
{
$aResult = array('class' => '', 'type' => '');
$this->assertEquals(
0.0001,
ClassTypes\getProperty($aResult, 'defdiameter', 0.0001)
);
$aResult = array('class' => 'place', 'type' => 'country');
$this->assertEquals(
15,
ClassTypes\getProperty($aResult, 'defdiameter', 0.0001)
);
$aResult = array('class' => 'boundary', 'type' => 'administrative', 'admin_level' => 6);
$this->assertEquals(
0.32,
ClassTypes\getProperty($aResult, 'defdiameter', 0.0001)
);
}
}

View File

@@ -1,116 +0,0 @@
<?php
namespace Nominatim;
require_once(CONST_BasePath.'/lib/lib.php');
require_once(CONST_BasePath.'/lib/DB.php');
// subclassing so we can set the protected connection variable
class NominatimSubClassedDB extends \Nominatim\DB
{
public function setConnection($oConnection)
{
$this->connection = $oConnection;
}
}
// phpcs:ignore PSR1.Classes.ClassDeclaration.MultipleClasses
class DBTest extends \PHPUnit\Framework\TestCase
{
public function testReusingConnection()
{
$oDB = new NominatimSubClassedDB('');
$oDB->setConnection('anything');
$this->assertTrue($oDB->connect());
}
public function testDatabaseExists()
{
$oDB = new \Nominatim\DB('');
$this->assertFalse($oDB->databaseExists());
}
public function testErrorHandling()
{
$this->expectException(DatabaseError::class);
$this->expectExceptionMessage('Failed to establish database connection');
$oDB = new \Nominatim\DB('pgsql:dbname=abc');
$oDB->connect();
}
public function testErrorHandling2()
{
$this->expectException(DatabaseError::class);
$this->expectExceptionMessage('Database query failed');
$oPDOStub = $this->getMockBuilder(PDO::class)
->setMethods(array('query', 'quote'))
->getMock();
$oPDOStub->method('query')
->will($this->returnCallback(function ($sVal) {
return "'$sVal'";
}));
$oPDOStub->method('query')
->will($this->returnCallback(function () {
throw new \PDOException('ERROR: syntax error at or near "FROM"');
}));
$oDB = new NominatimSubClassedDB('');
$oDB->setConnection($oPDOStub);
$oDB->getOne('SELECT name FROM');
}
public function testGetPostgresVersion()
{
$oDBStub = $this->getMockBuilder(\Nominatim\DB::class)
->disableOriginalConstructor()
->setMethods(array('getOne'))
->getMock();
$oDBStub->method('getOne')
->willReturn('100006');
$this->assertEquals(10, $oDBStub->getPostgresVersion());
}
public function testGetPostgisVersion()
{
$oDBStub = $this->getMockBuilder(\Nominatim\DB::class)
->disableOriginalConstructor()
->setMethods(array('getOne'))
->getMock();
$oDBStub->method('getOne')
->willReturn('2.4.4');
$this->assertEquals(2.4, $oDBStub->getPostgisVersion());
}
public function testParseDSN()
{
$this->assertEquals(
array(),
\Nominatim\DB::parseDSN('')
);
$this->assertEquals(
array(
'database' => 'db1',
'hostspec' => 'machine1'
),
\Nominatim\DB::parseDSN('pgsql:dbname=db1;host=machine1')
);
$this->assertEquals(
array(
'database' => 'db1',
'hostspec' => 'machine1',
'port' => '1234',
'username' => 'john',
'password' => 'secret'
),
\Nominatim\DB::parseDSN('pgsql:dbname=db1;host=machine1;port=1234;user=john;password=secret')
);
}
}

View File

@@ -1,31 +0,0 @@
<?php
namespace Nominatim;
require_once(CONST_BasePath.'/lib/init-website.php');
require_once(CONST_BasePath.'/lib/DatabaseError.php');
class DatabaseErrorTest extends \PHPUnit\Framework\TestCase
{
public function testSqlMessage()
{
$oSqlStub = $this->getMockBuilder(PDOException::class)
->setMethods(array('getMessage'))
->getMock();
$oSqlStub->method('getMessage')
->willReturn('Unknown table.');
$oErr = new DatabaseError('Sql error', 123, null, $oSqlStub);
$this->assertEquals('Sql error', $oErr->getMessage());
$this->assertEquals(123, $oErr->getCode());
$this->assertEquals('Unknown table.', $oErr->getSqlError());
}
public function testSqlObjectDump()
{
$oErr = new DatabaseError('Sql error', 123, null, array('one' => 'two'));
$this->assertRegExp('/two/', $oErr->getSqlDebugDump());
}
}

View File

@@ -2,23 +2,19 @@
namespace Nominatim; namespace Nominatim;
require_once(CONST_BasePath.'/lib/DebugHtml.php'); use Exception;
require_once('../../lib/DebugHtml.php');
class DebugTest extends \PHPUnit\Framework\TestCase class DebugTest extends \PHPUnit\Framework\TestCase
{ {
protected function setUp() protected function setUp()
{ {
$this->oWithDebuginfo = $this->getMockBuilder(\GeococdeMock::class) $this->oWithDebuginfo = $this->getMock(Geocode::class, array('debugInfo'));
->setMethods(array('debugInfo'))
->getMock();
$this->oWithDebuginfo->method('debugInfo') $this->oWithDebuginfo->method('debugInfo')
->willReturn(array('key1' => 'val1', 'key2' => 'val2', 'key3' => 'val3')); ->willReturn(array('key1' => 'val1', 'key2' => 'val2', 'key3' => 'val3'));
$this->oWithToString = $this->getMock(Geocode::class, array('__toString'));
$this->oWithToString = $this->getMockBuilder(\SomeMock::class)
->setMethods(array('__toString'))
->getMock();
$this->oWithToString->method('__toString')->willReturn('me as string'); $this->oWithToString->method('__toString')->willReturn('me as string');
} }

View File

@@ -2,11 +2,55 @@
namespace Nominatim; namespace Nominatim;
require_once(CONST_BasePath.'/lib/lib.php'); require_once '../../lib/lib.php';
require_once(CONST_BasePath.'/lib/ClassTypes.php'); require_once '../../lib/ClassTypes.php';
class LibTest extends \PHPUnit\Framework\TestCase class LibTest extends \PHPUnit\Framework\TestCase
{ {
public function testGetClassTypesWithImportance()
{
$aClasses = ClassTypes\getListWithImportance();
$this->assertGreaterThan(
200,
count($aClasses)
);
$this->assertEquals(
array(
'label' => 'Country',
'frequency' => 0,
'icon' => 'poi_boundary_administrative',
'defzoom' => 6,
'defdiameter' => 15,
'importance' => 3
),
$aClasses['place:country']
);
}
public function testGetResultDiameter()
{
$aResult = array('class' => '', 'type' => '');
$this->assertEquals(
0.0001,
ClassTypes\getProperty($aResult, 'defdiameter', 0.0001)
);
$aResult = array('class' => 'place', 'type' => 'country');
$this->assertEquals(
15,
ClassTypes\getProperty($aResult, 'defdiameter', 0.0001)
);
$aResult = array('class' => 'boundary', 'type' => 'administrative', 'admin_level' => 6);
$this->assertEquals(
0.32,
ClassTypes\getProperty($aResult, 'defdiameter', 0.0001)
);
}
public function testAddQuotes() public function testAddQuotes()
{ {
@@ -173,12 +217,4 @@ class LibTest extends \PHPUnit\Framework\TestCase
// start == end // start == end
$this->closestHouseNumberEvenOddOther(50, 50, 0.5, array('even' => 50, 'odd' => 50, 'other' => 50)); $this->closestHouseNumberEvenOddOther(50, 50, 0.5, array('even' => 50, 'odd' => 50, 'other' => 50));
} }
public function testGetSearchRankLabel()
{
$this->assertEquals('unknown', getSearchRankLabel(null));
$this->assertEquals('continent', getSearchRankLabel(0));
$this->assertEquals('continent', getSearchRankLabel(1));
$this->assertEquals('other: 30', getSearchRankLabel(30));
}
} }

View File

@@ -2,12 +2,14 @@
namespace Nominatim; namespace Nominatim;
require_once(CONST_BasePath.'/lib/ParameterParser.php'); use Exception;
require_once('../../lib/ParameterParser.php');
function userError($sError) function userError($sError)
{ {
throw new \Exception($sError); throw new Exception($sError);
} }
class ParameterParserTest extends \PHPUnit\Framework\TestCase class ParameterParserTest extends \PHPUnit\Framework\TestCase
@@ -53,18 +55,14 @@ class ParameterParserTest extends \PHPUnit\Framework\TestCase
public function testGetIntWithNonNumber() public function testGetIntWithNonNumber()
{ {
$this->expectException(\Exception::class); $this->setExpectedException(Exception::class, "Integer number expected for parameter 'int4'");
$this->expectExceptionMessage("Integer number expected for parameter 'int4'");
(new ParameterParser(array('int4' => 'a')))->getInt('int4'); (new ParameterParser(array('int4' => 'a')))->getInt('int4');
} }
public function testGetIntWithEmpytString() public function testGetIntWithEmpytString()
{ {
$this->expectException(\Exception::class); $this->setExpectedException(Exception::class, "Integer number expected for parameter 'int5'");
$this->expectExceptionMessage("Integer number expected for parameter 'int5'");
(new ParameterParser(array('int5' => '')))->getInt('int5'); (new ParameterParser(array('int5' => '')))->getInt('int5');
} }
@@ -87,26 +85,20 @@ class ParameterParserTest extends \PHPUnit\Framework\TestCase
public function testGetFloatWithEmptyString() public function testGetFloatWithEmptyString()
{ {
$this->expectException(\Exception::class); $this->setExpectedException(Exception::class, "Floating-point number expected for parameter 'float4'");
$this->expectExceptionMessage("Floating-point number expected for parameter 'float4'");
(new ParameterParser(array('float4' => '')))->getFloat('float4'); (new ParameterParser(array('float4' => '')))->getFloat('float4');
} }
public function testGetFloatWithTextString() public function testGetFloatWithTextString()
{ {
$this->expectException(\Exception::class); $this->setExpectedException(Exception::class, "Floating-point number expected for parameter 'float5'");
$this->expectExceptionMessage("Floating-point number expected for parameter 'float5'");
(new ParameterParser(array('float5' => 'a')))->getFloat('float5'); (new ParameterParser(array('float5' => 'a')))->getFloat('float5');
} }
public function testGetFloatWithInvalidNumber() public function testGetFloatWithInvalidNumber()
{ {
$this->expectException(\Exception::class); $this->setExpectedException(Exception::class, "Floating-point number expected for parameter 'float6'");
$this->expectExceptionMessage("Floating-point number expected for parameter 'float6'");
(new ParameterParser(array('float6' => '-55.')))->getFloat('float6'); (new ParameterParser(array('float6' => '-55.')))->getFloat('float6');
} }
@@ -146,9 +138,7 @@ class ParameterParserTest extends \PHPUnit\Framework\TestCase
public function testGetSetWithValueNotInSet() public function testGetSetWithValueNotInSet()
{ {
$this->expectException(\Exception::class); $this->setExpectedException(Exception::class, "Parameter 'val4' must be one of: foo, bar");
$this->expectExceptionMessage("Parameter 'val4' must be one of: foo, bar");
(new ParameterParser(array('val4' => 'faz')))->getSet('val4', array('foo', 'bar')); (new ParameterParser(array('val4' => 'faz')))->getSet('val4', array('foo', 'bar'));
} }
@@ -223,27 +213,5 @@ class ParameterParserTest extends \PHPUnit\Framework\TestCase
'ref' => 'ref', 'ref' => 'ref',
'type' => 'type', 'type' => 'type',
), $oParams->getPreferredLanguages('default')); ), $oParams->getPreferredLanguages('default'));
$oParams = new ParameterParser(array('accept-language' => 'ja_rm,zh_pinyin'));
$this->assertSame(array(
'short_name:ja_rm' => 'short_name:ja_rm',
'name:ja_rm' => 'name:ja_rm',
'short_name:zh_pinyin' => 'short_name:zh_pinyin',
'name:zh_pinyin' => 'name:zh_pinyin',
'short_name:ja' => 'short_name:ja',
'name:ja' => 'name:ja',
'short_name:zh' => 'short_name:zh',
'name:zh' => 'name:zh',
'short_name' => 'short_name',
'name' => 'name',
'brand' => 'brand',
'official_name:ja_rm' => 'official_name:ja_rm',
'official_name:zh_pinyin' => 'official_name:zh_pinyin',
'official_name:ja' => 'official_name:ja',
'official_name:zh' => 'official_name:zh',
'official_name' => 'official_name',
'ref' => 'ref',
'type' => 'type',
), $oParams->getPreferredLanguages('default'));
} }
} }

View File

@@ -2,7 +2,7 @@
namespace Nominatim; namespace Nominatim;
require_once(CONST_BasePath.'/lib/Phrase.php'); require_once '../../lib/Phrase.php';
class PhraseTest extends \PHPUnit\Framework\TestCase class PhraseTest extends \PHPUnit\Framework\TestCase
{ {

View File

@@ -2,7 +2,9 @@
namespace Nominatim; namespace Nominatim;
require_once(CONST_BasePath.'/lib/SearchContext.php'); @define('CONST_BasePath', '../../');
require_once '../../lib/SearchContext.php';
class SearchContextTest extends \PHPUnit\Framework\TestCase class SearchContextTest extends \PHPUnit\Framework\TestCase
{ {

View File

@@ -2,18 +2,18 @@
namespace Nominatim; namespace Nominatim;
require_once(CONST_BasePath.'/lib/DB.php'); require_once('../../lib/Status.php');
require_once(CONST_BasePath.'/lib/Status.php'); require_once('DB.php');
use Exception;
class StatusTest extends \PHPUnit\Framework\TestCase class StatusTest extends \PHPUnit\Framework\TestCase
{ {
public function testNoDatabaseGiven() public function testNoDatabaseGiven()
{ {
$this->expectException(\Exception::class); $this->setExpectedException(Exception::class, 'No database', 700);
$this->expectExceptionMessage('No database');
$this->expectExceptionCode(700);
$oDB = null; $oDB = null;
$oStatus = new Status($oDB); $oStatus = new Status($oDB);
@@ -22,35 +22,28 @@ class StatusTest extends \PHPUnit\Framework\TestCase
public function testNoDatabaseConnectionFail() public function testNoDatabaseConnectionFail()
{ {
$this->expectException(\Exception::class); $this->setExpectedException(Exception::class, 'No database', 700);
$this->expectExceptionMessage('Database connection failed');
$this->expectExceptionCode(700);
$oDbStub = $this->getMockBuilder(Nominatim\DB::class) // causes 'Non-static method should not be called statically, assuming $this from incompatible context'
->setMethods(array('connect')) // failure on travis
->getMock(); // $oDB = \DB::connect('', false); // returns a DB_Error instance
$oDbStub->method('connect') $oDB = new \DB_Error;
->will($this->returnCallback(function () { $oStatus = new Status($oDB);
throw new \Nominatim\DatabaseError('psql connection problem', 500, null, 'unknown database'); $this->assertEquals('No database', $oStatus->status());
}));
$oDB = null;
$oStatus = new Status($oDbStub); $oStatus = new Status($oDB);
$this->assertEquals('No database', $oStatus->status()); $this->assertEquals('No database', $oStatus->status());
} }
public function testModuleFail() public function testModuleFail()
{ {
$this->expectException(\Exception::class); $this->setExpectedException(Exception::class, 'Module call failed', 702);
$this->expectExceptionMessage('Module call failed');
$this->expectExceptionCode(702);
// stub has getOne method but doesn't return anything // stub has getOne method but doesn't return anything
$oDbStub = $this->getMockBuilder(Nominatim\DB::class) $oDbStub = $this->getMock(\DB::class, array('getOne'));
->setMethods(array('connect', 'getOne'))
->getMock();
$oStatus = new Status($oDbStub); $oStatus = new Status($oDbStub);
$this->assertNull($oStatus->status()); $this->assertNull($oStatus->status());
@@ -59,13 +52,9 @@ class StatusTest extends \PHPUnit\Framework\TestCase
public function testWordIdQueryFail() public function testWordIdQueryFail()
{ {
$this->expectException(\Exception::class); $this->setExpectedException(Exception::class, 'No value', 704);
$this->expectExceptionMessage('No value');
$this->expectExceptionCode(704);
$oDbStub = $this->getMockBuilder(Nominatim\DB::class) $oDbStub = $this->getMock(\DB::class, array('getOne'));
->setMethods(array('connect', 'getOne'))
->getMock();
// return no word_id // return no word_id
$oDbStub->method('getOne') $oDbStub->method('getOne')
@@ -81,9 +70,7 @@ class StatusTest extends \PHPUnit\Framework\TestCase
public function testOK() public function testOK()
{ {
$oDbStub = $this->getMockBuilder(Nominatim\DB::class) $oDbStub = $this->getMock(\DB::class, array('getOne'));
->setMethods(array('connect', 'getOne'))
->getMock();
$oDbStub->method('getOne') $oDbStub->method('getOne')
->will($this->returnCallback(function ($sql) { ->will($this->returnCallback(function ($sql) {
@@ -97,9 +84,7 @@ class StatusTest extends \PHPUnit\Framework\TestCase
public function testDataDate() public function testDataDate()
{ {
$oDbStub = $this->getMockBuilder(Nominatim\DB::class) $oDbStub = $this->getMock(\DB::class, array('getOne'));
->setMethods(array('getOne'))
->getMock();
$oDbStub->method('getOne') $oDbStub->method('getOne')
->willReturn(1519430221); ->willReturn(1519430221);

View File

@@ -2,16 +2,17 @@
namespace Nominatim; namespace Nominatim;
require_once(CONST_BasePath.'/lib/TokenList.php'); @define('CONST_BasePath', '../../');
require_once '../../lib/db.php';
require_once '../../lib/cmd.php';
require_once '../../lib/TokenList.php';
class TokenTest extends \PHPUnit\Framework\TestCase class TokenTest extends \PHPUnit\Framework\TestCase
{ {
protected function setUp() protected function setUp()
{ {
$this->oNormalizer = $this->getMockBuilder(\MockNormalizer::class) $this->oNormalizer = $this->getMock(\MockNormalizer::class, array('transliterate'));
->setMethods(array('transliterate'))
->getMock();
$this->oNormalizer->method('transliterate') $this->oNormalizer->method('transliterate')
->will($this->returnCallback(function ($text) { ->will($this->returnCallback(function ($text) {
return strtolower($text); return strtolower($text);
@@ -54,18 +55,7 @@ class TokenTest extends \PHPUnit\Framework\TestCase
{ {
$this->expectOutputRegex('/<p><tt>/'); $this->expectOutputRegex('/<p><tt>/');
$oDbStub = $this->getMockBuilder(Nominatim\DB::class) $oDbStub = $this->getMock(\DB::class, array('getAll'));
->setMethods(array('getAll', 'getDBQuotedList'))
->getMock();
$oDbStub->method('getDBQuotedList')
->will($this->returnCallback(function ($aVals) {
return array_map(function ($sVal) {
return "'".$sVal."'";
}, $aVals);
}));
$oDbStub->method('getAll') $oDbStub->method('getAll')
->will($this->returnCallback(function ($sql) { ->will($this->returnCallback(function ($sql) {
$aResults = array(); $aResults = array();

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