mirror of
https://github.com/osm-search/Nominatim.git
synced 2026-02-14 18:37:58 +00:00
Compare commits
359 Commits
v3.4.1
...
docs-3.5.x
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ba1ee6981 | ||
|
|
ff9ffa0351 | ||
|
|
4a6f7e3095 | ||
|
|
d15f57e589 | ||
|
|
96e7310aa6 | ||
|
|
5167168516 | ||
|
|
b052e5eb37 | ||
|
|
f6c00b9721 | ||
|
|
b62b7ffc0e | ||
|
|
d4f615232b | ||
|
|
dc854d379b | ||
|
|
e1b4c0a20e | ||
|
|
0d90f41f1c | ||
|
|
3dd182a915 | ||
|
|
a0e7d80daf | ||
|
|
d7e2f61e13 | ||
|
|
96ed4b02d7 | ||
|
|
cffc7c0121 | ||
|
|
d89000cc3d | ||
|
|
3661c75b39 | ||
|
|
3b20b11a9f | ||
|
|
cca366196d | ||
|
|
e09d444068 | ||
|
|
4956f5e710 | ||
|
|
5bebdfa434 | ||
|
|
aea915aa8d | ||
|
|
e0d29f398e | ||
|
|
c43b39bd88 | ||
|
|
8218da27b3 | ||
|
|
aa4bd00631 | ||
|
|
af6b9fdb39 | ||
|
|
c1b6493373 | ||
|
|
c386cca73f | ||
|
|
cadbdaff18 | ||
|
|
57510f517a | ||
|
|
3a2ddbe2e0 | ||
|
|
859347523f | ||
|
|
528fe6553f | ||
|
|
1faa0f4d41 | ||
|
|
82a11cae2d | ||
|
|
431948d768 | ||
|
|
f69c3d2b66 | ||
|
|
08b05964fa | ||
|
|
bd7f597682 | ||
|
|
6d4fbc9d32 | ||
|
|
124410a17b | ||
|
|
a543d57cbd | ||
|
|
8c3a0efe8b | ||
|
|
9e2841ad44 | ||
|
|
233e5f7c0e | ||
|
|
d5d9445cfd | ||
|
|
7be7417b5b | ||
|
|
0a14142156 | ||
|
|
a5e3785843 | ||
|
|
fc19ebb218 | ||
|
|
b45411f988 | ||
|
|
42f6371e47 | ||
|
|
be2aa6ab3a | ||
|
|
6e39ed9573 | ||
|
|
daf45a2993 | ||
|
|
d351b10fde | ||
|
|
0b21050904 | ||
|
|
644a7f524c | ||
|
|
53949ace36 | ||
|
|
14dba39157 | ||
|
|
43fd2a7423 | ||
|
|
4b0ac5356e | ||
|
|
c2f0d8e5ba | ||
|
|
0fb93b1e8a | ||
|
|
f94828c3f4 | ||
|
|
0e1e7c7df2 | ||
|
|
06110ba358 | ||
|
|
bae69f0102 | ||
|
|
77e7f4696b | ||
|
|
47fb2c9126 | ||
|
|
2ab9e4acd3 | ||
|
|
65ee7a8002 | ||
|
|
a5d0657d9b | ||
|
|
b8f01f91ca | ||
|
|
6cc6cf950c | ||
|
|
0b0349f746 | ||
|
|
2740974a13 | ||
|
|
97a9a262bb | ||
|
|
207efe700f | ||
|
|
e33315eaa6 | ||
|
|
5469d02d03 | ||
|
|
42c80893cb | ||
|
|
5c56ea3198 | ||
|
|
42f86329a9 | ||
|
|
08e273c0c7 | ||
|
|
5f8f98fa03 | ||
|
|
08c53ae27d | ||
|
|
f4f369895c | ||
|
|
38c21de0ee | ||
|
|
43cf36e0c7 | ||
|
|
9a9ff95989 | ||
|
|
fc40939775 | ||
|
|
553d8a828c | ||
|
|
80f7392fb1 | ||
|
|
61535c9972 | ||
|
|
b443c92a7a | ||
|
|
22da6c541d | ||
|
|
cd96354bc7 | ||
|
|
c6d859a08a | ||
|
|
ef47515420 | ||
|
|
a471a3d1b0 | ||
|
|
79a68fc2db | ||
|
|
93ddd46231 | ||
|
|
f5f0c197be | ||
|
|
4a30ec28b9 | ||
|
|
37ef9bb3d3 | ||
|
|
3e1d4a87fa | ||
|
|
320d46cc96 | ||
|
|
a06ceeef4c | ||
|
|
def573d7b4 | ||
|
|
7f7d29fdd1 | ||
|
|
c611d49941 | ||
|
|
11cd648699 | ||
|
|
98be5bf637 | ||
|
|
29df9771bb | ||
|
|
e68c1132da | ||
|
|
1047b1c191 | ||
|
|
9431e80eb4 | ||
|
|
178501de61 | ||
|
|
81c7f618fb | ||
|
|
eb2d816f2a | ||
|
|
244cb0e98c | ||
|
|
300ac4b77b | ||
|
|
0d189ac5df | ||
|
|
fed2c307a7 | ||
|
|
7aa2df5389 | ||
|
|
975ef0b305 | ||
|
|
e59146a733 | ||
|
|
8150c3602b | ||
|
|
ca8d776724 | ||
|
|
fdc40d5169 | ||
|
|
d0a97056c4 | ||
|
|
e98619f801 | ||
|
|
86eebc4305 | ||
|
|
4930f776fe | ||
|
|
19948c378a | ||
|
|
b3215b802d | ||
|
|
ed16d5b6aa | ||
|
|
7a94872413 | ||
|
|
98750922eb | ||
|
|
60c4c9ef2c | ||
|
|
101f04bbf2 | ||
|
|
4c593fa859 | ||
|
|
6603ad4006 | ||
|
|
d56c69dd01 | ||
|
|
e26a300c2f | ||
|
|
405482ede4 | ||
|
|
3db2b05069 | ||
|
|
ce5870223a | ||
|
|
9c1bb87493 | ||
|
|
1f7394dd54 | ||
|
|
bb569aa484 | ||
|
|
b0a350db37 | ||
|
|
78526a33b4 | ||
|
|
ab997b7fb1 | ||
|
|
6d431aebb7 | ||
|
|
a00ea23847 | ||
|
|
53ca751a02 | ||
|
|
8c444378bc | ||
|
|
55fdf0abda | ||
|
|
acd8ca2ebd | ||
|
|
06fdfad89e | ||
|
|
00ca493f33 | ||
|
|
b00d16fd7d | ||
|
|
03c373a4b3 | ||
|
|
bdaa39573f | ||
|
|
8a4c7f6e2b | ||
|
|
84ea0753d8 | ||
|
|
c1ef56c870 | ||
|
|
0e3252f045 | ||
|
|
65df218f91 | ||
|
|
5220a92be4 | ||
|
|
d643ca8dee | ||
|
|
de45152028 | ||
|
|
7fd9d0eeef | ||
|
|
d35a0b392e | ||
|
|
cbddfcde5b | ||
|
|
02ffa752ea | ||
|
|
6189e0c79b | ||
|
|
6ed6a0d447 | ||
|
|
484892ae97 | ||
|
|
027d9e938c | ||
|
|
e171f90d81 | ||
|
|
92c5d3b720 | ||
|
|
2a6e8ad68e | ||
|
|
55c8a0ac08 | ||
|
|
6073d948e6 | ||
|
|
b9171dd10b | ||
|
|
97b892fac2 | ||
|
|
b3fdf19b85 | ||
|
|
8c89e16ad2 | ||
|
|
960409c701 | ||
|
|
d1eeaa59a6 | ||
|
|
882f496e0a | ||
|
|
8b8aa1b4e6 | ||
|
|
932ac23f18 | ||
|
|
6c6560ca53 | ||
|
|
0698757e6e | ||
|
|
3a3f9b3496 | ||
|
|
97d87895bf | ||
|
|
c36fd72f99 | ||
|
|
57ae3d03a1 | ||
|
|
3737712044 | ||
|
|
8531339b4e | ||
|
|
540b12537a | ||
|
|
9e2fb45783 | ||
|
|
e7c128b973 | ||
|
|
d4a3470c9e | ||
|
|
4165b8c011 | ||
|
|
357ba2f64d | ||
|
|
3ce8818045 | ||
|
|
76082ac7cb | ||
|
|
4a451671d3 | ||
|
|
a3728b7188 | ||
|
|
a8711ab013 | ||
|
|
3e4754febd | ||
|
|
da1d661fa0 | ||
|
|
1801db523b | ||
|
|
2979c39628 | ||
|
|
bb9bb40287 | ||
|
|
8f6fdfeb0b | ||
|
|
b4e6d72fde | ||
|
|
a338ebfce0 | ||
|
|
4144364a15 | ||
|
|
11c0dd235b | ||
|
|
4a9502bf88 | ||
|
|
6c0d6d3178 | ||
|
|
0a26ca7104 | ||
|
|
2a15b2522f | ||
|
|
c11d1d78e9 | ||
|
|
7e51aa4cef | ||
|
|
9abb96fa6b | ||
|
|
879aafc916 | ||
|
|
5ec25122f6 | ||
|
|
9371b1aeb9 | ||
|
|
6f6d116451 | ||
|
|
3ff6eccfd7 | ||
|
|
5d1fa597ea | ||
|
|
3b6c2c9155 | ||
|
|
f863040b38 | ||
|
|
1033f8bce7 | ||
|
|
cf4dbbd681 | ||
|
|
6dccc693d0 | ||
|
|
c3dc66ce9c | ||
|
|
4856f56d61 | ||
|
|
2edc15dfb8 | ||
|
|
69e31baf68 | ||
|
|
586ff0c364 | ||
|
|
f3ba358d50 | ||
|
|
54bf4c3339 | ||
|
|
acda4344de | ||
|
|
6b0afd5d9b | ||
|
|
850910ed9e | ||
|
|
4ffa11a26c | ||
|
|
f5e60f8c40 | ||
|
|
ddaf1b79d4 | ||
|
|
d732dc3bb2 | ||
|
|
f0af5c5643 | ||
|
|
7a194789bc | ||
|
|
827d7a9a62 | ||
|
|
dae2761137 | ||
|
|
4304c1a7bb | ||
|
|
c537ea18a4 | ||
|
|
28fa7be75a | ||
|
|
f1a5862f3d | ||
|
|
4088e4e371 | ||
|
|
0ef6425847 | ||
|
|
7489deb1b7 | ||
|
|
2059e18e8b | ||
|
|
d11ee4c6d9 | ||
|
|
c74cbde329 | ||
|
|
20d541af06 | ||
|
|
2c163b3959 | ||
|
|
c6a7ef5574 | ||
|
|
7005c6af12 | ||
|
|
256986f01f | ||
|
|
33d322df9d | ||
|
|
631013be02 | ||
|
|
89a990e000 | ||
|
|
ccddd9d1de | ||
|
|
9587fc9909 | ||
|
|
22b7aed769 | ||
|
|
7db0da40ad | ||
|
|
2be70b2c36 | ||
|
|
0e03668cf2 | ||
|
|
28b89daa22 | ||
|
|
2bfa2f4292 | ||
|
|
3b22b9911b | ||
|
|
bc68ff1e43 | ||
|
|
f59af7483b | ||
|
|
394f85a96b | ||
|
|
626e3238f2 | ||
|
|
2051a84a09 | ||
|
|
f8bd4f5133 | ||
|
|
a4e514033d | ||
|
|
95f20ed7ab | ||
|
|
bfe92ea191 | ||
|
|
d3bacf475a | ||
|
|
3474464894 | ||
|
|
5b25bff2d8 | ||
|
|
c36896c524 | ||
|
|
d9fe25ac2e | ||
|
|
0bd006eef8 | ||
|
|
081d1f9779 | ||
|
|
546c975e28 | ||
|
|
05fb037edb | ||
|
|
5cdc196df1 | ||
|
|
0896c07972 | ||
|
|
be9f54d0a9 | ||
|
|
88fab44006 | ||
|
|
000fe3ddff | ||
|
|
f180f99a95 | ||
|
|
8d9aa9bf33 | ||
|
|
9fed91a47f | ||
|
|
12f830fbbb | ||
|
|
6d764a05b0 | ||
|
|
cd3ddec746 | ||
|
|
e4555a208d | ||
|
|
d53af96aa4 | ||
|
|
d1a9dc0f24 | ||
|
|
a1bcb28cea | ||
|
|
3fbba8b9db | ||
|
|
5fb850982a | ||
|
|
6f2e767c77 | ||
|
|
1ead5b0f3f | ||
|
|
eb6681d486 | ||
|
|
7503987630 | ||
|
|
1d337e8a76 | ||
|
|
f42e40712e | ||
|
|
b5fb8608ba | ||
|
|
dea1d67d03 | ||
|
|
9e6fc8f073 | ||
|
|
db6da75683 | ||
|
|
9cfd891fb9 | ||
|
|
f985680d2c | ||
|
|
8ae317e002 | ||
|
|
36e99c43ce | ||
|
|
eeb26aaa6f | ||
|
|
260dbe302a | ||
|
|
c297584726 | ||
|
|
5930383404 | ||
|
|
7395eb9791 | ||
|
|
e0de838b13 | ||
|
|
5292239714 | ||
|
|
b54fca4f7e | ||
|
|
26f47d2eb7 | ||
|
|
72c0898409 | ||
|
|
92d1f5122b | ||
|
|
314de3c3c0 | ||
|
|
544db43026 | ||
|
|
4aca3700b2 | ||
|
|
af833ff042 | ||
|
|
96c1a0a101 | ||
|
|
043f9d8298 |
@@ -1,11 +1,15 @@
|
||||
---
|
||||
sudo: required
|
||||
dist: xenial
|
||||
os: linux
|
||||
dist: bionic
|
||||
language: python
|
||||
python:
|
||||
- "3.6"
|
||||
addons:
|
||||
postgresql: "9.6"
|
||||
apt:
|
||||
packages:
|
||||
postgresql-server-dev-9.6
|
||||
postgresql-client-9.6
|
||||
git:
|
||||
depth: 3
|
||||
env:
|
||||
@@ -30,5 +34,6 @@ script:
|
||||
- if [[ $TEST_SUITE == "monaco" ]]; then wget --no-verbose --output-document=../data/monaco.osm.pbf http://download.geofabrik.de/europe/monaco-latest.osm.pbf; fi
|
||||
- if [[ $TEST_SUITE == "monaco" ]]; then /usr/bin/env php ./utils/setup.php --osm-file ../data/monaco.osm.pbf --osm2pgsql-cache 1000 --all 2>&1 | grep -v 'ETA (seconds)'; fi
|
||||
- if [[ $TEST_SUITE == "monaco" ]]; then /usr/bin/env php ./utils/specialphrases.php --wiki-import | psql -d test_api_nominatim >/dev/null; fi
|
||||
- if [[ $TEST_SUITE == "monaco" ]]; then /usr/bin/env php ./utils/check_import_finished.php; fi
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
196
CMakeLists.txt
196
CMakeLists.txt
@@ -19,8 +19,8 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||
project(nominatim)
|
||||
|
||||
set(NOMINATIM_VERSION_MAJOR 3)
|
||||
set(NOMINATIM_VERSION_MINOR 4)
|
||||
set(NOMINATIM_VERSION_PATCH 1)
|
||||
set(NOMINATIM_VERSION_MINOR 5)
|
||||
set(NOMINATIM_VERSION_PATCH 0)
|
||||
|
||||
set(NOMINATIM_VERSION "${NOMINATIM_VERSION_MAJOR}.${NOMINATIM_VERSION_MINOR}.${NOMINATIM_VERSION_PATCH}")
|
||||
|
||||
@@ -28,30 +28,40 @@ add_definitions(-DNOMINATIM_VERSION="${NOMINATIM_VERSION}")
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
#
|
||||
# Find external dependencies
|
||||
#
|
||||
# Configuration
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
set(BUILD_TESTS off CACHE BOOL "Build test suite" FORCE)
|
||||
set(WITH_LUA off CACHE BOOL "Build with lua support" FORCE)
|
||||
set(ONLY_DOCS off CACHE BOOL "Build documentation only")
|
||||
set(BUILD_IMPORTER on CACHE BOOL "Build everything for importing/updating the database")
|
||||
set(BUILD_API on CACHE BOOL "Build everything for the API server")
|
||||
set(BUILD_MODULE on CACHE BOOL "Build PostgreSQL module")
|
||||
set(BUILD_TESTS on CACHE BOOL "Build test suite")
|
||||
set(BUILD_DOCS on CACHE BOOL "Build documentation")
|
||||
set(BUILD_OSM2PGSQL on CACHE BOOL "Build osm2pgsql (expert only)")
|
||||
|
||||
if (NOT ONLY_DOCS)
|
||||
#-----------------------------------------------------------------------------
|
||||
# osm2pgsql (imports/updates only)
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
if (BUILD_IMPORTER AND BUILD_OSM2PGSQL)
|
||||
if (NOT EXISTS "${CMAKE_SOURCE_DIR}/osm2pgsql/CMakeLists.txt")
|
||||
message(FATAL_ERROR "The osm2pgsql directory is empty.\
|
||||
Did you forget to check out Nominatim recursively?\
|
||||
\nTry updating submodules with: git submodule update --init")
|
||||
endif()
|
||||
set(BUILD_TESTS_SAVED "${BUILD_TESTS}")
|
||||
set(BUILD_TESTS off)
|
||||
set(WITH_LUA off CACHE BOOL "")
|
||||
add_subdirectory(osm2pgsql)
|
||||
set(BUILD_TESTS ${BUILD_TESTS_SAVED})
|
||||
endif()
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
unset(PostgreSQL_TYPE_INCLUDE_DIR CACHE)
|
||||
set(PostgreSQL_TYPE_INCLUDE_DIR "/usr/include/")
|
||||
find_package(PostgreSQL REQUIRED)
|
||||
include_directories(${PostgreSQL_INCLUDE_DIRS})
|
||||
link_directories(${PostgreSQL_LIBRARY_DIRS})
|
||||
#-----------------------------------------------------------------------------
|
||||
# python and pyosmium (imports/updates only)
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
if (BUILD_IMPORTER)
|
||||
find_package(PythonInterp 3)
|
||||
|
||||
find_program(PYOSMIUM pyosmium-get-changes)
|
||||
if (NOT EXISTS "${PYOSMIUM}")
|
||||
@@ -61,115 +71,121 @@ if (NOT ONLY_DOCS)
|
||||
set(PYOSMIUM_PATH "${PYOSMIUM}")
|
||||
message(STATUS "Using pyosmium-get-changes at ${PYOSMIUM_PATH}")
|
||||
endif()
|
||||
|
||||
|
||||
find_program(PG_CONFIG pg_config)
|
||||
execute_process(COMMAND ${PG_CONFIG} --pgxs
|
||||
OUTPUT_VARIABLE PGXS
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
if (NOT EXISTS "${PGXS}")
|
||||
message(FATAL_ERROR "Postgresql server package not found.")
|
||||
endif()
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
find_package(BZip2 REQUIRED)
|
||||
|
||||
find_package(LibXml2 REQUIRED)
|
||||
include_directories(${LIBXML2_INCLUDE_DIR})
|
||||
|
||||
# Setting PHP binary variable as to command line (prevailing) or auto detect
|
||||
if (NOT PHP_BIN)
|
||||
find_program (PHP_BIN php)
|
||||
endif()
|
||||
# sanity check if PHP binary exists
|
||||
if (NOT EXISTS ${PHP_BIN})
|
||||
message(FATAL_ERROR "PHP binary not found. Install php or provide location with -DPHP_BIN=/path/php ")
|
||||
endif()
|
||||
message (STATUS "Using PHP binary " ${PHP_BIN})
|
||||
endif()
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
#
|
||||
# Setup settings and paths
|
||||
#
|
||||
# PHP
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
set(WEBSITESCRIPTS
|
||||
website/deletable.php
|
||||
website/details.php
|
||||
website/hierarchy.php
|
||||
website/lookup.php
|
||||
website/polygons.php
|
||||
website/reverse.php
|
||||
website/search.php
|
||||
website/status.php
|
||||
)
|
||||
# Setting PHP binary variable as to command line (prevailing) or auto detect
|
||||
|
||||
set(CUSTOMSCRIPTS
|
||||
utils/country_languages.php
|
||||
utils/importWikipedia.php
|
||||
utils/export.php
|
||||
utils/query.php
|
||||
utils/setup.php
|
||||
utils/specialphrases.php
|
||||
utils/update.php
|
||||
utils/warm.php
|
||||
if (NOT PHP_BIN)
|
||||
find_program (PHP_BIN php)
|
||||
endif()
|
||||
# sanity check if PHP binary exists
|
||||
if (NOT EXISTS ${PHP_BIN})
|
||||
message(FATAL_ERROR "PHP binary not found. Install php or provide location with -DPHP_BIN=/path/php ")
|
||||
endif()
|
||||
message (STATUS "Using PHP binary " ${PHP_BIN})
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# import scripts and utilities (importer only)
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
if (BUILD_IMPORTER)
|
||||
set(CUSTOMSCRIPTS
|
||||
utils/check_import_finished.php
|
||||
utils/country_languages.php
|
||||
utils/importWikipedia.php
|
||||
utils/export.php
|
||||
utils/query.php
|
||||
utils/setup.php
|
||||
utils/specialphrases.php
|
||||
utils/update.php
|
||||
utils/warm.php
|
||||
)
|
||||
|
||||
foreach (script_source ${CUSTOMSCRIPTS})
|
||||
configure_file(${PROJECT_SOURCE_DIR}/cmake/script.tmpl
|
||||
${PROJECT_BINARY_DIR}/${script_source})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# webserver scripts (API only)
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
if (BUILD_API)
|
||||
set(WEBSITESCRIPTS
|
||||
website/deletable.php
|
||||
website/details.php
|
||||
website/hierarchy.php
|
||||
website/lookup.php
|
||||
website/polygons.php
|
||||
website/reverse.php
|
||||
website/search.php
|
||||
website/status.php
|
||||
)
|
||||
|
||||
foreach (script_source ${CUSTOMSCRIPTS})
|
||||
configure_file(${PROJECT_SOURCE_DIR}/cmake/script.tmpl
|
||||
${PROJECT_BINARY_DIR}/${script_source})
|
||||
endforeach()
|
||||
foreach (script_source ${WEBSITESCRIPTS})
|
||||
configure_file(${PROJECT_SOURCE_DIR}/cmake/website.tmpl
|
||||
${PROJECT_BINARY_DIR}/${script_source})
|
||||
endforeach()
|
||||
|
||||
foreach (script_source ${WEBSITESCRIPTS})
|
||||
configure_file(${PROJECT_SOURCE_DIR}/cmake/website.tmpl
|
||||
${PROJECT_BINARY_DIR}/${script_source})
|
||||
endforeach()
|
||||
set(WEBPATHS css images js)
|
||||
|
||||
foreach (wp ${WEBPATHS})
|
||||
execute_process(
|
||||
COMMAND ln -sf ${PROJECT_SOURCE_DIR}/website/${wp} ${PROJECT_BINARY_DIR}/website/
|
||||
)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# default settings
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
configure_file(${PROJECT_SOURCE_DIR}/settings/defaults.php
|
||||
${PROJECT_BINARY_DIR}/settings/settings.php)
|
||||
|
||||
set(WEBPATHS css images js)
|
||||
|
||||
foreach (wp ${WEBPATHS})
|
||||
execute_process(
|
||||
COMMAND ln -sf ${PROJECT_SOURCE_DIR}/website/${wp} ${PROJECT_BINARY_DIR}/website/
|
||||
)
|
||||
endforeach()
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
#
|
||||
# Tests
|
||||
#
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
if (NOT ONLY_DOCS)
|
||||
if (BUILD_TESTS)
|
||||
include(CTest)
|
||||
|
||||
set(TEST_BDD db osm2pgsql api)
|
||||
|
||||
foreach (test ${TEST_BDD})
|
||||
add_test(NAME bdd_${test}
|
||||
COMMAND lettuce features/${test}
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests)
|
||||
COMMAND behave ${test}
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test/bdd)
|
||||
set_tests_properties(bdd_${test}
|
||||
PROPERTIES ENVIRONMENT "NOMINATIM_DIR=${PROJECT_BINARY_DIR}")
|
||||
endforeach()
|
||||
|
||||
add_test(NAME php
|
||||
COMMAND phpunit ./
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests-php)
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test/php)
|
||||
|
||||
add_test(NAME phpcs
|
||||
COMMAND phpcs --report-width=120 --colors lib website utils
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
||||
endif()
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Postgres module
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
if (NOT ONLY_DOCS)
|
||||
if (BUILD_MODULE)
|
||||
add_subdirectory(module)
|
||||
add_subdirectory(nominatim)
|
||||
endif()
|
||||
add_subdirectory(docs)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Documentation
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
if (BUILD_DOCS)
|
||||
add_subdirectory(docs)
|
||||
endif()
|
||||
|
||||
@@ -15,9 +15,9 @@ Please make sure to add the following information:
|
||||
* the result you are getting
|
||||
* the expected result, preferably a link to the OSM object you want to find,
|
||||
otherwise an address that is as precise as possible
|
||||
|
||||
To get the link to the OSM object, you can try the following:
|
||||
|
||||
|
||||
To get the link to the OSM object, you can try the following:
|
||||
|
||||
* go to https://openstreetmap.org
|
||||
* zoom to the area of the map where you expect the result and
|
||||
zoom in as much as possible
|
||||
@@ -26,7 +26,7 @@ Please make sure to add the following information:
|
||||
* find the object of interest in the list that appears on the left side
|
||||
* click on the object and report the URL back that the browser shows
|
||||
|
||||
### When Reporting Problems with your Installation...
|
||||
### When Reporting Bugs...
|
||||
|
||||
Please add the following information to your issue:
|
||||
|
||||
@@ -38,6 +38,9 @@ Please add the following information to your issue:
|
||||
if you run from the git repo, the output of `git rev-parse HEAD`)
|
||||
* (if applicable) exact command line of the command that was causing the issue
|
||||
|
||||
Bug reports that do not include extensive information about your system,
|
||||
about the problem and about what you have been trying to debug the problem
|
||||
will be closed.
|
||||
|
||||
## Workflow for Pull Requests
|
||||
|
||||
|
||||
42
ChangeLog
42
ChangeLog
@@ -1,8 +1,42 @@
|
||||
3.5.0
|
||||
|
||||
* structured select on HTML search page
|
||||
* new PHP Nominatim\Shell class to wrap shell escaping
|
||||
* remove polygon parameter from all API calls
|
||||
* improve handling of postcode areas
|
||||
* reorganise place linking algorithm, now using wikidata tag as well
|
||||
* remove linkees from search_name and larger_area tables
|
||||
* introduce country-specific address ranks
|
||||
* reorganise rank address computation
|
||||
* cleanup of partition function
|
||||
* improve parenting for large POIs
|
||||
* add support for Postgresql 12 and Postgis 3
|
||||
* add earlier cleanup when --drop is given, to reduce meory usage
|
||||
* remove use of place_id in URLs
|
||||
* replace C nominatim indexer with a simpler Python implementation
|
||||
* split up the huge sql/functions.sql file
|
||||
* move osm2pgsql tests to osm2pgsql
|
||||
* add new extratags style which imports all tags from OSM
|
||||
* add new script for checking the import after completion
|
||||
* update osm2pgsql, reducing memory usage
|
||||
* use new wikipedia importance and add processing of wikidata tags
|
||||
* add search form for details page
|
||||
* use ExtraDataPath for country_grid table
|
||||
* remove short_name from list of names to be displayed
|
||||
* split up CMakeFile, so that all parts can be built separately
|
||||
* update installation instructions for CentOS and Ubuntu
|
||||
* add script for importing/updating multiple country extracts
|
||||
* various documentation improvements
|
||||
|
||||
3.4.2
|
||||
|
||||
* fix security bug in /details endpoint where user input was not
|
||||
properly sanitized
|
||||
|
||||
3.4.1
|
||||
* update osm2pgsql
|
||||
* move deletion to copy thread (fixes deadlock in updates)
|
||||
* fix filtering where valid address objects got dropped
|
||||
* fix typo in import styles
|
||||
|
||||
* update osm2pgsql to fix hans during updates and lost address numbers
|
||||
during updates
|
||||
|
||||
3.4.0
|
||||
|
||||
|
||||
12
README.md
12
README.md
@@ -1,4 +1,4 @@
|
||||
[](https://travis-ci.org/openstreetmap/Nominatim)
|
||||
[](https://travis-ci.org/osm-search/Nominatim)
|
||||
|
||||
Nominatim
|
||||
=========
|
||||
@@ -19,8 +19,16 @@ https://nominatim.org/release-docs/develop/ .
|
||||
Installation
|
||||
============
|
||||
|
||||
**Nominatim is a complex piece of software and runs in a complex environment.
|
||||
Installing and running Nominatim is something for experienced system
|
||||
administrators only who can do some trouble-shooting themselves. We are sorry,
|
||||
but we can not provide installation support. We are all doing this in our free
|
||||
time and there is just so much of that time to go around. Do not open issues in
|
||||
our bug tracker if you need help. You can ask questions on the mailing list
|
||||
(see below) or on [help.openstreetmap.org](https://help.openstreetmap.org/).**
|
||||
|
||||
The latest stable release can be downloaded from https://nominatim.org.
|
||||
There you can also find [installation instructions for the release](https://nominatim.org/release-docs/latest/admin/Installation).
|
||||
There you can also find [installation instructions for the release](https://nominatim.org/release-docs/latest/admin/Installation), as well as an extensive [Troubleshooting/FAQ section](https://nominatim.org/release-docs/latest/admin/Faq/).
|
||||
|
||||
Detailed installation instructions for the development version can be
|
||||
found at [nominatim.org](https://nominatim.org/release-docs/develop/admin/Installation)
|
||||
|
||||
@@ -141,7 +141,7 @@ No. Long running Nominatim installations will differ once new import features (o
|
||||
bug fixes) get added since those usually only get applied to new/changed data.
|
||||
|
||||
Also this document skips the optional Wikipedia data import which affects ranking
|
||||
of search results. See [Nominatim installation](http://nominatim.org/release-docs/latest/Installation) for details.
|
||||
of search results. See [Nominatim installation](https://nominatim.org/release-docs/latest/admin/Installation) for details.
|
||||
|
||||
##### Why Ubuntu? Can I test CentOS/Fedora/CoreOS/FreeBSD?
|
||||
|
||||
|
||||
21
Vagrantfile
vendored
21
Vagrantfile
vendored
@@ -15,6 +15,15 @@ Vagrant.configure("2") do |config|
|
||||
end
|
||||
|
||||
config.vm.define "ubuntu", primary: true do |sub|
|
||||
sub.vm.box = "bento/ubuntu-20.04"
|
||||
sub.vm.provision :shell do |s|
|
||||
s.path = "vagrant/Install-on-Ubuntu-20.sh"
|
||||
s.privileged = false
|
||||
s.args = [checkout]
|
||||
end
|
||||
end
|
||||
|
||||
config.vm.define "ubuntu18", primary: true do |sub|
|
||||
sub.vm.box = "bento/ubuntu-18.04"
|
||||
sub.vm.provision :shell do |s|
|
||||
s.path = "vagrant/Install-on-Ubuntu-18.sh"
|
||||
@@ -61,6 +70,18 @@ Vagrant.configure("2") do |config|
|
||||
sub.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
end
|
||||
|
||||
config.vm.define "centos8" do |sub|
|
||||
sub.vm.box = "generic/centos8"
|
||||
sub.vm.provision :shell do |s|
|
||||
s.path = "vagrant/Install-on-Centos-8.sh"
|
||||
s.privileged = false
|
||||
s.args = "yes"
|
||||
end
|
||||
sub.vm.synced_folder ".", "/home/vagrant/Nominatim", disabled: true
|
||||
sub.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
end
|
||||
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.gui = false
|
||||
vb.memory = 2048
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
Convert [TIGER](https://www.census.gov/geographies/mapping-files/time-series/geo/tiger-line-file.html)/Line dataset of the US Census Bureau to SQL files which can be imported by Nominatim. The created tables in the Nominatim database are separate from OpenStreetMap tables and get queried at search time separately.
|
||||
|
||||
The dataset gets updated once per year. Downloading is prone to be slow (can take a full day) and converting them can take hours as well.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ This step downloads and converts [Wikipedia](https://dumps.wikimedia.org/) page
|
||||
|
||||
To download, convert, and import the data, then process summary statistics and compute importance scores, run:
|
||||
```
|
||||
./wikipedia_import.sh
|
||||
./import_wikipedia.sh
|
||||
```
|
||||
---
|
||||
Wikidata
|
||||
@@ -54,5 +54,5 @@ By including Wikidata in the wikipedia_articles table, new connections can be ma
|
||||
|
||||
To download, convert, and import the data, then process required items, run:
|
||||
```
|
||||
./wikidata_import.sh
|
||||
./import_wikidata.sh
|
||||
```
|
||||
|
||||
@@ -1,37 +1,66 @@
|
||||
#!/bin/bash
|
||||
|
||||
psqlcmd() {
|
||||
psql wikiprocessingdb
|
||||
psql --quiet wikiprocessingdb
|
||||
}
|
||||
|
||||
mysql2pgsqlcmd() {
|
||||
./mysql2pgsql.perl /dev/stdin /dev/stdout
|
||||
}
|
||||
|
||||
download() {
|
||||
echo "Downloading $1"
|
||||
wget --quiet --no-clobber --tries 3 "$1"
|
||||
}
|
||||
|
||||
# list the languages to process (refer to List of Wikipedias here: https://en.wikipedia.org/wiki/List_of_Wikipedias)
|
||||
|
||||
language=( "ar" "bg" "ca" "cs" "da" "de" "en" "es" "eo" "eu" "fa" "fr" "ko" "hi" "hr" "id" "it" "he" "lt" "hu" "ms" "nl" "ja" "no" "pl" "pt" "kk" "ro" "ru" "sk" "sl" "sr" "fi" "sv" "tr" "uk" "vi" "vo" "war" "zh" )
|
||||
# languages to process (refer to List of Wikipedias here: https://en.wikipedia.org/wiki/List_of_Wikipedias)
|
||||
# requires Bash 4.0
|
||||
readarray -t LANGUAGES < languages.txt
|
||||
|
||||
|
||||
# get a few wikidata dump tables
|
||||
|
||||
wget https://dumps.wikimedia.org/wikidatawiki/latest/wikidatawiki-latest-geo_tags.sql.gz
|
||||
wget https://dumps.wikimedia.org/wikidatawiki/latest/wikidatawiki-latest-page.sql.gz
|
||||
wget https://dumps.wikimedia.org/wikidatawiki/latest/wikidatawiki-latest-wb_items_per_site.sql.gz
|
||||
echo "====================================================================="
|
||||
echo "Download wikidata dump tables"
|
||||
echo "====================================================================="
|
||||
|
||||
# 114M wikidatawiki-latest-geo_tags.sql.gz
|
||||
# 1.7G wikidatawiki-latest-page.sql.gz
|
||||
# 1.2G wikidatawiki-latest-wb_items_per_site.sql.gz
|
||||
download https://dumps.wikimedia.org/wikidatawiki/latest/wikidatawiki-latest-geo_tags.sql.gz
|
||||
download https://dumps.wikimedia.org/wikidatawiki/latest/wikidatawiki-latest-page.sql.gz
|
||||
download https://dumps.wikimedia.org/wikidatawiki/latest/wikidatawiki-latest-wb_items_per_site.sql.gz
|
||||
|
||||
|
||||
# import wikidata tables
|
||||
|
||||
gzip -dc wikidatawiki-latest-geo_tags.sql.gz | mysql2pgsqlcmd | psqlcmd
|
||||
gzip -dc wikidatawiki-latest-page.sql.gz | mysql2pgsqlcmd | psqlcmd
|
||||
|
||||
echo "====================================================================="
|
||||
echo "Import wikidata dump tables"
|
||||
echo "====================================================================="
|
||||
|
||||
echo "Importing wikidatawiki-latest-geo_tags"
|
||||
gzip -dc wikidatawiki-latest-geo_tags.sql.gz | mysql2pgsqlcmd | psqlcmd
|
||||
|
||||
echo "Importing wikidatawiki-latest-page"
|
||||
gzip -dc wikidatawiki-latest-page.sql.gz | mysql2pgsqlcmd | psqlcmd
|
||||
|
||||
echo "Importing wikidatawiki-latest-wb_items_per_site"
|
||||
gzip -dc wikidatawiki-latest-wb_items_per_site.sql.gz | mysql2pgsqlcmd | psqlcmd
|
||||
|
||||
|
||||
# get wikidata places from wikidata query API
|
||||
|
||||
|
||||
|
||||
|
||||
echo "====================================================================="
|
||||
echo "Get wikidata places from wikidata query API"
|
||||
echo "====================================================================="
|
||||
|
||||
echo "Number of place types:"
|
||||
wc -l wikidata_place_types.txt
|
||||
|
||||
while read F ; do
|
||||
wget "https://query.wikidata.org/bigdata/namespace/wdq/sparql?format=json&query=SELECT ?item WHERE{?item wdt:P31*/wdt:P279*wd:$F;}" -O $F.json
|
||||
echo "Querying for place type $F..."
|
||||
wget --quiet "https://query.wikidata.org/bigdata/namespace/wdq/sparql?format=json&query=SELECT ?item WHERE{?item wdt:P31*/wdt:P279*wd:$F;}" -O $F.json
|
||||
jq -r '.results | .[] | .[] | [.item.value] | @csv' $F.json >> $F.txt
|
||||
awk -v qid=$F '{print $0 ","qid}' $F.txt | sed -e 's!"http://www.wikidata.org/entity/!!' | sed 's/"//g' >> $F.csv
|
||||
cat $F.csv >> wikidata_place_dump.csv
|
||||
@@ -39,57 +68,207 @@ while read F ; do
|
||||
done < wikidata_place_types.txt
|
||||
|
||||
|
||||
# import wikidata places
|
||||
|
||||
echo "CREATE TABLE wikidata_place_dump (item text, instance_of text);" | psqlcmd
|
||||
echo "COPY wikidata_place_dump (item, instance_of) FROM '/srv/nominatim/Nominatim/data-sources/wikipedia-wikidata/wikidata_place_dump.csv' DELIMITER ',' CSV;" | psqlcmd
|
||||
|
||||
echo "CREATE TABLE wikidata_place_type_levels (place_type text, level integer);" | psqlcmd
|
||||
echo "COPY wikidata_place_type_levels (place_type, level) FROM '/srv/nominatim/Nominatim/data-sources/wikipedia-wikidata/wikidata_place_type_levels.csv' DELIMITER ',' CSV HEADER;" | psqlcmd
|
||||
|
||||
|
||||
# create derived tables
|
||||
echo "====================================================================="
|
||||
echo "Import wikidata places"
|
||||
echo "====================================================================="
|
||||
|
||||
echo "CREATE TABLE geo_earth_primary AS SELECT gt_page_id, gt_lat, gt_lon FROM geo_tags WHERE gt_globe = 'earth' AND gt_primary = 1 AND NOT( gt_lat < -90 OR gt_lat > 90 OR gt_lon < -180 OR gt_lon > 180 OR gt_lat=0 OR gt_lon=0) ;" | psqlcmd
|
||||
echo "CREATE TABLE geo_earth_wikidata AS SELECT DISTINCT geo_earth_primary.gt_page_id, geo_earth_primary.gt_lat, geo_earth_primary.gt_lon, page.page_title, page.page_namespace FROM geo_earth_primary LEFT OUTER JOIN page ON (geo_earth_primary.gt_page_id = page.page_id) ORDER BY geo_earth_primary.gt_page_id;" | psqlcmd
|
||||
echo "CREATE TABLE wikidata_place_dump (
|
||||
item text,
|
||||
instance_of text
|
||||
);" | psqlcmd
|
||||
|
||||
echo "ALTER TABLE wikidata_place_dump ADD COLUMN ont_level integer, ADD COLUMN lat numeric(11,8), ADD COLUMN lon numeric(11,8);" | psqlcmd
|
||||
echo "UPDATE wikidata_place_dump SET ont_level = wikidata_place_type_levels.level FROM wikidata_place_type_levels WHERE wikidata_place_dump.instance_of = wikidata_place_type_levels.place_type;" | psqlcmd
|
||||
echo "COPY wikidata_place_dump (item, instance_of)
|
||||
FROM '/srv/nominatim/Nominatim/data-sources/wikipedia-wikidata/wikidata_place_dump.csv'
|
||||
DELIMITER ','
|
||||
CSV
|
||||
;" | psqlcmd
|
||||
|
||||
echo "CREATE TABLE wikidata_places AS SELECT DISTINCT ON (item) item, instance_of, MAX(ont_level) AS ont_level, lat, lon FROM wikidata_place_dump GROUP BY item, instance_of, ont_level, lat, lon ORDER BY item;" | psqlcmd
|
||||
echo "UPDATE wikidata_places SET lat = geo_earth_wikidata.gt_lat, lon = geo_earth_wikidata.gt_lon FROM geo_earth_wikidata WHERE wikidata_places.item = geo_earth_wikidata.page_title" | psqlcmd
|
||||
echo "CREATE TABLE wikidata_place_type_levels (
|
||||
place_type text,
|
||||
level integer
|
||||
);" | psqlcmd
|
||||
|
||||
echo "COPY wikidata_place_type_levels (place_type, level)
|
||||
FROM '/srv/nominatim/Nominatim/data-sources/wikipedia-wikidata/wikidata_place_type_levels.csv'
|
||||
DELIMITER ','
|
||||
CSV
|
||||
HEADER
|
||||
;" | psqlcmd
|
||||
|
||||
|
||||
# process language pages
|
||||
|
||||
echo "CREATE TABLE wikidata_pages (item text, instance_of text, lat numeric(11,8), lon numeric(11,8), ips_site_page text, language text );" | psqlcmd
|
||||
|
||||
for i in "${language[@]}"
|
||||
echo "====================================================================="
|
||||
echo "Create derived tables"
|
||||
echo "====================================================================="
|
||||
|
||||
echo "CREATE TABLE geo_earth_primary AS
|
||||
SELECT gt_page_id,
|
||||
gt_lat,
|
||||
gt_lon
|
||||
FROM geo_tags
|
||||
WHERE gt_globe = 'earth'
|
||||
AND gt_primary = 1
|
||||
AND NOT( gt_lat < -90
|
||||
OR gt_lat > 90
|
||||
OR gt_lon < -180
|
||||
OR gt_lon > 180
|
||||
OR gt_lat=0
|
||||
OR gt_lon=0)
|
||||
;" | psqlcmd
|
||||
|
||||
echo "CREATE TABLE geo_earth_wikidata AS
|
||||
SELECT DISTINCT geo_earth_primary.gt_page_id,
|
||||
geo_earth_primary.gt_lat,
|
||||
geo_earth_primary.gt_lon,
|
||||
page.page_title,
|
||||
page.page_namespace
|
||||
FROM geo_earth_primary
|
||||
LEFT OUTER JOIN page
|
||||
ON (geo_earth_primary.gt_page_id = page.page_id)
|
||||
ORDER BY geo_earth_primary.gt_page_id
|
||||
;" | psqlcmd
|
||||
|
||||
echo "ALTER TABLE wikidata_place_dump
|
||||
ADD COLUMN ont_level integer,
|
||||
ADD COLUMN lat numeric(11,8),
|
||||
ADD COLUMN lon numeric(11,8)
|
||||
;" | psqlcmd
|
||||
|
||||
echo "UPDATE wikidata_place_dump
|
||||
SET ont_level = wikidata_place_type_levels.level
|
||||
FROM wikidata_place_type_levels
|
||||
WHERE wikidata_place_dump.instance_of = wikidata_place_type_levels.place_type
|
||||
;" | psqlcmd
|
||||
|
||||
echo "CREATE TABLE wikidata_places
|
||||
AS
|
||||
SELECT DISTINCT ON (item) item,
|
||||
instance_of,
|
||||
MAX(ont_level) AS ont_level,
|
||||
lat,
|
||||
lon
|
||||
FROM wikidata_place_dump
|
||||
GROUP BY item,
|
||||
instance_of,
|
||||
ont_level,
|
||||
lat,
|
||||
lon
|
||||
ORDER BY item
|
||||
;" | psqlcmd
|
||||
|
||||
echo "UPDATE wikidata_places
|
||||
SET lat = geo_earth_wikidata.gt_lat,
|
||||
lon = geo_earth_wikidata.gt_lon
|
||||
FROM geo_earth_wikidata
|
||||
WHERE wikidata_places.item = geo_earth_wikidata.page_title
|
||||
;" | psqlcmd
|
||||
|
||||
|
||||
|
||||
|
||||
echo "====================================================================="
|
||||
echo "Process language pages"
|
||||
echo "====================================================================="
|
||||
|
||||
|
||||
echo "CREATE TABLE wikidata_pages (
|
||||
item text,
|
||||
instance_of text,
|
||||
lat numeric(11,8),
|
||||
lon numeric(11,8),
|
||||
ips_site_page text,
|
||||
language text
|
||||
);" | psqlcmd
|
||||
|
||||
for i in "${LANGUAGES[@]}"
|
||||
do
|
||||
echo "CREATE TABLE wikidata_${i}_pages as select wikidata_places.item, wikidata_places.instance_of, wikidata_places.lat, wikidata_places.lon, wb_items_per_site.ips_site_page FROM wikidata_places LEFT JOIN wb_items_per_site ON (CAST (( LTRIM(wikidata_places.item, 'Q')) AS INTEGER) = wb_items_per_site.ips_item_id) WHERE ips_site_id = '${i}wiki' AND LEFT(wikidata_places.item,1) = 'Q' order by wikidata_places.item;" | psqlcmd
|
||||
echo "ALTER TABLE wikidata_${i}_pages ADD COLUMN language text;" | psqlcmd
|
||||
echo "UPDATE wikidata_${i}_pages SET language = '${i}';" | psqlcmd
|
||||
echo "INSERT INTO wikidata_pages SELECT item, instance_of, lat, lon, ips_site_page, language FROM wikidata_${i}_pages;" | psqlcmd
|
||||
echo "CREATE TABLE wikidata_${i}_pages AS
|
||||
SELECT wikidata_places.item,
|
||||
wikidata_places.instance_of,
|
||||
wikidata_places.lat,
|
||||
wikidata_places.lon,
|
||||
wb_items_per_site.ips_site_page
|
||||
FROM wikidata_places
|
||||
LEFT JOIN wb_items_per_site
|
||||
ON (CAST (( LTRIM(wikidata_places.item, 'Q')) AS INTEGER) = wb_items_per_site.ips_item_id)
|
||||
WHERE ips_site_id = '${i}wiki'
|
||||
AND LEFT(wikidata_places.item,1) = 'Q'
|
||||
ORDER BY wikidata_places.item
|
||||
;" | psqlcmd
|
||||
|
||||
echo "ALTER TABLE wikidata_${i}_pages
|
||||
ADD COLUMN language text
|
||||
;" | psqlcmd
|
||||
|
||||
echo "UPDATE wikidata_${i}_pages
|
||||
SET language = '${i}'
|
||||
;" | psqlcmd
|
||||
|
||||
echo "INSERT INTO wikidata_pages
|
||||
SELECT item,
|
||||
instance_of,
|
||||
lat,
|
||||
lon,
|
||||
ips_site_page,
|
||||
language
|
||||
FROM wikidata_${i}_pages
|
||||
;" | psqlcmd
|
||||
done
|
||||
|
||||
echo "ALTER TABLE wikidata_pages ADD COLUMN wp_page_title text;" | psqlcmd
|
||||
echo "UPDATE wikidata_pages SET wp_page_title = REPLACE(ips_site_page, ' ', '_');" | psqlcmd
|
||||
echo "ALTER TABLE wikidata_pages DROP COLUMN ips_site_page;" | psqlcmd
|
||||
echo "ALTER TABLE wikidata_pages
|
||||
ADD COLUMN wp_page_title text
|
||||
;" | psqlcmd
|
||||
echo "UPDATE wikidata_pages
|
||||
SET wp_page_title = REPLACE(ips_site_page, ' ', '_')
|
||||
;" | psqlcmd
|
||||
echo "ALTER TABLE wikidata_pages
|
||||
DROP COLUMN ips_site_page
|
||||
;" | psqlcmd
|
||||
|
||||
|
||||
# add wikidata to wikipedia_article table
|
||||
|
||||
echo "UPDATE wikipedia_article SET lat = wikidata_pages.lat, lon = wikidata_pages.lon, wd_page_title = wikidata_pages.item, instance_of = wikidata_pages.instance_of FROM wikidata_pages WHERE wikipedia_article.language = wikidata_pages.language AND wikipedia_article.title = wikidata_pages.wp_page_title;" | psqlcmd
|
||||
echo "CREATE TABLE wikipedia_article_slim AS SELECT * FROM wikipedia_article WHERE wikidata_id IS NOT NULL;" | psqlcmd
|
||||
echo "ALTER TABLE wikipedia_article RENAME TO wikipedia_article_full;" | psqlcmd
|
||||
echo "ALTER TABLE wikipedia_article_slim RENAME TO wikipedia_article;" | psqlcmd
|
||||
|
||||
|
||||
# clean up intermediate tables
|
||||
echo "====================================================================="
|
||||
echo "Add wikidata to wikipedia_article table"
|
||||
echo "====================================================================="
|
||||
|
||||
echo "UPDATE wikipedia_article
|
||||
SET lat = wikidata_pages.lat,
|
||||
lon = wikidata_pages.lon,
|
||||
wd_page_title = wikidata_pages.item,
|
||||
instance_of = wikidata_pages.instance_of
|
||||
FROM wikidata_pages
|
||||
WHERE wikipedia_article.language = wikidata_pages.language
|
||||
AND wikipedia_article.title = wikidata_pages.wp_page_title
|
||||
;" | psqlcmd
|
||||
|
||||
echo "CREATE TABLE wikipedia_article_slim
|
||||
AS
|
||||
SELECT * FROM wikipedia_article
|
||||
WHERE wikidata_id IS NOT NULL
|
||||
;" | psqlcmd
|
||||
|
||||
echo "ALTER TABLE wikipedia_article
|
||||
RENAME TO wikipedia_article_full
|
||||
;" | psqlcmd
|
||||
|
||||
echo "ALTER TABLE wikipedia_article_slim
|
||||
RENAME TO wikipedia_article
|
||||
;" | psqlcmd
|
||||
|
||||
|
||||
|
||||
|
||||
echo "====================================================================="
|
||||
echo "Dropping intermediate tables"
|
||||
echo "====================================================================="
|
||||
|
||||
echo "DROP TABLE wikidata_place_dump;" | psqlcmd
|
||||
echo "DROP TABLE geo_earth_primary;" | psqlcmd
|
||||
for i in "${language[@]}"
|
||||
for i in "${LANGUAGES[@]}"
|
||||
do
|
||||
echo "DROP TABLE wikidata_${i}_pages;" | psqlcmd
|
||||
done
|
||||
|
||||
@@ -1,77 +1,297 @@
|
||||
#!/bin/bash
|
||||
|
||||
psqlcmd() {
|
||||
psql wikiprocessingdb
|
||||
psql --quiet wikiprocessingdb |& \
|
||||
grep -v 'does not exist, skipping' |& \
|
||||
grep -v 'violates check constraint' |& \
|
||||
grep -vi 'Failing row contains'
|
||||
}
|
||||
|
||||
mysql2pgsqlcmd() {
|
||||
./mysql2pgsql.perl /dev/stdin /dev/stdout
|
||||
./mysql2pgsql.perl --nodrop /dev/stdin /dev/stdout
|
||||
}
|
||||
|
||||
download() {
|
||||
echo "Downloading $1"
|
||||
wget --quiet --no-clobber --tries=3 "$1"
|
||||
}
|
||||
|
||||
|
||||
# list the languages to process (refer to List of Wikipedias here: https://en.wikipedia.org/wiki/List_of_Wikipedias)
|
||||
|
||||
language=( "ar" "bg" "ca" "cs" "da" "de" "en" "es" "eo" "eu" "fa" "fr" "ko" "hi" "hr" "id" "it" "he" "lt" "hu" "ms" "nl" "ja" "no" "pl" "pt" "kk" "ro" "ru" "sk" "sl" "sr" "fi" "sv" "tr" "uk" "vi" "vo" "war" "zh" )
|
||||
# languages to process (refer to List of Wikipedias here: https://en.wikipedia.org/wiki/List_of_Wikipedias)
|
||||
# requires Bash 4.0
|
||||
readarray -t LANGUAGES < languages.txt
|
||||
|
||||
|
||||
# create wikipedia calculation tables
|
||||
|
||||
echo "CREATE TABLE linkcounts (language text, title text, count integer, sumcount integer, lat double precision, lon double precision);" | psqlcmd
|
||||
echo "CREATE TABLE wikipedia_article (language text NOT NULL, title text NOT NULL, langcount integer, othercount integer, totalcount integer, lat double precision, lon double precision, importance double precision, title_en text, osm_type character(1), osm_id bigint );" | psqlcmd
|
||||
echo "CREATE TABLE wikipedia_redirect (language text, from_title text, to_title text );" | psqlcmd
|
||||
echo "====================================================================="
|
||||
echo "Create wikipedia calculation tables"
|
||||
echo "====================================================================="
|
||||
|
||||
echo "CREATE TABLE linkcounts (
|
||||
language text,
|
||||
title text,
|
||||
count integer,
|
||||
sumcount integer,
|
||||
lat double precision,
|
||||
lon double precision
|
||||
);" | psqlcmd
|
||||
|
||||
echo "CREATE TABLE wikipedia_article (
|
||||
language text NOT NULL,
|
||||
title text NOT NULL,
|
||||
langcount integer,
|
||||
othercount integer,
|
||||
totalcount integer,
|
||||
lat double precision,
|
||||
lon double precision,
|
||||
importance double precision,
|
||||
title_en text,
|
||||
osm_type character(1),
|
||||
osm_id bigint
|
||||
);" | psqlcmd
|
||||
|
||||
echo "CREATE TABLE wikipedia_redirect (
|
||||
language text,
|
||||
from_title text,
|
||||
to_title text
|
||||
);" | psqlcmd
|
||||
|
||||
|
||||
# download individual wikipedia language tables
|
||||
|
||||
for i in "${language[@]}"
|
||||
|
||||
|
||||
echo "====================================================================="
|
||||
echo "Download individual wikipedia language tables"
|
||||
echo "====================================================================="
|
||||
|
||||
|
||||
for i in "${LANGUAGES[@]}"
|
||||
do
|
||||
wget https://dumps.wikimedia.org/${i}wiki/latest/${i}wiki-latest-page.sql.gz
|
||||
wget https://dumps.wikimedia.org/${i}wiki/latest/${i}wiki-latest-pagelinks.sql.gz
|
||||
wget https://dumps.wikimedia.org/${i}wiki/latest/${i}wiki-latest-langlinks.sql.gz
|
||||
wget https://dumps.wikimedia.org/${i}wiki/latest/${i}wiki-latest-redirect.sql.gz
|
||||
echo "Language: $i"
|
||||
|
||||
# english is the largest
|
||||
# 1.7G enwiki-latest-page.sql.gz
|
||||
# 6.2G enwiki-latest-pagelinks.sql.gz
|
||||
# 355M enwiki-latest-langlinks.sql.gz
|
||||
# 128M enwiki-latest-redirect.sql.gz
|
||||
|
||||
# example of smaller languge turkish
|
||||
# 53M trwiki-latest-page.sql.gz
|
||||
# 176M trwiki-latest-pagelinks.sql.gz
|
||||
# 106M trwiki-latest-langlinks.sql.gz
|
||||
# 3.2M trwiki-latest-redirect.sql.gz
|
||||
|
||||
download https://dumps.wikimedia.org/${i}wiki/latest/${i}wiki-latest-page.sql.gz
|
||||
download https://dumps.wikimedia.org/${i}wiki/latest/${i}wiki-latest-pagelinks.sql.gz
|
||||
download https://dumps.wikimedia.org/${i}wiki/latest/${i}wiki-latest-langlinks.sql.gz
|
||||
download https://dumps.wikimedia.org/${i}wiki/latest/${i}wiki-latest-redirect.sql.gz
|
||||
done
|
||||
|
||||
|
||||
# import individual wikipedia language tables
|
||||
|
||||
for i in "${language[@]}"
|
||||
|
||||
|
||||
echo "====================================================================="
|
||||
echo "Import individual wikipedia language tables"
|
||||
echo "====================================================================="
|
||||
|
||||
for i in "${LANGUAGES[@]}"
|
||||
do
|
||||
gzip -dc ${i}wiki-latest-pagelinks.sql.gz | sed "s/\`pagelinks\`/\`${i}pagelinks\`/g" | mysql2pgsqlcmd | psqlcmd
|
||||
gzip -dc ${i}wiki-latest-page.sql.gz | sed "s/\`page\`/\`${i}page\`/g" | mysql2pgsqlcmd | psqlcmd
|
||||
gzip -dc ${i}wiki-latest-langlinks.sql.gz | sed "s/\`langlinks\`/\`${i}langlinks\`/g" | mysql2pgsqlcmd | psqlcmd
|
||||
gzip -dc ${i}wiki-latest-redirect.sql.gz | sed "s/\`redirect\`/\`${i}redirect\`/g" | mysql2pgsqlcmd | psqlcmd
|
||||
echo "Language: $i"
|
||||
|
||||
# We pre-create the table schema. This allows us to
|
||||
# 1. Skip index creation. Most queries we do are full table scans
|
||||
# 2. Add constrain to only import namespace=0 (wikipedia articles)
|
||||
# Both cuts down data size considerably (50%+)
|
||||
|
||||
echo "Importing ${i}wiki-latest-pagelinks"
|
||||
|
||||
echo "DROP TABLE IF EXISTS ${i}pagelinks;" | psqlcmd
|
||||
echo "CREATE TABLE ${i}pagelinks (
|
||||
pl_from int NOT NULL DEFAULT '0',
|
||||
pl_namespace int NOT NULL DEFAULT '0',
|
||||
pl_title text NOT NULL DEFAULT '',
|
||||
pl_from_namespace int NOT NULL DEFAULT '0'
|
||||
);" | psqlcmd
|
||||
|
||||
time \
|
||||
gzip -dc ${i}wiki-latest-pagelinks.sql.gz | \
|
||||
sed "s/\`pagelinks\`/\`${i}pagelinks\`/g" | \
|
||||
mysql2pgsqlcmd | \
|
||||
grep -v '^CREATE INDEX ' | \
|
||||
psqlcmd
|
||||
|
||||
|
||||
|
||||
|
||||
echo "Importing ${i}wiki-latest-page"
|
||||
|
||||
# autoincrement serial8 4byte
|
||||
echo "DROP TABLE IF EXISTS ${i}page;" | psqlcmd
|
||||
echo "CREATE TABLE ${i}page (
|
||||
page_id int NOT NULL,
|
||||
page_namespace int NOT NULL DEFAULT '0',
|
||||
page_title text NOT NULL DEFAULT '',
|
||||
page_restrictions text NOT NULL,
|
||||
page_is_redirect smallint NOT NULL DEFAULT '0',
|
||||
page_is_new smallint NOT NULL DEFAULT '0',
|
||||
page_random double precision NOT NULL DEFAULT '0',
|
||||
page_touched text NOT NULL DEFAULT '',
|
||||
page_links_updated text DEFAULT NULL,
|
||||
page_latest int NOT NULL DEFAULT '0',
|
||||
page_len int NOT NULL DEFAULT '0',
|
||||
page_content_model text DEFAULT NULL,
|
||||
page_lang text DEFAULT NULL
|
||||
);" | psqlcmd
|
||||
|
||||
time \
|
||||
gzip -dc ${i}wiki-latest-page.sql.gz | \
|
||||
sed "s/\`page\`/\`${i}page\`/g" | \
|
||||
mysql2pgsqlcmd | \
|
||||
grep -v '^CREATE INDEX ' | \
|
||||
psqlcmd
|
||||
|
||||
|
||||
|
||||
|
||||
echo "Importing ${i}wiki-latest-langlinks"
|
||||
|
||||
echo "DROP TABLE IF EXISTS ${i}langlinks;" | psqlcmd
|
||||
echo "CREATE TABLE ${i}langlinks (
|
||||
ll_from int NOT NULL DEFAULT '0',
|
||||
ll_lang text NOT NULL DEFAULT '',
|
||||
ll_title text NOT NULL DEFAULT ''
|
||||
);" | psqlcmd
|
||||
|
||||
time \
|
||||
gzip -dc ${i}wiki-latest-langlinks.sql.gz | \
|
||||
sed "s/\`langlinks\`/\`${i}langlinks\`/g" | \
|
||||
mysql2pgsqlcmd | \
|
||||
grep -v '^CREATE INDEX ' | \
|
||||
psqlcmd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
echo "Importing ${i}wiki-latest-redirect"
|
||||
|
||||
echo "DROP TABLE IF EXISTS ${i}redirect;" | psqlcmd
|
||||
echo "CREATE TABLE ${i}redirect (
|
||||
rd_from int NOT NULL DEFAULT '0',
|
||||
rd_namespace int NOT NULL DEFAULT '0',
|
||||
rd_title text NOT NULL DEFAULT '',
|
||||
rd_interwiki text DEFAULT NULL,
|
||||
rd_fragment text DEFAULT NULL
|
||||
);" | psqlcmd
|
||||
|
||||
time \
|
||||
gzip -dc ${i}wiki-latest-redirect.sql.gz | \
|
||||
sed "s/\`redirect\`/\`${i}redirect\`/g" | \
|
||||
mysql2pgsqlcmd | \
|
||||
grep -v '^CREATE INDEX ' | \
|
||||
psqlcmd
|
||||
done
|
||||
|
||||
|
||||
# process language tables and associated pagelink counts
|
||||
|
||||
for i in "${language[@]}"
|
||||
|
||||
|
||||
echo "====================================================================="
|
||||
echo "Process language tables and associated pagelink counts"
|
||||
echo "====================================================================="
|
||||
|
||||
|
||||
for i in "${LANGUAGES[@]}"
|
||||
do
|
||||
echo "create table ${i}pagelinkcount as select pl_title as title,count(*) as count from ${i}pagelinks where pl_namespace = 0 group by pl_title;" | psqlcmd
|
||||
echo "insert into linkcounts select '${i}',pl_title,count(*) from ${i}pagelinks where pl_namespace = 0 group by pl_title;" | psqlcmd
|
||||
echo "insert into wikipedia_redirect select '${i}',page_title,rd_title from ${i}redirect join ${i}page on (rd_from = page_id) where page_namespace = 0 and rd_namespace = 0;" | psqlcmd
|
||||
echo "alter table ${i}pagelinkcount add column othercount integer;" | psqlcmd
|
||||
echo "update ${i}pagelinkcount set othercount = 0;" | psqlcmd
|
||||
for j in "${language[@]}"
|
||||
echo "Language: $i"
|
||||
|
||||
echo "CREATE TABLE ${i}pagelinkcount
|
||||
AS
|
||||
SELECT pl_title AS title,
|
||||
COUNT(*) AS count,
|
||||
0::bigint as othercount
|
||||
FROM ${i}pagelinks
|
||||
WHERE pl_namespace = 0
|
||||
GROUP BY pl_title
|
||||
;" | psqlcmd
|
||||
|
||||
echo "INSERT INTO linkcounts
|
||||
SELECT '${i}',
|
||||
pl_title,
|
||||
COUNT(*)
|
||||
FROM ${i}pagelinks
|
||||
WHERE pl_namespace = 0
|
||||
GROUP BY pl_title
|
||||
;" | psqlcmd
|
||||
|
||||
echo "INSERT INTO wikipedia_redirect
|
||||
SELECT '${i}',
|
||||
page_title,
|
||||
rd_title
|
||||
FROM ${i}redirect
|
||||
JOIN ${i}page ON (rd_from = page_id)
|
||||
WHERE page_namespace = 0
|
||||
AND rd_namespace = 0
|
||||
;" | psqlcmd
|
||||
|
||||
done
|
||||
|
||||
|
||||
for i in "${LANGUAGES[@]}"
|
||||
do
|
||||
for j in "${LANGUAGES[@]}"
|
||||
do
|
||||
echo "update ${i}pagelinkcount set othercount = ${i}pagelinkcount.othercount + x.count from (select page_title as title,count from ${i}langlinks join ${i}page on (ll_from = page_id) join ${j}pagelinkcount on (ll_lang = '${j}' and ll_title = title)) as x where x.title = ${i}pagelinkcount.title;" | psqlcmd
|
||||
echo "UPDATE ${i}pagelinkcount
|
||||
SET othercount = ${i}pagelinkcount.othercount + x.count
|
||||
FROM (
|
||||
SELECT page_title AS title,
|
||||
count
|
||||
FROM ${i}langlinks
|
||||
JOIN ${i}page ON (ll_from = page_id)
|
||||
JOIN ${j}pagelinkcount ON (ll_lang = '${j}' AND ll_title = title)
|
||||
) AS x
|
||||
WHERE x.title = ${i}pagelinkcount.title
|
||||
;" | psqlcmd
|
||||
done
|
||||
echo "insert into wikipedia_article select '${i}', title, count, othercount, count+othercount from ${i}pagelinkcount;" | psqlcmd
|
||||
|
||||
echo "INSERT INTO wikipedia_article
|
||||
SELECT '${i}',
|
||||
title,
|
||||
count,
|
||||
othercount,
|
||||
count + othercount
|
||||
FROM ${i}pagelinkcount
|
||||
;" | psqlcmd
|
||||
done
|
||||
|
||||
|
||||
# calculate importance score for each wikipedia page
|
||||
|
||||
echo "update wikipedia_article set importance = log(totalcount)/log((select max(totalcount) from wikipedia_article))" | psqlcmd
|
||||
|
||||
|
||||
# clean up intermediate tables to conserve space
|
||||
|
||||
for i in "${language[@]}"
|
||||
echo "====================================================================="
|
||||
echo "Calculate importance score for each wikipedia page"
|
||||
echo "====================================================================="
|
||||
|
||||
echo "UPDATE wikipedia_article
|
||||
SET importance = LOG(totalcount)/LOG((SELECT MAX(totalcount) FROM wikipedia_article))
|
||||
;" | psqlcmd
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
echo "====================================================================="
|
||||
echo "Clean up intermediate tables to conserve space"
|
||||
echo "====================================================================="
|
||||
|
||||
for i in "${LANGUAGES[@]}"
|
||||
do
|
||||
echo "DROP TABLE ${i}pagelinks;" | psqlcmd
|
||||
echo "DROP TABLE ${i}page;" | psqlcmd
|
||||
echo "DROP TABLE ${i}langlinks;" | psqlcmd
|
||||
echo "DROP TABLE ${i}redirect;" | psqlcmd
|
||||
echo "DROP TABLE ${i}pagelinks;" | psqlcmd
|
||||
echo "DROP TABLE ${i}page;" | psqlcmd
|
||||
echo "DROP TABLE ${i}langlinks;" | psqlcmd
|
||||
echo "DROP TABLE ${i}redirect;" | psqlcmd
|
||||
echo "DROP TABLE ${i}pagelinkcount;" | psqlcmd
|
||||
done
|
||||
|
||||
echo "all done."
|
||||
|
||||
39
data-sources/wikipedia-wikidata/languages.txt
Normal file
39
data-sources/wikipedia-wikidata/languages.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
ar
|
||||
bg
|
||||
ca
|
||||
cs
|
||||
da
|
||||
de
|
||||
en
|
||||
es
|
||||
eo
|
||||
eu
|
||||
fa
|
||||
fr
|
||||
ko
|
||||
hi
|
||||
hr
|
||||
id
|
||||
it
|
||||
he
|
||||
lt
|
||||
hu
|
||||
ms
|
||||
nl
|
||||
ja
|
||||
no
|
||||
pl
|
||||
pt
|
||||
kk
|
||||
ro
|
||||
ru
|
||||
sk
|
||||
sl
|
||||
sr
|
||||
fi
|
||||
sv
|
||||
tr
|
||||
uk
|
||||
vi
|
||||
war
|
||||
zh
|
||||
@@ -7,21 +7,43 @@ configure_file(mkdocs.yml ../mkdocs.yml)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/appendix)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/data-sources)
|
||||
|
||||
ADD_CUSTOM_TARGET(doc
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/admin ${CMAKE_CURRENT_BINARY_DIR}/admin
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/develop ${CMAKE_CURRENT_BINARY_DIR}/develop
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/api ${CMAKE_CURRENT_BINARY_DIR}/api
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/index.md ${CMAKE_CURRENT_BINARY_DIR}/index.md
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/extra.css ${CMAKE_CURRENT_BINARY_DIR}/extra.css
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/data-sources/overview.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/overview.md
|
||||
set (DOC_SOURCES
|
||||
admin
|
||||
develop
|
||||
api
|
||||
index.md
|
||||
extra.css
|
||||
styles.css
|
||||
data-sources/overview.md
|
||||
)
|
||||
|
||||
foreach (src ${DOC_SOURCES})
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/${src} ${CMAKE_CURRENT_BINARY_DIR}/${src}
|
||||
)
|
||||
endforeach()
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/us-tiger/README.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/US-Tiger.md
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/gb-postcodes/README.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/GB-Postcodes.md
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/country-grid/README.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/Country-Grid.md
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/country-grid/mexico.quad.png ${CMAKE_CURRENT_BINARY_DIR}/data-sources/mexico.quad.png
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/wikipedia-wikidata/README.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/Wikipedia-Wikidata.md
|
||||
)
|
||||
|
||||
ADD_CUSTOM_TARGET(doc
|
||||
COMMAND ${CMAKE_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-Centos-8.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Centos-8.md
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Ubuntu-18.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Ubuntu-18.md
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Ubuntu-20.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Ubuntu-20.md
|
||||
COMMAND mkdocs build -d ${CMAKE_CURRENT_BINARY_DIR}/../site-html -f ${CMAKE_CURRENT_BINARY_DIR}/../mkdocs.yml
|
||||
)
|
||||
|
||||
|
||||
171
docs/admin/Advanced-Installations.md
Normal file
171
docs/admin/Advanced-Installations.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# Advanced installations
|
||||
|
||||
This page contains instructions for setting up multiple countries in
|
||||
your Nominatim database. It is assumed that you have already successfully
|
||||
installed the Nominatim software itself, if not return to the
|
||||
[installation page](Installation.md).
|
||||
|
||||
## Importing multiple regions
|
||||
|
||||
To import multiple regions in your database, you need to configure and run `utils/import_multiple_regions.sh` file. This script will set up the update directory which has the following structure:
|
||||
|
||||
```bash
|
||||
update
|
||||
├── europe
|
||||
│ ├── andorra
|
||||
│ │ └── sequence.state
|
||||
│ └── monaco
|
||||
│ └── sequence.state
|
||||
└── tmp
|
||||
├── combined.osm.pbf
|
||||
└── europe
|
||||
├── andorra-latest.osm.pbf
|
||||
└── monaco-latest.osm.pbf
|
||||
|
||||
|
||||
```
|
||||
|
||||
The `sequence.state` files will contain the sequence ID, which will be used by pyosmium to get updates. The tmp folder is used for import dump.
|
||||
|
||||
### Configuring multiple regions
|
||||
|
||||
The file `import_multiple_regions.sh` needs to be edited as per your requirement:
|
||||
|
||||
1. List of countries. eg:
|
||||
|
||||
COUNTRIES="europe/monaco europe/andorra"
|
||||
|
||||
2. Path to Build directory. eg:
|
||||
|
||||
NOMINATIMBUILD="/srv/nominatim/build"
|
||||
|
||||
3. Path to Update directory. eg:
|
||||
|
||||
UPDATEDIR="/srv/nominatim/update"
|
||||
|
||||
4. Replication URL. eg:
|
||||
|
||||
BASEURL="https://download.geofabrik.de"
|
||||
DOWNCOUNTRYPOSTFIX="-latest.osm.pbf"
|
||||
|
||||
!!! tip
|
||||
If your database already exists and you want to add more countries, replace the setting up part
|
||||
`${SETUPFILE} --osm-file ${UPDATEDIR}/tmp/combined.osm.pbf --all 2>&1`
|
||||
with `${UPDATEFILE} --import-file ${UPDATEDIR}/tmp/combined.osm.pbf 2>&1`.
|
||||
|
||||
### Setting up multiple regions
|
||||
|
||||
Run the following command from your Nominatim directory after configuring the file.
|
||||
|
||||
bash ./utils/import_multiple_regions.sh
|
||||
|
||||
!!! danger "Important"
|
||||
This file uses osmium-tool. It must be installed before executing the import script.
|
||||
Installation instructions can be found [here](https://osmcode.org/osmium-tool/manual.html#installation).
|
||||
|
||||
### Updating multiple regions
|
||||
|
||||
To import multiple regions in your database, you need to configure and run ```utils/update_database.sh```.
|
||||
This uses the update directory set up while setting up the DB.
|
||||
|
||||
### Configuring multiple regions
|
||||
|
||||
The file `update_database.sh` needs to be edited as per your requirement:
|
||||
|
||||
1. List of countries. eg:
|
||||
|
||||
COUNTRIES="europe/monaco europe/andorra"
|
||||
|
||||
2. Path to Build directory. eg:
|
||||
|
||||
NOMINATIMBUILD="/srv/nominatim/build"
|
||||
|
||||
3. Path to Update directory. eg:
|
||||
|
||||
UPDATEDIR="/srv/nominatim/update"
|
||||
|
||||
4. Replication URL. eg:
|
||||
|
||||
BASEURL="https://download.geofabrik.de"
|
||||
DOWNCOUNTRYPOSTFIX="-updates"
|
||||
|
||||
5. Followup can be set according to your installation. eg: For Photon,
|
||||
|
||||
FOLLOWUP="curl http://localhost:2322/nominatim-update"
|
||||
|
||||
will handle the indexing.
|
||||
|
||||
### Updating the database
|
||||
|
||||
Run the following command from your Nominatim directory after configuring the file.
|
||||
|
||||
bash ./utils/update_database.sh
|
||||
|
||||
This will get diffs from the replication server, import diffs and index the database. The default replication server in the script([Geofabrik](https://download.geofabrik.de)) provides daily updates.
|
||||
|
||||
## Importing Nominatim to an external PostgreSQL database
|
||||
|
||||
You can install Nominatim using a database that runs on a different server when
|
||||
you have physical access to the file system on the other server. Nominatim
|
||||
uses a custom normalization library that needs to be made accessible to the
|
||||
PostgreSQL server. This section explains how to set up the normalization
|
||||
library.
|
||||
|
||||
### Option 1: Compiling the library on the database server
|
||||
|
||||
The most sure way to get a working library is to compile it on the database
|
||||
server. From the prerequisites you need at least cmake, gcc and the
|
||||
PostgreSQL server package.
|
||||
|
||||
Clone or unpack the Nominatim source code, enter the source directory and
|
||||
create and enter a build directory.
|
||||
|
||||
```sh
|
||||
cd Nominatim
|
||||
mkdir build
|
||||
cd build
|
||||
```
|
||||
|
||||
Now configure cmake to only build the PostgreSQL module and build it:
|
||||
|
||||
```
|
||||
cmake -DBUILD_IMPORTER=off -DBUILD_API=off -DBUILD_TESTS=off -DBUILD_DOCS=off -DBUILD_OSM2PGSQL=off ..
|
||||
make
|
||||
```
|
||||
|
||||
When done, you find the normalization library in `build/module/nominatim.so`.
|
||||
Copy it to a place where it is readable and executable by the PostgreSQL server
|
||||
process.
|
||||
|
||||
### Option 2: Compiling the library on the import machine
|
||||
|
||||
You can also compile the normalization library on the machine from where you
|
||||
run the import.
|
||||
|
||||
!!! important
|
||||
You can only do this when the database server and the import machine have
|
||||
the same architecture and run the same version of Linux. Otherwise there is
|
||||
no guarantee that the compiled library is compatible with the PostgreSQL
|
||||
server running on the database server.
|
||||
|
||||
Make sure that the PostgreSQL server package is installed on the machine
|
||||
**with the same version as on the database server**. You do not need to install
|
||||
the PostgreSQL server itself.
|
||||
|
||||
Download and compile Nominatim as per standard instructions. Once done, you find
|
||||
the nomrmalization library in `build/module/nominatim.so`. Copy the file to
|
||||
the database server at a location where it is readable and executable by the
|
||||
PostgreSQL server process.
|
||||
|
||||
### Running the import
|
||||
|
||||
On the client side you now need to configure the import to point to the
|
||||
correct location of the library **on the database server**. Add the following
|
||||
line to your your `settings/local.php` file:
|
||||
|
||||
```php
|
||||
@define('CONST_Database_Module_Path', '<directory on the database server where nominatim.so resides>');
|
||||
```
|
||||
|
||||
Now change the `CONST_Database_DSN` to point to your remote server and continue
|
||||
to follow the [standard instructions for importing](/admin/Import).
|
||||
@@ -22,21 +22,38 @@ then you can resume with the following command:
|
||||
If the reported rank is 26 or higher, you can also safely add `--index-noanalyse`.
|
||||
|
||||
|
||||
### PostgreSQL crashed "invalid page in block"
|
||||
|
||||
Usually serious problem, can be a hardware issue, not all data written to disc
|
||||
for example. Check PostgreSQL log file and search PostgreSQL issues/mailing
|
||||
list for hints.
|
||||
|
||||
If it happened during index creation you can try rerunning the step with
|
||||
|
||||
```sh
|
||||
./utils/setup.php --create-search-indices --ignore-errors
|
||||
```
|
||||
|
||||
Otherwise it's best to start the full setup from the beginning.
|
||||
|
||||
|
||||
### PHP "open_basedir restriction in effect" warnings
|
||||
|
||||
PHP Warning: file_get_contents(): open_basedir restriction in effect.
|
||||
|
||||
You need to adjust the [open_basedir](https://www.php.net/manual/en/ini.core.php#ini.open-basedir) setting
|
||||
in your PHP configuration (`php.ini file`). By default this setting may look like this:
|
||||
You need to adjust the
|
||||
[open_basedir](https://www.php.net/manual/en/ini.core.php#ini.open-basedir)
|
||||
setting in your PHP configuration (`php.ini` file). By default this setting may
|
||||
look like this:
|
||||
|
||||
open_basedir = /srv/http/:/home/:/tmp/:/usr/share/pear/
|
||||
|
||||
Either add reported directories to the list or disable this setting temporarily by
|
||||
dding ";" at the beginning of the line. Don't forget to enable this setting again
|
||||
once you are done with the PHP command line operations.
|
||||
Either add reported directories to the list or disable this setting temporarily
|
||||
by adding ";" at the beginning of the line. Don't forget to enable this setting
|
||||
again once you are done with the PHP command line operations.
|
||||
|
||||
|
||||
### PHP timzeone warnings
|
||||
### PHP timezeone warnings
|
||||
|
||||
The Apache log may contain lots of PHP warnings like this:
|
||||
`PHP Warning: date_default_timezone_set() function.`
|
||||
@@ -66,7 +83,7 @@ server development libraries (`postgresql-server-dev-9.5` on Ubuntu)
|
||||
and recompile (`cmake .. && make`).
|
||||
|
||||
|
||||
## I see the error "ERROR: permission denied for language c"
|
||||
### I see the error "ERROR: permission denied for language c"
|
||||
|
||||
`nominatim.so`, written in C, is required to be installed on the database
|
||||
server. Some managed database (cloud) services like Amazon RDS do not allow
|
||||
@@ -89,6 +106,11 @@ If you are using a flatnode file, then it may also be that the underlying
|
||||
filesystem does not fully support 'mmap'. A notable candidate is virtualbox's
|
||||
vboxfs.
|
||||
|
||||
### I see the error: "clang: Command not found" on CentOS
|
||||
|
||||
On CentOS 7 users reported `/opt/rh/llvm-toolset-7/root/usr/bin/clang: Command not found`.
|
||||
Double-check clang is installed. Instead of `make` try running `make CLANG=true`.
|
||||
|
||||
### nominatim UPDATE failed: ERROR: buffer 179261 is not owned by resource owner Portal
|
||||
|
||||
Several users [reported this](https://github.com/openstreetmap/Nominatim/issues/1168) during the initial import of the database. It's
|
||||
@@ -107,10 +129,11 @@ to get the full error message.
|
||||
|
||||
`could not connect to server: No such file or directory`
|
||||
|
||||
On CentOS v7 the PostgreSQL server is started with `systemd`.
|
||||
Check if `/usr/lib/systemd/system/httpd.service` contains a line `PrivateTmp=true`.
|
||||
If so then Apache cannot see the `/tmp/.s.PGSQL.5432` file. It's a good security feature,
|
||||
so use the [preferred solution](../appendix/Install-on-Centos-7/#adding-selinux-security-settings).
|
||||
On CentOS v7 the PostgreSQL server is started with `systemd`. Check if
|
||||
`/usr/lib/systemd/system/httpd.service` contains a line `PrivateTmp=true`. If
|
||||
so then Apache cannot see the `/tmp/.s.PGSQL.5432` file. It's a good security
|
||||
feature, so use the
|
||||
[preferred solution](../appendix/Install-on-Centos-7/#adding-selinux-security-settings).
|
||||
|
||||
However, you can solve this the quick and dirty way by commenting out that line and then run
|
||||
|
||||
@@ -118,14 +141,12 @@ However, you can solve this the quick and dirty way by commenting out that line
|
||||
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"
|
||||
|
||||
The user the webserver, e.g. Apache, runs under needs to have access to the Nominatim database. You can find the user like [this](https://serverfault.com/questions/125865/finding-out-what-user-apache-is-running-as), for default Ubuntu operating system for example it's `www-data`.
|
||||
The user the webserver, e.g. Apache, runs under needs to have access to the
|
||||
Nominatim database. You can find the user like
|
||||
[this](https://serverfault.com/questions/125865/finding-out-what-user-apache-is-running-as),
|
||||
for default Ubuntu operating system for example it's `www-data`.
|
||||
|
||||
1. Repeat the `createuser` step of the installation instructions.
|
||||
|
||||
@@ -150,7 +171,8 @@ Example error message
|
||||
|
||||
The PostgreSQL database, i.e. user `postgres`, needs to have access to that file.
|
||||
|
||||
The permission need to be read & executable by everybody, e.g.
|
||||
The permission need to be read & executable by everybody, but not writeable
|
||||
by everybody, e.g.
|
||||
|
||||
```
|
||||
-rwxr-xr-x 1 nominatim nominatim 297984 build/module/nominatim.so
|
||||
@@ -161,22 +183,22 @@ Try `chmod a+r nominatim.so; chmod a+x nominatim.so`.
|
||||
When running SELinux, make sure that the
|
||||
[context is set up correctly](../appendix/Install-on-Centos-7/#adding-selinux-security-settings).
|
||||
|
||||
When you recently updated your operating system, updated PostgreSQL to
|
||||
a new version or moved files (e.g. the build directory) you should
|
||||
recreate `nominatim.so`. Try
|
||||
|
||||
```
|
||||
cd build
|
||||
rm -r module/
|
||||
cmake $main_Nominatim_path && make
|
||||
```
|
||||
|
||||
### Setup.php fails with "DB Error: extension not found"
|
||||
|
||||
Make sure you have the PostgreSQL extensions "hstore" and "postgis" installed.
|
||||
See the installation instruction for a full list of required packages.
|
||||
See the installation instructions for a full list of required packages.
|
||||
|
||||
|
||||
### Setup.php reports "Cannot redeclare getDB()"
|
||||
|
||||
`Cannot redeclare getDB() (previously declared in /your/path/Nominatim/lib/db.php:4)`
|
||||
|
||||
The message is a bit misleading as PHP needs to load the file `DB.php` and
|
||||
instead re-loads Nominatim's `db.php`. To solve this make sure you
|
||||
have the [Pear module 'DB'](https://pear.php.net/package/DB/) installed.
|
||||
|
||||
sudo pear install DB
|
||||
|
||||
### I forgot to delete the flatnodes file before starting an import.
|
||||
|
||||
That's fine. For each import the flatnodes file get overwritten.
|
||||
@@ -186,22 +208,6 @@ for more information.
|
||||
|
||||
## Running your own instance
|
||||
|
||||
### Can I import multiple countries and keep them up to date?
|
||||
|
||||
You should use the extracts and updates from https://download.geofabrik.de.
|
||||
For the initial import, download the countries you need and merge them.
|
||||
See [OSM Help](https://help.openstreetmap.org/questions/48843/merging-two-or-more-geographical-areas-to-import-two-or-more-osm-files-in-nominatim)
|
||||
for examples how to do that. Use the resulting single osm file when
|
||||
running `setup.php`.
|
||||
|
||||
For updates you need to download the change files for each country
|
||||
once per day and apply them **separately** using
|
||||
|
||||
./utils/update.php --import-diff <filename> --index
|
||||
|
||||
See [this issue](https://github.com/openstreetmap/Nominatim/issues/60#issuecomment-18679446)
|
||||
for a script that runs the updates using osmosis.
|
||||
|
||||
### Can I import negative OSM ids into Nominatim?
|
||||
|
||||
See [this question of Stackoverflow](https://help.openstreetmap.org/questions/64662/nominatim-flatnode-with-negative-id).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Importing and Updating the Database
|
||||
# Importing the Database
|
||||
|
||||
The following instructions explain how to create a Nominatim database
|
||||
from an OSM planet file and how to keep the database up to date. It
|
||||
@@ -29,11 +29,11 @@ Add to your `settings/local.php`:
|
||||
@define('CONST_Osm2pgsql_Flatnode_File', '/path/to/flatnode.file');
|
||||
|
||||
Replace the second part with a suitable path on your system and make sure
|
||||
the directory exists. There should be at least 40GB of free space.
|
||||
the directory exists. There should be at least 64GB of free space.
|
||||
|
||||
## Downloading additional data
|
||||
|
||||
### Wikipedia rankings
|
||||
### Wikipedia/Wikidata rankings
|
||||
|
||||
Wikipedia can be used as an optional auxiliary data source to help indicate
|
||||
the importance of OSM features. Nominatim will work without this information
|
||||
@@ -41,15 +41,15 @@ but it will improve the quality of the results if this is installed.
|
||||
This data is available as a binary download:
|
||||
|
||||
cd $NOMINATIM_SOURCE_DIR/data
|
||||
wget https://www.nominatim.org/data/wikipedia_article.sql.bin
|
||||
wget https://www.nominatim.org/data/wikipedia_redirect.sql.bin
|
||||
wget https://www.nominatim.org/data/wikimedia-importance.sql.gz
|
||||
|
||||
Combined the 2 files are around 1.5GB and add around 30GB to the install
|
||||
size of Nominatim. They also increase the install time by an hour or so.
|
||||
The file is about 400MB and adds around 4GB to Nominatim database.
|
||||
|
||||
*NOTE:* you'll need to download the Wikipedia rankings before performing
|
||||
the initial import of the data if you want the rankings applied to the
|
||||
loaded data.
|
||||
!!! tip
|
||||
If you forgot to download the wikipedia rankings, you can also add
|
||||
importances after the import. Download the files, then run
|
||||
`./utils/setup.php --import-wikipedia-articles`
|
||||
and `./utils/update.php --recompute-importance`.
|
||||
|
||||
### Great Britain, USA postcodes
|
||||
|
||||
@@ -64,7 +64,7 @@ involve a GB or US postcode. This data can be optionally downloaded:
|
||||
|
||||
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
|
||||
at least 64GB of RAM and around 800GB of SSD hard disks. Depending on your
|
||||
use case there are various ways to reduce the amount of data imported. This
|
||||
section discusses these methods. They can also be combined.
|
||||
|
||||
@@ -120,13 +120,16 @@ import styles available which only read selected data:
|
||||
Import all data necessary to compute addresses down to house number level.
|
||||
* **settings/import-full.style**
|
||||
Default style that also includes points of interest.
|
||||
* **settings/import-extratags.style**
|
||||
Like the full style but also adds most of the OSM tags into the extratags
|
||||
column.
|
||||
|
||||
The style can be changed with the configuration `CONST_Import_Style`.
|
||||
|
||||
To give you an idea of the impact of using the different styles, the table
|
||||
below gives rough estimates of the final database size after import of a
|
||||
2018 planet and after using the `--drop` option. It also shows the time
|
||||
needed for the import on a machine with 32GB RAM, 4 CPUS and SSDs. Note that
|
||||
needed for the import on a machine with 64GB RAM, 4 CPUS and SSDs. Note that
|
||||
the given sizes are just an estimate meant for comparison of style requirements.
|
||||
Your planet import is likely to be larger as the OSM data grows with time.
|
||||
|
||||
@@ -136,31 +139,68 @@ admin | 5h | 190 GB | 20 GB
|
||||
street | 42h | 400 GB | 180 GB
|
||||
address | 59h | 500 GB | 260 GB
|
||||
full | 80h | 575 GB | 300 GB
|
||||
extratags | 80h | 585 GB | 310 GB
|
||||
|
||||
You can also customize the styles further. For an description of the
|
||||
You can also customize the styles further. For a description of the
|
||||
style format see [the development section](../develop/Import.md).
|
||||
|
||||
## Initial import of the data
|
||||
|
||||
**Important:** first try the import with a small extract, for example from
|
||||
[Geofabrik](https://download.geofabrik.de).
|
||||
!!! danger "Important"
|
||||
First try the import with a small extract, for example from
|
||||
[Geofabrik](https://download.geofabrik.de).
|
||||
|
||||
Download the data to import and load the data with the following command
|
||||
from the build directory:
|
||||
|
||||
```sh
|
||||
./utils/setup.php --osm-file <data file> --all [--osm2pgsql-cache 28000] 2>&1 | tee setup.log
|
||||
./utils/setup.php --osm-file <data file> --all 2>&1 | tee setup.log
|
||||
```
|
||||
|
||||
The `--osm2pgsql-cache` parameter is optional but strongly recommended for
|
||||
planet imports. It sets the node cache size for the osm2pgsql import part
|
||||
(see `-C` parameter in osm2pgsql help). As a rule of thumb, this should be
|
||||
about the same size as the file you are importing but never more than
|
||||
2/3 of RAM available. If your machine starts swapping reduce the size.
|
||||
***Note for full planet imports:*** Even on a perfectly configured machine
|
||||
the import of a full planet takes at least 2 days. Once you see messages
|
||||
with `Rank .. ETA` appear, the indexing process has started. This part takes
|
||||
the most time. There are 30 ranks to process. Rank 26 and 30 are the most complex.
|
||||
They take each about a third of the total import time. If you have not reached
|
||||
rank 26 after two days of import, it is worth revisiting your system
|
||||
configuration as it may not be optimal for the import.
|
||||
|
||||
Computing word frequency for search terms can improve the performance of
|
||||
forward geocoding in particular under high load as it helps PostgreSQL's query
|
||||
planner to make the right decisions. To recompute word counts run:
|
||||
### Notes on memory usage
|
||||
|
||||
In the first step of the import Nominatim uses osm2pgsql to load the OSM data
|
||||
into the PostgreSQL database. This step is very demanding in terms of RAM usage.
|
||||
osm2pgsql and PostgreSQL are running in parallel at this point. PostgreSQL
|
||||
blocks at least the part of RAM that has been configured with the
|
||||
`shared_buffers` parameter during [PostgreSQL tuning](Installation#postgresql-tuning)
|
||||
and needs some memory on top of that. osm2pgsql needs at least 2GB of RAM for
|
||||
its internal data structures, potentially more when it has to process very large
|
||||
relations. In addition it needs to maintain a cache for node locations. The size
|
||||
of this cache can be configured with the parameter `--osm2pgsql-cache`.
|
||||
|
||||
When importing with a flatnode file, it is best to disable the node cache
|
||||
completely and leave the memory for the flatnode file. Nominatim will do this
|
||||
by default, so you do not need to configure anything in this case.
|
||||
|
||||
For imports without a flatnode file, set `--osm2pgsql-cache` approximately to
|
||||
the size of the OSM pbf file (in MB) you are importing. Make sure you leave
|
||||
enough RAM for PostgreSQL and osm2pgsql as mentioned above. If the system starts
|
||||
swapping or you are getting out-of-memory errors, reduce the cache size or
|
||||
even consider using a flatnode file.
|
||||
|
||||
### Verify import finished
|
||||
|
||||
Run this script to verify all required tables and indices got created successfully.
|
||||
|
||||
```sh
|
||||
./utils/check_import_finished.php
|
||||
```
|
||||
|
||||
|
||||
## Tuning the database
|
||||
|
||||
Accurate word frequency information for search terms helps PostgreSQL's query
|
||||
planner to make the right decisions. Recomputing them can improve the performance
|
||||
of forward geocoding in particular under high load. To recompute word counts run:
|
||||
|
||||
```sh
|
||||
./utils/update.php --recompute-word-counts
|
||||
@@ -178,12 +218,13 @@ you also need to enable these key phrases like this:
|
||||
./utils/specialphrases.php --wiki-import > specialphrases.sql
|
||||
psql -d nominatim -f specialphrases.sql
|
||||
|
||||
Note that this command downloads the phrases from the wiki link above.
|
||||
Note that this command downloads the phrases from the wiki link above. You
|
||||
need internet access for the step.
|
||||
|
||||
|
||||
## Installing Tiger housenumber data for the US
|
||||
|
||||
Nominatim is able to use the official [TIGER](https://www.census.gov/geo/maps-data/data/tiger.html)
|
||||
Nominatim is able to use the official [TIGER](https://www.census.gov/geographies/mapping-files/time-series/geo/tiger-line-file.html)
|
||||
address set to complement the OSM house number data in the US. You can add
|
||||
TIGER data to your own Nominatim instance by following these steps. The
|
||||
entire US adds about 10GB to your database.
|
||||
@@ -212,65 +253,3 @@ entire US adds about 10GB to your database.
|
||||
```
|
||||
|
||||
|
||||
## Updates
|
||||
|
||||
There are many different ways to update your Nominatim database.
|
||||
The following section describes how to keep it up-to-date with Pyosmium.
|
||||
For a list of other methods see the output of `./utils/update.php --help`.
|
||||
|
||||
#### Installing the newest version of Pyosmium
|
||||
|
||||
It is recommended to install Pyosmium via pip. Make sure to use python3.
|
||||
Run (as the same user who will later run the updates):
|
||||
|
||||
```sh
|
||||
pip3 install --user osmium
|
||||
```
|
||||
|
||||
Nominatim needs a tool called `pyosmium-get-updates` which comes with
|
||||
Pyosmium. You need to tell Nominatim where to find it. Add the
|
||||
following line to your `settings/local.php`:
|
||||
|
||||
@define('CONST_Pyosmium_Binary', '/home/user/.local/bin/pyosmium-get-changes');
|
||||
|
||||
The path above is fine if you used the `--user` parameter with pip.
|
||||
Replace `user` with your user name.
|
||||
|
||||
#### Setting up the update process
|
||||
|
||||
Next the update needs to be initialised. By default Nominatim is configured
|
||||
to update using the global minutely diffs.
|
||||
|
||||
If you want a different update source you will need to add some settings
|
||||
to `settings/local.php`. For example, to use the daily country extracts
|
||||
diffs for Ireland from Geofabrik add the following:
|
||||
|
||||
// base URL of the replication service
|
||||
@define('CONST_Replication_Url', 'https://download.geofabrik.de/europe/ireland-and-northern-ireland-updates');
|
||||
// How often upstream publishes diffs
|
||||
@define('CONST_Replication_Update_Interval', '86400');
|
||||
// How long to sleep if no update found yet
|
||||
@define('CONST_Replication_Recheck_Interval', '900');
|
||||
|
||||
To set up the update process now run the following command:
|
||||
|
||||
./utils/update.php --init-updates
|
||||
|
||||
It outputs the date where updates will start. Recheck that this date is
|
||||
what you expect.
|
||||
|
||||
The `--init-updates` command needs to be rerun whenever the replication service
|
||||
is changed.
|
||||
|
||||
#### Updating Nominatim
|
||||
|
||||
The following command will keep your database constantly up to date:
|
||||
|
||||
./utils/update.php --import-osmosis-all
|
||||
|
||||
(Note that even though the old name "import-osmosis-all" has been kept for compatibility reasons, Osmosis is not required to run this - it uses pyosmium behind the scenes.)
|
||||
|
||||
If you have imported multiple country extracts and want to keep them
|
||||
up-to-date, have a look at the script in
|
||||
[issue #60](https://github.com/openstreetmap/Nominatim/issues/60).
|
||||
|
||||
@@ -4,8 +4,9 @@ This page contains generic installation instructions for Nominatim and its
|
||||
prerequisites. There are also step-by-step instructions available for
|
||||
the following operating systems:
|
||||
|
||||
* [Ubuntu 20.04](../appendix/Install-on-Ubuntu-20.md)
|
||||
* [Ubuntu 18.04](../appendix/Install-on-Ubuntu-18.md)
|
||||
* [Ubuntu 16.04](../appendix/Install-on-Ubuntu-16.md)
|
||||
* [CentOS 8](../appendix/Install-on-Centos-8.md)
|
||||
* [CentOS 7.2](../appendix/Install-on-Centos-7.md)
|
||||
|
||||
These OS-specific instructions can also be found in executable form
|
||||
@@ -25,45 +26,47 @@ and can't offer support.
|
||||
For compiling:
|
||||
|
||||
* [cmake](https://cmake.org/)
|
||||
* [libxml2](http://xmlsoft.org/)
|
||||
* a recent C++ compiler
|
||||
|
||||
Nominatim comes with its own version of osm2pgsql. See the
|
||||
osm2pgsql README for additional dependencies required for compiling osm2pgsql.
|
||||
|
||||
For running tests:
|
||||
|
||||
* [behave](http://pythonhosted.org/behave/)
|
||||
* [Psycopg2](https://initd.org/psycopg)
|
||||
* [nose](https://nose.readthedocs.io)
|
||||
* [phpunit](https://phpunit.de)
|
||||
* [expat](https://libexpat.github.io/)
|
||||
* [proj](https://proj.org/)
|
||||
* [bzip2](http://www.bzip.org/)
|
||||
* [zlib](https://www.zlib.net/)
|
||||
* [Boost libraries](https://www.boost.org/), including system and filesystem
|
||||
* PostgreSQL client libraries
|
||||
* a recent C++ compiler (gcc 5+ or Clang 3.8+)
|
||||
|
||||
For running Nominatim:
|
||||
|
||||
* [PostgreSQL](https://www.postgresql.org) (9.3 or later)
|
||||
* [PostGIS](https://postgis.org) (2.2 or later)
|
||||
* [PostgreSQL](https://www.postgresql.org) (9.3+)
|
||||
* [PostGIS](https://postgis.net) (2.2+)
|
||||
* [Python 3](https://www.python.org/)
|
||||
* [Psycopg2](https://www.psycopg.org)
|
||||
* [PHP](https://php.net) (7.0 or later)
|
||||
* PHP-pgsql
|
||||
* PHP-intl (bundled with PHP)
|
||||
* [PEAR::DB](https://pear.php.net/package/DB)
|
||||
* a webserver (apache or nginx are recommended)
|
||||
|
||||
For running continuous updates:
|
||||
|
||||
* [pyosmium](https://osmcode.org/pyosmium/) (with Python 3)
|
||||
|
||||
For running tests:
|
||||
|
||||
* [behave](https://behave.readthedocs.io)
|
||||
* [nose](https://nose.readthedocs.io)
|
||||
* [phpunit](https://phpunit.de) >= 7.3
|
||||
|
||||
### Hardware
|
||||
|
||||
A minimum of 2GB of RAM is required or installation will fail. For a full
|
||||
planet import 32GB of RAM or more are strongly recommended.
|
||||
planet import 64GB of RAM or more are strongly recommended. Do not report
|
||||
out of memory problems if you have less than 64GB RAM.
|
||||
|
||||
For a full planet install you will need at least 700GB of hard disk space
|
||||
For a full planet install you will need at least 800GB of hard disk space
|
||||
(take into account that the OSM database is growing fast). SSD disks
|
||||
will help considerably to speed up import and queries.
|
||||
|
||||
On a 6-core machine with 32GB RAM and SSDs the import of a full planet takes
|
||||
a bit more than 2 days. Without SSDs 7-8 days are more realistic.
|
||||
|
||||
Even on a well configured machine the import of a full planet takes
|
||||
at least 2 days. Without SSDs 7-8 days are more realistic.
|
||||
|
||||
## Setup of the server
|
||||
|
||||
@@ -73,17 +76,30 @@ You might want to tune your PostgreSQL installation so that the later steps
|
||||
make best use of your hardware. You should tune the following parameters in
|
||||
your `postgresql.conf` file.
|
||||
|
||||
shared_buffers (2GB)
|
||||
maintenance_work_mem (10GB)
|
||||
work_mem (50MB)
|
||||
effective_cache_size (24GB)
|
||||
shared_buffers = 2GB
|
||||
maintenance_work_mem = (10GB)
|
||||
autovacuum_work_mem = 2GB
|
||||
work_mem = (50MB)
|
||||
effective_cache_size = (24GB)
|
||||
synchronous_commit = off
|
||||
checkpoint_segments = 100 # only for postgresql <= 9.4
|
||||
max_wal_size = 1GB # postgresql > 9.4
|
||||
checkpoint_timeout = 10min
|
||||
checkpoint_completion_target = 0.9
|
||||
|
||||
The numbers in brackets behind some parameters seem to work fine for
|
||||
32GB RAM machine. Adjust to your setup.
|
||||
64GB RAM machine. Adjust to your setup. A higher number for `max_wal_size`
|
||||
means that PostgreSQL needs to run checkpoints less often but it does require
|
||||
the additional space on your disk.
|
||||
|
||||
Autovacuum must not be switched off because it ensures that the
|
||||
tables are frequently analysed. If your machine has very little memory,
|
||||
you might consider setting:
|
||||
|
||||
autovacuum_max_workers = 1
|
||||
|
||||
and even reduce `autovacuum_work_mem` further. This will reduce the amount
|
||||
of memory that autovacuum takes away from the import process.
|
||||
|
||||
For the initial import, you should also set:
|
||||
|
||||
@@ -91,8 +107,8 @@ For the initial import, you should also set:
|
||||
full_page_writes = off
|
||||
|
||||
Don't forget to reenable them after the initial import or you risk database
|
||||
corruption. Autovacuum must not be switched off because it ensures that the
|
||||
tables are frequently analysed.
|
||||
corruption.
|
||||
|
||||
|
||||
### Webserver setup
|
||||
|
||||
@@ -105,13 +121,15 @@ from there.
|
||||
Make sure your Apache configuration contains the required permissions for the
|
||||
directory and create an alias:
|
||||
|
||||
<Directory "/srv/nominatim/build/website">
|
||||
Options FollowSymLinks MultiViews
|
||||
AddType text/html .php
|
||||
DirectoryIndex search.php
|
||||
Require all granted
|
||||
</Directory>
|
||||
Alias /nominatim /srv/nominatim/build/website
|
||||
``` apache
|
||||
<Directory "/srv/nominatim/build/website">
|
||||
Options FollowSymLinks MultiViews
|
||||
AddType text/html .php
|
||||
DirectoryIndex search.php
|
||||
Require all granted
|
||||
</Directory>
|
||||
Alias /nominatim /srv/nominatim/build/website
|
||||
```
|
||||
|
||||
`/srv/nominatim/build` should be replaced with the location of your
|
||||
build directory.
|
||||
@@ -139,20 +157,35 @@ follows:
|
||||
Tell nginx that php files are special and to fastcgi_pass to the php-fpm
|
||||
unix socket by adding the location definition to the default configuration.
|
||||
|
||||
root /srv/nominatim/build/website;
|
||||
index search.php index.html;
|
||||
location ~ [^/]\.php(/|$) {
|
||||
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
||||
if (!-f $document_root$fastcgi_script_name) {
|
||||
return 404;
|
||||
}
|
||||
fastcgi_pass unix:/var/run/php5-fpm.sock;
|
||||
fastcgi_index search.php;
|
||||
include fastcgi.conf;
|
||||
``` nginx
|
||||
root /srv/nominatim/build/website;
|
||||
index search.php;
|
||||
location / {
|
||||
try_files $uri $uri/ @php;
|
||||
}
|
||||
|
||||
location @php {
|
||||
fastcgi_param SCRIPT_FILENAME "$document_root$uri.php";
|
||||
fastcgi_param PATH_TRANSLATED "$document_root$uri.php";
|
||||
fastcgi_param QUERY_STRING $args;
|
||||
fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
||||
location ~ [^/]\.php(/|$) {
|
||||
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
||||
if (!-f $document_root$fastcgi_script_name) {
|
||||
return 404;
|
||||
}
|
||||
fastcgi_pass unix:/var/run/php7.3-fpm.sock;
|
||||
fastcgi_index search.php;
|
||||
include fastcgi.conf;
|
||||
}
|
||||
```
|
||||
|
||||
Restart the nginx and php5-fpm services and the website should now be available
|
||||
at `http://localhost/`.
|
||||
|
||||
|
||||
Now continue with [importing the database](Import-and-Update.md).
|
||||
Now continue with [importing the database](Import.md).
|
||||
|
||||
@@ -6,6 +6,39 @@ to newer versions of Nominatim.
|
||||
SQL statements should be executed from the PostgreSQL commandline. Execute
|
||||
`psql nominatim` to enter command line mode.
|
||||
|
||||
## 3.4.0 -> 3.5.0
|
||||
|
||||
### New Wikipedia/Wikidata importance tables
|
||||
|
||||
The `wikipedia_*` tables have a new format that also includes references to
|
||||
Wikidata. You need to update the computation functions and the tables as
|
||||
follows:
|
||||
|
||||
* download the new Wikipedia tables as described in the import section
|
||||
* reimport the tables: `./utils/setup.php --import-wikipedia-articles`
|
||||
* update the functions: `./utils/setup.php --create-functions --enable-diff-updates`
|
||||
* create a new lookup index:
|
||||
```
|
||||
CREATE INDEX idx_placex_wikidata on placex
|
||||
USING BTREE ((extratags -> 'wikidata'))
|
||||
WHERE extratags ? 'wikidata' and class = 'place' and osm_type = 'N' and rank_search < 26
|
||||
```
|
||||
* compute importance: `./utils/update.php --recompute-importance`
|
||||
|
||||
The last step takes about 10 hours on the full planet.
|
||||
|
||||
Remove one function (it will be recreated in the next step):
|
||||
|
||||
```sql
|
||||
DROP FUNCTION create_country(hstore,character varying);
|
||||
```
|
||||
|
||||
Finally, update all SQL functions:
|
||||
|
||||
```sh
|
||||
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
|
||||
```
|
||||
|
||||
## 3.3.0 -> 3.4.0
|
||||
|
||||
### Reorganisation of location_area_country table
|
||||
@@ -23,6 +56,12 @@ CREATE INDEX idx_location_area_country_geometry ON location_area_country USING G
|
||||
CREATE INDEX idx_location_area_country_place_id ON location_area_country USING BTREE (place_id);
|
||||
```
|
||||
|
||||
Finally, update all SQL functions:
|
||||
|
||||
```sh
|
||||
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
|
||||
```
|
||||
|
||||
## 3.2.0 -> 3.3.0
|
||||
|
||||
### New database connection string (DSN) format
|
||||
|
||||
67
docs/admin/Update.md
Normal file
67
docs/admin/Update.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Updating the Database
|
||||
|
||||
There are many different ways to update your Nominatim database.
|
||||
The following section describes how to keep it up-to-date with Pyosmium.
|
||||
For a list of other methods see the output of `./utils/update.php --help`.
|
||||
|
||||
!!! warning
|
||||
If you have configured a flatnode file for the import, then you
|
||||
need to keep this flatnode file around for updates as well.
|
||||
|
||||
#### Installing the newest version of Pyosmium
|
||||
|
||||
It is recommended to install Pyosmium via pip. Make sure to use python3.
|
||||
Run (as the same user who will later run the updates):
|
||||
|
||||
```sh
|
||||
pip3 install --user osmium
|
||||
```
|
||||
|
||||
Nominatim needs a tool called `pyosmium-get-changes` which comes with
|
||||
Pyosmium. You need to tell Nominatim where to find it. Add the
|
||||
following line to your `settings/local.php`:
|
||||
|
||||
@define('CONST_Pyosmium_Binary', '/home/user/.local/bin/pyosmium-get-changes');
|
||||
|
||||
The path above is fine if you used the `--user` parameter with pip.
|
||||
Replace `user` with your user name.
|
||||
|
||||
#### Setting up the update process
|
||||
|
||||
Next the update needs to be initialised. By default Nominatim is configured
|
||||
to update using the global minutely diffs.
|
||||
|
||||
If you want a different update source you will need to add some settings
|
||||
to `settings/local.php`. For example, to use the daily country extracts
|
||||
diffs for Ireland from Geofabrik add the following:
|
||||
|
||||
// base URL of the replication service
|
||||
@define('CONST_Replication_Url', 'https://download.geofabrik.de/europe/ireland-and-northern-ireland-updates');
|
||||
// How often upstream publishes diffs
|
||||
@define('CONST_Replication_Update_Interval', '86400');
|
||||
// How long to sleep if no update found yet
|
||||
@define('CONST_Replication_Recheck_Interval', '900');
|
||||
|
||||
To set up the update process now run the following command:
|
||||
|
||||
./utils/update.php --init-updates
|
||||
|
||||
It outputs the date where updates will start. Recheck that this date is
|
||||
what you expect.
|
||||
|
||||
The `--init-updates` command needs to be rerun whenever the replication service
|
||||
is changed.
|
||||
|
||||
#### Updating Nominatim
|
||||
|
||||
The following command will keep your database constantly up to date:
|
||||
|
||||
./utils/update.php --import-osmosis-all
|
||||
|
||||
(Note that even though the old name "import-osmosis-all" has been kept for
|
||||
compatibility reasons, Osmosis is not required to run this - it uses pyosmium
|
||||
behind the scenes.)
|
||||
|
||||
If you have imported multiple country extracts and want to keep them
|
||||
up-to-date, [Advanced installations section](Advanced-Installations.md) contains instructions
|
||||
to set up and update multiple country extracts.
|
||||
@@ -46,15 +46,16 @@ a single place (for reverse) of the following format:
|
||||
|
||||
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 ([see notes](#place_id-is-not-a-persistent-id))
|
||||
* `osm_type`, `osm_id` - reference to the OSM object
|
||||
* `boundingbox` - area of corner coordinates
|
||||
* `boundingbox` - area of corner coordinates ([see notes](#boundingbox))
|
||||
* `lat`, `lon` - latitude and longitude of the centroid of the object
|
||||
* `display_name` - full comma-separated address
|
||||
* `class`, `type` - key and value of the main OSM tag
|
||||
* `importance` - computed importance rank
|
||||
* `icon` - link to class icon (if available)
|
||||
* `address` - dictionary of address details (only with `addressdetails=1`)
|
||||
* `address` - dictionary of address details (only with `addressdetails=1`,
|
||||
[see notes](#addressdetails))
|
||||
* `extratags` - dictionary with additional useful tags like website or maxspeed
|
||||
(only with `extratags=1`)
|
||||
* `namedetails` - dictionary with full list of available names including ref etc.
|
||||
@@ -75,14 +76,15 @@ a bounding box (`bbox`).
|
||||
|
||||
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 ([see notes](#place_id-is-not-a-persistent-id))
|
||||
* `osm_type`, `osm_id` - reference to the OSM object
|
||||
* `category`, `type` - key and value of the main OSM tag
|
||||
* `display_name` - full comma-separated address
|
||||
* `place_rank` - class search rank
|
||||
* `importance` - computed importance rank
|
||||
* `icon` - link to class icon (if available)
|
||||
* `address` - dictionary of address details (only with `addressdetails=1`)
|
||||
* `address` - dictionary of address details (only with `addressdetails=1`,
|
||||
[see notes](#addressdetails))
|
||||
* `extratags` - dictionary with additional useful tags like `website` or `maxspeed`
|
||||
(only with `extratags=1`)
|
||||
* `namedetails` - dictionary with full list of available names including ref etc.
|
||||
@@ -100,12 +102,9 @@ The following feature attributes are implemented:
|
||||
* `type` - value of the main tag of the object (e.g. residential, restaurant, ...)
|
||||
* `label` - full comma-separated address
|
||||
* `name` - localised name of the place
|
||||
* `housenumber`, `street`, `locality`, `postcode`, `city`,
|
||||
`district`, `county`, `state`, `country` -
|
||||
* `housenumber`, `street`, `locality`, `district`, `postcode`, `city`,
|
||||
`county`, `state`, `country` -
|
||||
provided when it can be determined from the address
|
||||
(see [this issue](https://github.com/openstreetmap/Nominatim/issues/1080) for
|
||||
current limitations on the correctness of the address) and `addressdetails=1`
|
||||
was given
|
||||
* `admin` - list of localised names of administrative boundaries (only with `addressdetails=1`)
|
||||
|
||||
Use `polygon_geojson` to output the full geometry of the object instead
|
||||
@@ -148,11 +147,11 @@ attribution to OSM and the original querystring.
|
||||
|
||||
The place information can be found in the `result` element. The attributes of that element contain:
|
||||
|
||||
* `place_id` - reference to the Nominatim internal database ID (see notes below)
|
||||
* `place_id` - reference to the Nominatim internal database ID ([see notes](#place_id-is-not-a-persistent-id))
|
||||
* `osm_type`, `osm_id` - reference to the OSM object
|
||||
* `ref` - content of `ref` tag if it exists
|
||||
* `lat`, `lon` - latitude and longitude of the centroid of the object
|
||||
* `boundingbox` - comma-separated list of corner coordinates
|
||||
* `boundingbox` - comma-separated list of corner coordinates ([see notes](#boundingbox))
|
||||
|
||||
The full address of the result can be found in the content of the
|
||||
`result` element as a comma-separated list.
|
||||
@@ -203,11 +202,11 @@ generic information about the query:
|
||||
The place information can be found in the `place` elements, of which there may
|
||||
be more than one. The attributes of that element contain:
|
||||
|
||||
* `place_id` - reference to the Nominatim internal database ID (see notes below)
|
||||
* `place_id` - reference to the Nominatim internal database ID ([see notes](#place_id-is-not-a-persistent-id))
|
||||
* `osm_type`, `osm_id` - reference to the OSM object
|
||||
* `ref` - content of `ref` tag if it exists
|
||||
* `lat`, `lon` - latitude and longitude of the centroid of the object
|
||||
* `boundingbox` - comma-separated list of corner coordinates
|
||||
* `boundingbox` - comma-separated list of corner coordinates ([see notes](#boundingbox))
|
||||
* `place_rank` - class search rank
|
||||
* `display_name` - full comma-separated address
|
||||
* `class`, `type` - key and value of the main OSM tag
|
||||
@@ -244,3 +243,32 @@ relation) so `osm_type`+`osm_id`+`class_name` would be more unique.
|
||||
|
||||
Comma separated list of min latitude, max latitude, min longitude, max longitude.
|
||||
The whole planet would be `-90,90,-180,180`.
|
||||
|
||||
Can we used to pan and center the map on the result, for example with leafletjs
|
||||
mapping library
|
||||
`map.fitBounds([[bbox[0],bbox[2]],[bbox[1],bbox[3]]], {padding: [20, 20], maxzoom: 16});`
|
||||
|
||||
Bounds crossing the antimeridian have a min latitude -180 and max latitude 180,
|
||||
essentially covering the planet (See [issue 184](https://github.com/openstreetmap/Nominatim/issues/184)).
|
||||
|
||||
### addressdetails
|
||||
|
||||
Address details in the xml and json formats return a list of names together
|
||||
with a designation label. Per default the following labels may appear:
|
||||
|
||||
* continent
|
||||
* country, country_code
|
||||
* region, state, state_district, county
|
||||
* municipality, city, town, village
|
||||
* city_district, district, borough, suburb, subdivision
|
||||
* hamlet, croft, isolated_dwelling
|
||||
* neighbourhood, allotments, quarter
|
||||
* city_block, residental, farm, farmyard, industrial, commercial, retail
|
||||
* road
|
||||
* house_number, house_name
|
||||
* emergency, historic, military, natural, landuse, place, railway,
|
||||
man_made, aerialway, boundary, amenity, aeroway, club, craft, leisure,
|
||||
office, mountain_pass, shop, tourism, bridge, tunnel, waterway
|
||||
|
||||
They roughly correspond to the classification of the OpenStreetMap data
|
||||
according to either the `place` tag or the main key of the object.
|
||||
|
||||
@@ -7,7 +7,7 @@ Its API has the following endpoints for querying the data:
|
||||
* __[/search](Search.md)__ - search OSM objects by name or type
|
||||
* __[/reverse](Reverse.md)__ - search OSM object by their location
|
||||
* __[/lookup](Lookup.md)__ - look up address details for OSM objects by their ID
|
||||
* __/status__ - query the status of the server
|
||||
* __[/status](Status.md)__ - query the status of the server
|
||||
* __/deletable__ - list objects that have been deleted in OSM but are held
|
||||
back in Nominatim in case the deletion was accidental
|
||||
* __/polygons__ - list of broken polygons detected by Nominatim
|
||||
|
||||
@@ -92,8 +92,12 @@ comma-separated list of language codes.
|
||||
* `countrycodes=<countrycode>[,<countrycode>][,<countrycode>]...`
|
||||
|
||||
Limit search results to one or more countries. `<countrycode>` must be the
|
||||
ISO 3166-1alpha2 code, e.g. `gb` for the United Kingdom, `de` for Germany.
|
||||
[ISO 3166-1alpha2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code,
|
||||
e.g. `gb` for the United Kingdom, `de` for Germany.
|
||||
|
||||
Each place in Nominatim is assigned to one country code based
|
||||
on `admin_level=2` tags, in rare cases to none (for example in
|
||||
international waters outside any country).
|
||||
|
||||
* `exclude_place_ids=<place_id,[place_id],[place_id]`
|
||||
|
||||
@@ -168,21 +172,27 @@ This overrides the specified machine readable format. (Default: 0)
|
||||
## Examples
|
||||
|
||||
|
||||
##### XML with polygon points
|
||||
##### XML with kml polygon
|
||||
|
||||
* [https://nominatim.openstreetmap.org/search?q=135+pilkington+avenue,+birmingham&format=xml&polygon=1&addressdetails=1](https://nominatim.openstreetmap.org/search?q=135+pilkington+avenue,+birmingham&format=xml&polygon=1&addressdetails=1)
|
||||
* [https://nominatim.openstreetmap.org/search/gb/birmingham/pilkington%20avenue/135?format=xml&polygon=1&addressdetails=1](https://nominatim.openstreetmap.org/search/gb/birmingham/pilkington%20avenue/135?format=xml&polygon=1&addressdetails=1)
|
||||
* [https://nominatim.openstreetmap.org/search/135%20pilkington%20avenue,%20birmingham?format=xml&polygon=1&addressdetails=1](https://nominatim.openstreetmap.org/search/135%20pilkington%20avenue,%20birmingham?format=xml&polygon=1&addressdetails=1)
|
||||
* [https://nominatim.openstreetmap.org/search?q=135+pilkington+avenue,+birmingham&format=xml&polygon_geojson=1&addressdetails=1](https://nominatim.openstreetmap.org/search?q=135+pilkington+avenue,+birmingham&format=xml&polygon_geojson=1&addressdetails=1)
|
||||
|
||||
```xml
|
||||
<searchresults timestamp="Sat, 07 Nov 09 14:42:10 +0000" querystring="135 pilkington, avenue birmingham" polygon="true">
|
||||
<place
|
||||
place_id="1620612" osm_type="node" osm_id="452010817"
|
||||
boundingbox="52.548641204834,52.5488433837891,-1.81612110137939,-1.81592094898224"
|
||||
polygonpoints="[['-1.81592098644987','52.5487429714954'],['-1.81592290792183','52.5487234624632'],...]"
|
||||
lat="52.5487429714954" lon="-1.81602098644987"
|
||||
display_name="135, Pilkington Avenue, Wylde Green, City of Birmingham, West Midlands (county), B72, United Kingdom"
|
||||
class="place" type="house">
|
||||
<geokml>
|
||||
<Polygon>
|
||||
<outerBoundaryIs>
|
||||
<LinearRing>
|
||||
<coordinates>-1.816513,52.548756599999997 -1.816434,52.548747300000002 -1.816429,52.5487629 -1.8163717,52.548756099999999 -1.8163464,52.548834599999999 -1.8164599,52.548848100000001 -1.8164685,52.5488213 -1.8164913,52.548824000000003 -1.816513,52.548756599999997</coordinates>
|
||||
</LinearRing>
|
||||
</outerBoundaryIs>
|
||||
</Polygon>
|
||||
</geokml>
|
||||
<house_number>135</house_number>
|
||||
<road>Pilkington Avenue</road>
|
||||
<village>Wylde Green</village>
|
||||
|
||||
60
docs/api/Status.md
Normal file
60
docs/api/Status.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Status
|
||||
|
||||
Useful for checking if the service and database is running. The JSON output also shows
|
||||
when the database was last updated.
|
||||
|
||||
## Parameters
|
||||
|
||||
* `format=[text|json]` (defaults to 'text')
|
||||
|
||||
|
||||
## Output
|
||||
|
||||
#### Text format
|
||||
|
||||
```
|
||||
https://nominatim.openstreetmap.org/status.php
|
||||
```
|
||||
|
||||
will return HTTP status code 200 and print `OK`.
|
||||
|
||||
On error it will return HTTP status code 500 and print a message, e.g.
|
||||
`ERROR: Database connection failed`.
|
||||
|
||||
|
||||
|
||||
#### JSON format
|
||||
|
||||
```
|
||||
https://nominatim.openstreetmap.org/status.php?format=json
|
||||
```
|
||||
|
||||
will return HTTP code 200 and a structure
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 0,
|
||||
"message": "OK",
|
||||
"data_updated": "2020-05-04T14:47:00+00:00"
|
||||
}
|
||||
```
|
||||
|
||||
On error will also return HTTP status code 200 and a structure with error
|
||||
code and message, e.g.
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 700,
|
||||
"message": "Database connection failed"
|
||||
}
|
||||
```
|
||||
|
||||
Possible status codes are
|
||||
|
||||
| | message | notes |
|
||||
|-----|----------------------|---------------------------------------------------|
|
||||
| 700 | "No database" | connection failed |
|
||||
| 701 | "Module failed" | database could not load nominatim.so |
|
||||
| 702 | "Module call failed" | nominatim.so loaded but calling a function failed |
|
||||
| 703 | "Query failed" | test query against a database table failed |
|
||||
| 704 | "No value" | test query worked but returned no results |
|
||||
@@ -1,36 +1,39 @@
|
||||
# Documentation Pages
|
||||
|
||||
The [Nominatim documentation](https://nominatim.org/release-docs/develop/) is built using the [MkDocs](https://www.mkdocs.org/) static site generation framework. The master branch is automatically deployed every night on under [https://nominatim.org/release-docs/develop/]()
|
||||
The [Nominatim documentation](https://nominatim.org/release-docs/develop/) is built using the [MkDocs](https://www.mkdocs.org/) static site generation framework. The master branch is automatically deployed every night on under [https://nominatim.org/release-docs/develop/](https://nominatim.org/release-docs/develop/)
|
||||
|
||||
To preview local changes:
|
||||
To preview local changes, first install MkDocs
|
||||
|
||||
1. Install MkDocs
|
||||
```
|
||||
pip3 install --user mkdocs
|
||||
```
|
||||
|
||||
```
|
||||
pip3 install --user mkdocs
|
||||
```
|
||||
If `mkdocs` can't be found after the installation, the $PATH might have not
|
||||
been set correctly yet. Try opening a new terminal session.
|
||||
|
||||
|
||||
2. In build directory run
|
||||
Then go to the build directory and run
|
||||
|
||||
```
|
||||
make doc
|
||||
INFO - Cleaning site directory
|
||||
INFO - Building documentation to directory: /home/vagrant/build/site-html
|
||||
```
|
||||
```
|
||||
make doc
|
||||
INFO - Cleaning site directory
|
||||
INFO - Building documentation to directory: /home/vagrant/build/site-html
|
||||
```
|
||||
|
||||
This runs `mkdocs build` plus extra transformion of some files and adds symlinks (see `CMakeLists.txt` for the exact steps).
|
||||
This runs `mkdocs build` plus extra transformation of some files and adds
|
||||
symlinks (see `CMakeLists.txt` for the exact steps).
|
||||
|
||||
Now you can start webserver for local testing
|
||||
|
||||
3. Start webserver for local testing
|
||||
```
|
||||
build> mkdocs serve
|
||||
[server:296] Serving on http://127.0.0.1:8000
|
||||
[handlers:62] Start watching changes
|
||||
```
|
||||
|
||||
```
|
||||
mkdocs serve
|
||||
[server:296] Serving on http://127.0.0.1:8000
|
||||
[handlers:62] Start watching changes
|
||||
```
|
||||
If you develop inside a Vagrant virtual machine:
|
||||
|
||||
If you develop inside a Vagrant virtual machine:
|
||||
* add port forwarding to your Vagrantfile, e.g. `config.vm.network "forwarded_port", guest: 8000, host: 8000`
|
||||
* use `mkdocs serve --dev-addr 0.0.0.0:8000` because the default localhost
|
||||
IP does not get forwarded.
|
||||
* add port forwarding to your Vagrantfile,
|
||||
e.g. `config.vm.network "forwarded_port", guest: 8000, host: 8000`
|
||||
* use `mkdocs serve --dev-addr 0.0.0.0:8000` because the default localhost
|
||||
IP does not get forwarded.
|
||||
|
||||
45
docs/develop/Postcodes.md
Normal file
45
docs/develop/Postcodes.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Postcodes in Nominatim
|
||||
|
||||
The blog post
|
||||
[Nominatim and Postcodes](https://www.openstreetmap.org/user/lonvia/diary/43143)
|
||||
describes the handling implemented since Nominatim 3.1.
|
||||
|
||||
Postcode centroids (aka 'calculated postcodes') are generated by looking at all
|
||||
postcodes of a country, grouping them and calculating the geometric centroid.
|
||||
There is currently no logic to deal with extreme outliers (typos or other
|
||||
mistakes in OSM data). There is also no check if a postcodes adheres to a
|
||||
country's format, e.g. if Swiss postcodes are 4 digits.
|
||||
|
||||
|
||||
## Regular updating calculated postcodes
|
||||
|
||||
The script to rerun the calculation is
|
||||
`build/utils/update.php --calculate-postcodes`
|
||||
and runs once per night on nominatim.openstreetmap.org.
|
||||
|
||||
|
||||
## Finding places that share a specific postcode
|
||||
|
||||
In the Nominatim database run
|
||||
|
||||
```sql
|
||||
SELECT address->'postcode' as pc,
|
||||
osm_type, osm_id, class, type,
|
||||
st_x(centroid) as lon, st_y(centroid) as lat
|
||||
FROM placex
|
||||
WHERE country_code='fr'
|
||||
AND upper(trim (both ' ' from address->'postcode')) = '33210';
|
||||
```
|
||||
|
||||
Alternatively on [Overpass](https://overpass-turbo.eu/) run the following query
|
||||
|
||||
```
|
||||
[out:json][timeout:250];
|
||||
area["name"="France"]->.boundaryarea;
|
||||
(
|
||||
nwr(area.boundaryarea)["addr:postcode"="33210"];
|
||||
);
|
||||
out body;
|
||||
>;
|
||||
out skel qt;
|
||||
```
|
||||
46
docs/develop/Setup.md
Normal file
46
docs/develop/Setup.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Setup Test Environment
|
||||
|
||||
To test changes and contribute to Nominatim you should be able to run
|
||||
the test suite(s). For many usecases it's enough to create a Vagrant
|
||||
virtual machine (see `VAGRANT.md`), import one small country into the
|
||||
database.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Nominatim supports a range of PHP versions and PHPUnit versions also
|
||||
move fast. We try to test against the newest stable PHP release and
|
||||
PHPUnit version even though we expect many Nominatim users will install
|
||||
older version on their production servers.
|
||||
|
||||
#### Ubuntu 20
|
||||
|
||||
sudo apt-get install -y phpunit php-codesniffer php-cgi
|
||||
pip3 install --user behave nose
|
||||
|
||||
#### Ubuntu 18
|
||||
|
||||
pip3 install --user behave nose
|
||||
|
||||
sudo apt-get install -y composer php-cgi php-cli php-mbstring php-xml zip unzip
|
||||
|
||||
composer global require "squizlabs/php_codesniffer=*"
|
||||
sudo ln -s ~/.config/composer/vendor/bin/phpcs /usr/bin/
|
||||
|
||||
composer global require "phpunit/phpunit=8.*"
|
||||
sudo ln -s ~/.config/composer/vendor/bin/phpunit /usr/bin/
|
||||
|
||||
|
||||
#### CentOS 7 or 8
|
||||
|
||||
sudo dnf install -y php-dom php-mbstring
|
||||
pip3 install --user behave nose
|
||||
|
||||
composer global require "squizlabs/php_codesniffer=*"
|
||||
sudo ln -s ~/.config/composer/vendor/bin/phpcs /usr/bin/
|
||||
|
||||
composer global require "phpunit/phpunit=^7"
|
||||
sudo ln -s ~/.config/composer/vendor/bin/phpunit /usr/bin/
|
||||
|
||||
## Run tests, code linter, code coverage
|
||||
|
||||
See `README.md` in `test` subdirectory.
|
||||
@@ -1,7 +1,7 @@
|
||||
site_name: Nominatim Documentation
|
||||
site_name: Nominatim 3.5.2
|
||||
theme: readthedocs
|
||||
docs_dir: ${CMAKE_CURRENT_BINARY_DIR}
|
||||
site_url: http://nominatim.org
|
||||
site_url: https://nominatim.org
|
||||
repo_url: https://github.com/openstreetmap/Nominatim
|
||||
pages:
|
||||
- 'Introduction' : 'index.md'
|
||||
@@ -11,17 +11,22 @@ pages:
|
||||
- 'Reverse': 'api/Reverse.md'
|
||||
- 'Address Lookup': 'api/Lookup.md'
|
||||
- 'Details' : 'api/Details.md'
|
||||
- 'Status' : 'api/Status.md'
|
||||
- 'Place Output Formats': 'api/Output.md'
|
||||
- 'FAQ': 'api/Faq.md'
|
||||
- 'Administration Guide':
|
||||
- 'Basic Installation': 'admin/Installation.md'
|
||||
- 'Importing and Updating' : 'admin/Import-and-Update.md'
|
||||
- 'Importing' : 'admin/Import.md'
|
||||
- 'Updating' : 'admin/Update.md'
|
||||
- 'Advanced Installations' : 'admin/Advanced-Installations.md'
|
||||
- 'Migration from older Versions' : 'admin/Migration.md'
|
||||
- 'Troubleshooting' : 'admin/Faq.md'
|
||||
- 'Developers Guide':
|
||||
- 'Overview' : 'develop/overview.md'
|
||||
- 'OSM Data Import' : 'develop/Import.md'
|
||||
- 'Place Ranking' : 'develop/Ranking.md'
|
||||
- 'Postcodes' : 'develop/Postcodes.md'
|
||||
- 'Setup Test Environment' : 'develop/Setup.md'
|
||||
- 'Documentation' : 'develop/Documentation.md'
|
||||
- 'External Data Sources':
|
||||
- 'Overview' : 'data-sources/overview.md'
|
||||
@@ -31,11 +36,12 @@ pages:
|
||||
- 'Wikipedia & Wikidata': 'data-sources/Wikipedia-Wikidata.md'
|
||||
- 'Appendix':
|
||||
- 'Installation on CentOS 7' : 'appendix/Install-on-Centos-7.md'
|
||||
- 'Installation on Ubuntu 16' : 'appendix/Install-on-Ubuntu-16.md'
|
||||
- 'Installation on CentOS 8' : 'appendix/Install-on-Centos-8.md'
|
||||
- 'Installation on Ubuntu 18' : 'appendix/Install-on-Ubuntu-18.md'
|
||||
- 'Installation on Ubuntu 20' : 'appendix/Install-on-Ubuntu-20.md'
|
||||
markdown_extensions:
|
||||
- codehilite:
|
||||
use_pygments: False
|
||||
- codehilite
|
||||
- admonition
|
||||
- toc:
|
||||
permalink:
|
||||
extra_css: [extra.css]
|
||||
extra_css: [extra.css, styles.css]
|
||||
|
||||
69
docs/styles.css
Normal file
69
docs/styles.css
Normal file
@@ -0,0 +1,69 @@
|
||||
.codehilite .hll { background-color: #ffffcc }
|
||||
.codehilite { background: #f0f0f0; }
|
||||
.codehilite .c { color: #60a0b0; font-style: italic } /* Comment */
|
||||
.codehilite .err { /* border: 1px solid #FF0000 */ } /* Error */
|
||||
.codehilite .k { color: #007020; font-weight: bold } /* Keyword */
|
||||
.codehilite .o { color: #666666 } /* Operator */
|
||||
.codehilite .ch { color: #60a0b0; font-style: italic } /* Comment.Hashbang */
|
||||
.codehilite .cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */
|
||||
.codehilite .cp { color: #007020 } /* Comment.Preproc */
|
||||
.codehilite .cpf { color: #60a0b0; font-style: italic } /* Comment.PreprocFile */
|
||||
.codehilite .c1 { color: #60a0b0; font-style: italic } /* Comment.Single */
|
||||
.codehilite .cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */
|
||||
.codehilite .gd { color: #A00000 } /* Generic.Deleted */
|
||||
.codehilite .ge { font-style: italic } /* Generic.Emph */
|
||||
.codehilite .gr { color: #FF0000 } /* Generic.Error */
|
||||
.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||
.codehilite .gi { color: #00A000 } /* Generic.Inserted */
|
||||
.codehilite .go { color: #888888 } /* Generic.Output */
|
||||
.codehilite .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
|
||||
.codehilite .gs { font-weight: bold } /* Generic.Strong */
|
||||
.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||
.codehilite .gt { color: #0044DD } /* Generic.Traceback */
|
||||
.codehilite .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
|
||||
.codehilite .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
|
||||
.codehilite .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
|
||||
.codehilite .kp { color: #007020 } /* Keyword.Pseudo */
|
||||
.codehilite .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
|
||||
.codehilite .kt { color: #902000 } /* Keyword.Type */
|
||||
.codehilite .m { color: #40a070 } /* Literal.Number */
|
||||
.codehilite .s { color: #4070a0 } /* Literal.String */
|
||||
.codehilite .na { color: #4070a0 } /* Name.Attribute */
|
||||
.codehilite .nb { color: #007020 } /* Name.Builtin */
|
||||
.codehilite .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
|
||||
.codehilite .no { color: #60add5 } /* Name.Constant */
|
||||
.codehilite .nd { color: #555555; font-weight: bold } /* Name.Decorator */
|
||||
.codehilite .ni { color: #d55537; font-weight: bold } /* Name.Entity */
|
||||
.codehilite .ne { color: #007020 } /* Name.Exception */
|
||||
.codehilite .nf { color: #06287e } /* Name.Function */
|
||||
.codehilite .nl { color: #002070; font-weight: bold } /* Name.Label */
|
||||
.codehilite .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
|
||||
.codehilite .nt { color: #062873; font-weight: bold } /* Name.Tag */
|
||||
.codehilite .nv { color: #bb60d5 } /* Name.Variable */
|
||||
.codehilite .ow { color: #007020; font-weight: bold } /* Operator.Word */
|
||||
.codehilite .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.codehilite .mb { color: #40a070 } /* Literal.Number.Bin */
|
||||
.codehilite .mf { color: #40a070 } /* Literal.Number.Float */
|
||||
.codehilite .mh { color: #40a070 } /* Literal.Number.Hex */
|
||||
.codehilite .mi { color: #40a070 } /* Literal.Number.Integer */
|
||||
.codehilite .mo { color: #40a070 } /* Literal.Number.Oct */
|
||||
.codehilite .sa { color: #4070a0 } /* Literal.String.Affix */
|
||||
.codehilite .sb { color: #4070a0 } /* Literal.String.Backtick */
|
||||
.codehilite .sc { color: #4070a0 } /* Literal.String.Char */
|
||||
.codehilite .dl { color: #4070a0 } /* Literal.String.Delimiter */
|
||||
.codehilite .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
|
||||
.codehilite .s2 { color: #4070a0 } /* Literal.String.Double */
|
||||
.codehilite .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
|
||||
.codehilite .sh { color: #4070a0 } /* Literal.String.Heredoc */
|
||||
.codehilite .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
|
||||
.codehilite .sx { color: #c65d09 } /* Literal.String.Other */
|
||||
.codehilite .sr { color: #235388 } /* Literal.String.Regex */
|
||||
.codehilite .s1 { color: #4070a0 } /* Literal.String.Single */
|
||||
.codehilite .ss { color: #517918 } /* Literal.String.Symbol */
|
||||
.codehilite .bp { color: #007020 } /* Name.Builtin.Pseudo */
|
||||
.codehilite .fm { color: #06287e } /* Name.Function.Magic */
|
||||
.codehilite .vc { color: #bb60d5 } /* Name.Variable.Class */
|
||||
.codehilite .vg { color: #bb60d5 } /* Name.Variable.Global */
|
||||
.codehilite .vi { color: #bb60d5 } /* Name.Variable.Instance */
|
||||
.codehilite .vm { color: #bb60d5 } /* Name.Variable.Magic */
|
||||
.codehilite .il { color: #40a070 } /* Literal.Number.Integer.Long */
|
||||
@@ -9,10 +9,13 @@ require_once(CONST_BasePath.'/lib/ClassTypes.php');
|
||||
*/
|
||||
class AddressDetails
|
||||
{
|
||||
private $iPlaceID;
|
||||
private $aAddressLines;
|
||||
|
||||
public function __construct(&$oDB, $iPlaceID, $sHousenumber, $mLangPref)
|
||||
{
|
||||
$this->iPlaceID = $iPlaceID;
|
||||
|
||||
if (is_array($mLangPref)) {
|
||||
$mLangPref = $oDB->getArraySQL($oDB->getDBQuotedList($mLangPref));
|
||||
}
|
||||
@@ -58,48 +61,94 @@ class AddressDetails
|
||||
return join(', ', $aParts);
|
||||
}
|
||||
|
||||
public function getAddressNames()
|
||||
public function getAddressNames($sCountry = null)
|
||||
{
|
||||
$aAddress = array();
|
||||
$aFallback = array();
|
||||
|
||||
foreach ($this->aAddressLines as $aLine) {
|
||||
if (!self::isAddress($aLine)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bFallback = false;
|
||||
$aTypeLabel = ClassTypes\getInfo($aLine);
|
||||
$sTypeLabel = ClassTypes\getLabelTag($aLine);
|
||||
|
||||
if ($aTypeLabel === false) {
|
||||
$aTypeLabel = ClassTypes\getFallbackInfo($aLine);
|
||||
$bFallback = true;
|
||||
}
|
||||
|
||||
$sName = false;
|
||||
if (isset($aLine['localname']) && $aLine['localname']) {
|
||||
$sName = null;
|
||||
if (isset($aLine['localname']) && $aLine['localname']!=='') {
|
||||
$sName = $aLine['localname'];
|
||||
} elseif (isset($aLine['housenumber']) && $aLine['housenumber']) {
|
||||
} elseif (isset($aLine['housenumber']) && $aLine['housenumber']!=='') {
|
||||
$sName = $aLine['housenumber'];
|
||||
}
|
||||
|
||||
if ($sName) {
|
||||
$sTypeLabel = strtolower(isset($aTypeLabel['simplelabel']) ? $aTypeLabel['simplelabel'] : $aTypeLabel['label']);
|
||||
$sTypeLabel = str_replace(' ', '_', $sTypeLabel);
|
||||
if (isset($sName)) {
|
||||
$sTypeLabel = strtolower(str_replace(' ', '_', $sTypeLabel));
|
||||
if (!isset($aAddress[$sTypeLabel])
|
||||
|| isset($aFallback[$sTypeLabel])
|
||||
|| $aLine['class'] == 'place'
|
||||
) {
|
||||
$aAddress[$sTypeLabel] = $sName;
|
||||
if ($bFallback) {
|
||||
$aFallback[$sTypeLabel] = $bFallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $aAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotates the given json with geocodejson address information fields.
|
||||
*
|
||||
* @param array $aJson Json hash to add the fields to.
|
||||
*
|
||||
* Geocodejson has the following fields:
|
||||
* street, locality, postcode, city, district,
|
||||
* county, state, country
|
||||
*
|
||||
* Postcode and housenumber are added by type, district is not used.
|
||||
* All other fields are set according to address rank.
|
||||
*/
|
||||
public function addGeocodeJsonAddressParts(&$aJson)
|
||||
{
|
||||
foreach (array_reverse($this->aAddressLines) as $aLine) {
|
||||
if (!$aLine['isaddress']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($aLine['localname']) || $aLine['localname'] == '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($aLine['type'] == 'postcode' || $aLine['type'] == 'postal_code') {
|
||||
$aJson['postcode'] = $aLine['localname'];
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($aLine['type'] == 'house_number') {
|
||||
$aJson['housenumber'] = $aLine['localname'];
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->iPlaceID == $aLine['place_id']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$iRank = (int)$aLine['rank_address'];
|
||||
|
||||
if ($iRank > 25 && $iRank < 28) {
|
||||
$aJson['street'] = $aLine['localname'];
|
||||
} elseif ($iRank >= 22 && $iRank <= 25) {
|
||||
$aJson['locality'] = $aLine['localname'];
|
||||
} elseif ($iRank >= 17 && $iRank <= 21) {
|
||||
$aJson['district'] = $aLine['localname'];
|
||||
} elseif ($iRank >= 13 && $iRank <= 16) {
|
||||
$aJson['city'] = $aLine['localname'];
|
||||
} elseif ($iRank >= 10 && $iRank <= 12) {
|
||||
$aJson['county'] = $aLine['localname'];
|
||||
} elseif ($iRank >= 5 && $iRank <= 9) {
|
||||
$aJson['state'] = $aLine['localname'];
|
||||
} elseif ($iRank == 4) {
|
||||
$aJson['country'] = $aLine['localname'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getAdminLevels()
|
||||
{
|
||||
$aAddress = array();
|
||||
|
||||
@@ -2,373 +2,559 @@
|
||||
|
||||
namespace Nominatim\ClassTypes;
|
||||
|
||||
function getInfo($aPlace)
|
||||
/**
|
||||
* Create a label tag for the given place that can be used as an XML name.
|
||||
*
|
||||
* @param array[] $aPlace Information about the place to label.
|
||||
*
|
||||
* A label tag groups various object types together under a common
|
||||
* label. The returned value is lower case and has no spaces
|
||||
*/
|
||||
function getLabelTag($aPlace, $sCountry = null)
|
||||
{
|
||||
$aClassType = getList();
|
||||
|
||||
if (isset($aPlace['admin_level'])) {
|
||||
$sName = $aPlace['class'].':'.$aPlace['type'].':'.$aPlace['admin_level'];
|
||||
if (isset($aClassType[$sName])) {
|
||||
return $aClassType[$sName];
|
||||
}
|
||||
$iRank = (int) ($aPlace['rank_address'] ?? 30);
|
||||
$sLabel;
|
||||
if (isset($aPlace['place_type'])) {
|
||||
$sLabel = $aPlace['place_type'];
|
||||
} elseif ($aPlace['class'] == 'boundary' && $aPlace['type'] == 'administrative') {
|
||||
$sLabel = getBoundaryLabel($iRank/2, $sCountry);
|
||||
} elseif ($iRank < 26) {
|
||||
$sLabel = $aPlace['type'];
|
||||
} elseif ($iRank < 28) {
|
||||
$sLabel = 'road';
|
||||
} elseif ($aPlace['class'] == 'place'
|
||||
&& ($aPlace['type'] == 'house_number' ||
|
||||
$aPlace['type'] == 'house_name' ||
|
||||
$aPlace['type'] == 'country_code')
|
||||
) {
|
||||
$sLabel = $aPlace['type'];
|
||||
} else {
|
||||
$sLabel = $aPlace['class'];
|
||||
}
|
||||
|
||||
$sName = $aPlace['class'].':'.$aPlace['type'];
|
||||
if (isset($aClassType[$sName])) {
|
||||
return $aClassType[$sName];
|
||||
}
|
||||
|
||||
return false;
|
||||
return strtolower(str_replace(' ', '_', $sLabel));
|
||||
}
|
||||
|
||||
function getFallbackInfo($aPlace)
|
||||
/**
|
||||
* Create a label for the given place.
|
||||
*
|
||||
* @param array[] $aPlace Information about the place to label.
|
||||
*/
|
||||
function getLabel($aPlace, $sCountry = null)
|
||||
{
|
||||
$aClassType = getList();
|
||||
|
||||
$sFallback = 'boundary:administrative:'.((int)($aPlace['rank_address']/2));
|
||||
if (isset($aClassType[$sFallback])) {
|
||||
return $aClassType[$sFallback];
|
||||
if (isset($aPlace['place_type'])) {
|
||||
return ucwords(str_replace('_', ' ', $aPlace['place_type']));
|
||||
}
|
||||
|
||||
return array('simplelabel' => 'address'.$aPlace['rank_address']);
|
||||
if ($aPlace['class'] == 'boundary' && $aPlace['type'] == 'administrative') {
|
||||
return getBoundaryLabel(($aPlace['rank_address'] ?? 30)/2, $sCountry ?? null);
|
||||
}
|
||||
|
||||
// Return a label only for 'important' class/type combinations
|
||||
if (getImportance($aPlace) !== null) {
|
||||
return ucwords(str_replace('_', ' ', $aPlace['type']));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getProperty($aPlace, $sProp, $mDefault = false)
|
||||
|
||||
/**
|
||||
* Return a simple label for an administrative boundary for the given country.
|
||||
*
|
||||
* @param int $iAdminLevel Content of admin_level tag.
|
||||
* @param string $sCountry Country code of the country where the object is
|
||||
* in. May be null, in which case a world-wide
|
||||
* fallback is used.
|
||||
* @param string $sFallback String to return if no explicit string is listed.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function getBoundaryLabel($iAdminLevel, $sCountry, $sFallback = 'Administrative')
|
||||
{
|
||||
$aClassType = getList();
|
||||
static $aBoundaryList = array (
|
||||
'default' => array (
|
||||
1 => 'Continent',
|
||||
2 => 'Country',
|
||||
3 => 'Region',
|
||||
4 => 'State',
|
||||
5 => 'State District',
|
||||
6 => 'County',
|
||||
7 => 'Municipality',
|
||||
8 => 'City',
|
||||
9 => 'City District',
|
||||
10 => 'Suburb',
|
||||
11 => 'Neighbourhood'
|
||||
),
|
||||
'no' => array (
|
||||
3 => 'State',
|
||||
4 => 'County'
|
||||
),
|
||||
'se' => array (
|
||||
3 => 'State',
|
||||
4 => 'County'
|
||||
)
|
||||
);
|
||||
|
||||
if (isset($aPlace['admin_level'])) {
|
||||
$sName = $aPlace['class'].':'.$aPlace['type'].':'.$aPlace['admin_level'];
|
||||
if (isset($aClassType[$sName]) && isset($aClassType[$sName][$sProp])) {
|
||||
return $aClassType[$sName][$sProp];
|
||||
}
|
||||
if (isset($aBoundaryList[$sCountry])
|
||||
&& isset($aBoundaryList[$sCountry][$iAdminLevel])
|
||||
) {
|
||||
return $aBoundaryList[$sCountry][$iAdminLevel];
|
||||
}
|
||||
|
||||
$sName = $aPlace['class'].':'.$aPlace['type'];
|
||||
if (isset($aClassType[$sName]) && isset($aClassType[$sName][$sProp])) {
|
||||
return $aClassType[$sName][$sProp];
|
||||
}
|
||||
|
||||
return $mDefault;
|
||||
return $aBoundaryList['default'][$iAdminLevel] ?? $sFallback;
|
||||
}
|
||||
|
||||
function getListWithImportance()
|
||||
/**
|
||||
* Return an estimated radius of how far the object node extends.
|
||||
*
|
||||
* @param array[] $aPlace Information about the place. This must be a node
|
||||
* feature.
|
||||
*
|
||||
* @return float The radius around the feature in degrees.
|
||||
*/
|
||||
function getDefRadius($aPlace)
|
||||
{
|
||||
static $aOrders = null;
|
||||
if ($aOrders === null) {
|
||||
$aOrders = getList();
|
||||
$i = 1;
|
||||
foreach ($aOrders as $sID => $a) {
|
||||
$aOrders[$sID]['importance'] = $i++;
|
||||
}
|
||||
}
|
||||
|
||||
return $aOrders;
|
||||
}
|
||||
|
||||
function getList()
|
||||
{
|
||||
return array(
|
||||
'boundary:administrative:1' => array('label' => 'Continent', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'boundary:administrative:2' => array('label' => 'Country', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'place:country' => array('label' => 'Country', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defzoom' => 6, 'defdiameter' => 15),
|
||||
'boundary:administrative:3' => array('label' => 'State', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'boundary:administrative:4' => array('label' => 'State', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'place:state' => array('label' => 'State', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defzoom' => 8, 'defdiameter' => 5.12),
|
||||
'boundary:administrative:5' => array('label' => 'State District', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'boundary:administrative:6' => array('label' => 'County', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'boundary:administrative:7' => array('label' => 'County', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'place:county' => array('label' => 'County', 'frequency' => 108, 'icon' => 'poi_boundary_administrative', 'defzoom' => 10, 'defdiameter' => 1.28),
|
||||
'boundary:administrative:8' => array('label' => 'City', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'place:city' => array('label' => 'City', 'frequency' => 66, 'icon' => 'poi_place_city', 'defzoom' => 12, 'defdiameter' => 0.32),
|
||||
'boundary:administrative:9' => array('label' => 'City District', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'boundary:administrative:10' => array('label' => 'Suburb', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'boundary:administrative:11' => array('label' => 'Neighbourhood', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'place:region' => array('label' => 'Region', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defzoom' => 8, 'defdiameter' => 0.04),
|
||||
'place:island' => array('label' => 'Island', 'frequency' => 288, 'defzoom' => 11, 'defdiameter' => 0.64),
|
||||
'boundary:administrative' => array('label' => 'Administrative', 'frequency' => 413, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'boundary:postal_code' => array('label' => 'Postcode', 'frequency' => 413, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'place:town' => array('label' => 'Town', 'frequency' => 1497, 'icon' => 'poi_place_town', 'defzoom' => 14, 'defdiameter' => 0.08),
|
||||
'place:village' => array('label' => 'Village', 'frequency' => 11230, 'icon' => 'poi_place_village', 'defzoom' => 15, 'defdiameter' => 0.04),
|
||||
'place:hamlet' => array('label' => 'Hamlet', 'frequency' => 7075, 'icon' => 'poi_place_village', 'defzoom' => 15, 'defdiameter' => 0.04),
|
||||
'place:suburb' => array('label' => 'Suburb', 'frequency' => 2528, 'icon' => 'poi_place_village', 'defdiameter' => 0.04),
|
||||
'place:locality' => array('label' => 'Locality', 'frequency' => 4113, 'icon' => 'poi_place_village', 'defdiameter' => 0.02),
|
||||
'landuse:farm' => array('label' => 'Farm', 'frequency' => 1201, 'defdiameter' => 0.02),
|
||||
'place:farm' => array('label' => 'Farm', 'frequency' => 1162, 'defdiameter' => 0.02),
|
||||
|
||||
'highway:motorway_junction' => array('label' => 'Motorway Junction', 'frequency' => 1126, 'simplelabel' => 'Junction'),
|
||||
'highway:motorway' => array('label' => 'Motorway', 'frequency' => 4627, 'simplelabel' => 'Road'),
|
||||
'highway:trunk' => array('label' => 'Trunk', 'frequency' => 23084, 'simplelabel' => 'Road'),
|
||||
'highway:primary' => array('label' => 'Primary', 'frequency' => 32138, 'simplelabel' => 'Road'),
|
||||
'highway:secondary' => array('label' => 'Secondary', 'frequency' => 25807, 'simplelabel' => 'Road'),
|
||||
'highway:tertiary' => array('label' => 'Tertiary', 'frequency' => 29829, 'simplelabel' => 'Road'),
|
||||
'highway:residential' => array('label' => 'Residential', 'frequency' => 361498, 'simplelabel' => 'Road'),
|
||||
'highway:unclassified' => array('label' => 'Unclassified', 'frequency' => 66441, 'simplelabel' => 'Road'),
|
||||
'highway:living_street' => array('label' => 'Living Street', 'frequency' => 710, 'simplelabel' => 'Road'),
|
||||
'highway:service' => array('label' => 'Service', 'frequency' => 9963, 'simplelabel' => 'Road'),
|
||||
'highway:track' => array('label' => 'Track', 'frequency' => 2565, 'simplelabel' => 'Road'),
|
||||
'highway:road' => array('label' => 'Road', 'frequency' => 591, 'simplelabel' => 'Road'),
|
||||
'highway:byway' => array('label' => 'Byway', 'frequency' => 346, 'simplelabel' => 'Road'),
|
||||
'highway:bridleway' => array('label' => 'Bridleway', 'frequency' => 1556),
|
||||
'highway:cycleway' => array('label' => 'Cycleway', 'frequency' => 2419),
|
||||
'highway:pedestrian' => array('label' => 'Pedestrian', 'frequency' => 2757),
|
||||
'highway:footway' => array('label' => 'Footway', 'frequency' => 15008),
|
||||
'highway:steps' => array('label' => 'Steps', 'frequency' => 444, 'simplelabel' => 'Footway'),
|
||||
'highway:motorway_link' => array('label' => 'Motorway Link', 'frequency' => 795, 'simplelabel' => 'Road'),
|
||||
'highway:trunk_link' => array('label' => 'Trunk Link', 'frequency' => 1258, 'simplelabel' => 'Road'),
|
||||
'highway:primary_link' => array('label' => 'Primary Link', 'frequency' => 313, 'simplelabel' => 'Road'),
|
||||
|
||||
'landuse:industrial' => array('label' => 'Industrial', 'frequency' => 1062),
|
||||
'landuse:residential' => array('label' => 'Residential', 'frequency' => 886),
|
||||
'landuse:retail' => array('label' => 'Retail', 'frequency' => 754),
|
||||
'landuse:commercial' => array('label' => 'Commercial', 'frequency' => 657),
|
||||
|
||||
'place:airport' => array('label' => 'Airport', 'frequency' => 36, 'icon' => 'transport_airport2', 'defdiameter' => 0.03),
|
||||
'aeroway:aerodrome' => array('label' => 'Aerodrome', 'frequency' => 36, 'icon' => 'transport_airport2', 'defdiameter' => 0.03),
|
||||
'aeroway' => array('label' => 'Aeroway', 'frequency' => 36, 'icon' => 'transport_airport2', 'defdiameter' => 0.03),
|
||||
'railway:station' => array('label' => 'Station', 'frequency' => 3431, 'icon' => 'transport_train_station2', 'defdiameter' => 0.01),
|
||||
'amenity:place_of_worship' => array('label' => 'Place Of Worship', 'frequency' => 9049, 'icon' => 'place_of_worship_unknown3'),
|
||||
'amenity:pub' => array('label' => 'Pub', 'frequency' => 18969, 'icon' => 'food_pub'),
|
||||
'amenity:bar' => array('label' => 'Bar', 'frequency' => 164, 'icon' => 'food_bar'),
|
||||
'amenity:university' => array('label' => 'University', 'frequency' => 607, 'icon' => 'education_university'),
|
||||
'tourism:museum' => array('label' => 'Museum', 'frequency' => 543, 'icon' => 'tourist_museum'),
|
||||
'amenity:arts_centre' => array('label' => 'Arts Centre', 'frequency' => 136, 'icon' => 'tourist_art_gallery2'),
|
||||
'tourism:zoo' => array('label' => 'Zoo', 'frequency' => 47, 'icon' => 'tourist_zoo'),
|
||||
'tourism:theme_park' => array('label' => 'Theme Park', 'frequency' => 24, 'icon' => 'poi_point_of_interest'),
|
||||
'tourism:attraction' => array('label' => 'Attraction', 'frequency' => 1463, 'icon' => 'poi_point_of_interest'),
|
||||
'leisure:golf_course' => array('label' => 'Golf Course', 'frequency' => 712, 'icon' => 'sport_golf'),
|
||||
'historic:castle' => array('label' => 'Castle', 'frequency' => 316, 'icon' => 'tourist_castle'),
|
||||
'amenity:hospital' => array('label' => 'Hospital', 'frequency' => 879, 'icon' => 'health_hospital'),
|
||||
'amenity:school' => array('label' => 'School', 'frequency' => 8192, 'icon' => 'education_school'),
|
||||
'amenity:theatre' => array('label' => 'Theatre', 'frequency' => 371, 'icon' => 'tourist_theatre'),
|
||||
'amenity:public_building' => array('label' => 'Public Building', 'frequency' => 985),
|
||||
'amenity:library' => array('label' => 'Library', 'frequency' => 794, 'icon' => 'amenity_library'),
|
||||
'amenity:townhall' => array('label' => 'Townhall', 'frequency' => 242),
|
||||
'amenity:community_centre' => array('label' => 'Community Centre', 'frequency' => 157),
|
||||
'amenity:fire_station' => array('label' => 'Fire Station', 'frequency' => 221, 'icon' => 'amenity_firestation3'),
|
||||
'amenity:police' => array('label' => 'Police', 'frequency' => 334, 'icon' => 'amenity_police2'),
|
||||
'amenity:bank' => array('label' => 'Bank', 'frequency' => 1248, 'icon' => 'money_bank2'),
|
||||
'amenity:post_office' => array('label' => 'Post Office', 'frequency' => 859, 'icon' => 'amenity_post_office'),
|
||||
'leisure:park' => array('label' => 'Park', 'frequency' => 2378),
|
||||
'amenity:park' => array('label' => 'Park', 'frequency' => 53),
|
||||
'landuse:park' => array('label' => 'Park', 'frequency' => 50),
|
||||
'landuse:recreation_ground' => array('label' => 'Recreation Ground', 'frequency' => 517),
|
||||
'tourism:hotel' => array('label' => 'Hotel', 'frequency' => 2150, 'icon' => 'accommodation_hotel2'),
|
||||
'tourism:motel' => array('label' => 'Motel', 'frequency' => 43),
|
||||
'amenity:cinema' => array('label' => 'Cinema', 'frequency' => 277, 'icon' => 'tourist_cinema'),
|
||||
'tourism:artwork' => array('label' => 'Artwork', 'frequency' => 171, 'icon' => 'tourist_art_gallery2'),
|
||||
'historic:archaeological_site' => array('label' => 'Archaeological Site', 'frequency' => 407, 'icon' => 'tourist_archaeological2'),
|
||||
'amenity:doctors' => array('label' => 'Doctors', 'frequency' => 581, 'icon' => 'health_doctors'),
|
||||
'leisure:sports_centre' => array('label' => 'Sports Centre', 'frequency' => 767, 'icon' => 'sport_leisure_centre'),
|
||||
'leisure:swimming_pool' => array('label' => 'Swimming Pool', 'frequency' => 24, 'icon' => 'sport_swimming_outdoor'),
|
||||
'shop:supermarket' => array('label' => 'Supermarket', 'frequency' => 2673, 'icon' => 'shopping_supermarket'),
|
||||
'shop:convenience' => array('label' => 'Convenience', 'frequency' => 1469, 'icon' => 'shopping_convenience'),
|
||||
'amenity:restaurant' => array('label' => 'Restaurant', 'frequency' => 3179, 'icon' => 'food_restaurant'),
|
||||
'amenity:fast_food' => array('label' => 'Fast Food', 'frequency' => 2289, 'icon' => 'food_fastfood'),
|
||||
'amenity:cafe' => array('label' => 'Cafe', 'frequency' => 1780, 'icon' => 'food_cafe'),
|
||||
'tourism:guest_house' => array('label' => 'Guest House', 'frequency' => 223, 'icon' => 'accommodation_bed_and_breakfast'),
|
||||
'amenity:pharmacy' => array('label' => 'Pharmacy', 'frequency' => 733, 'icon' => 'health_pharmacy_dispensing'),
|
||||
'amenity:fuel' => array('label' => 'Fuel', 'frequency' => 1308, 'icon' => 'transport_fuel'),
|
||||
'natural:peak' => array('label' => 'Peak', 'frequency' => 3212, 'icon' => 'poi_peak'),
|
||||
'waterway:waterfall' => array('label' => 'Waterfall', 'frequency' => 24),
|
||||
'natural:wood' => array('label' => 'Wood', 'frequency' => 1845, 'icon' => 'landuse_coniferous_and_deciduous'),
|
||||
'natural:water' => array('label' => 'Water', 'frequency' => 1790),
|
||||
'landuse:forest' => array('label' => 'Forest', 'frequency' => 467),
|
||||
'landuse:cemetery' => array('label' => 'Cemetery', 'frequency' => 463),
|
||||
'landuse:allotments' => array('label' => 'Allotments', 'frequency' => 408),
|
||||
'landuse:farmyard' => array('label' => 'Farmyard', 'frequency' => 397),
|
||||
'railway:rail' => array('label' => 'Rail', 'frequency' => 4894),
|
||||
'waterway:canal' => array('label' => 'Canal', 'frequency' => 1723),
|
||||
'waterway:river' => array('label' => 'River', 'frequency' => 4089),
|
||||
'waterway:stream' => array('label' => 'Stream', 'frequency' => 2684),
|
||||
'shop:bicycle' => array('label' => 'Bicycle', 'frequency' => 349, 'icon' => 'shopping_bicycle'),
|
||||
'shop:clothes' => array('label' => 'Clothes', 'frequency' => 315, 'icon' => 'shopping_clothes'),
|
||||
'shop:hairdresser' => array('label' => 'Hairdresser', 'frequency' => 312, 'icon' => 'shopping_hairdresser'),
|
||||
'shop:doityourself' => array('label' => 'Doityourself', 'frequency' => 247, 'icon' => 'shopping_diy'),
|
||||
'shop:estate_agent' => array('label' => 'Estate Agent', 'frequency' => 162, 'icon' => 'shopping_estateagent2'),
|
||||
'shop:car' => array('label' => 'Car', 'frequency' => 159, 'icon' => 'shopping_car'),
|
||||
'shop:garden_centre' => array('label' => 'Garden Centre', 'frequency' => 143, 'icon' => 'shopping_garden_centre'),
|
||||
'shop:car_repair' => array('label' => 'Car Repair', 'frequency' => 141, 'icon' => 'shopping_car_repair'),
|
||||
'shop:newsagent' => array('label' => 'Newsagent', 'frequency' => 132),
|
||||
'shop:bakery' => array('label' => 'Bakery', 'frequency' => 129, 'icon' => 'shopping_bakery'),
|
||||
'shop:furniture' => array('label' => 'Furniture', 'frequency' => 124),
|
||||
'shop:butcher' => array('label' => 'Butcher', 'frequency' => 105, 'icon' => 'shopping_butcher'),
|
||||
'shop:apparel' => array('label' => 'Apparel', 'frequency' => 98, 'icon' => 'shopping_clothes'),
|
||||
'shop:electronics' => array('label' => 'Electronics', 'frequency' => 96),
|
||||
'shop:department_store' => array('label' => 'Department Store', 'frequency' => 86),
|
||||
'shop:books' => array('label' => 'Books', 'frequency' => 85),
|
||||
'shop:yes' => array('label' => 'Shop', 'frequency' => 68),
|
||||
'shop:outdoor' => array('label' => 'Outdoor', 'frequency' => 67),
|
||||
'shop:mall' => array('label' => 'Mall', 'frequency' => 63),
|
||||
'shop:florist' => array('label' => 'Florist', 'frequency' => 61),
|
||||
'shop:charity' => array('label' => 'Charity', 'frequency' => 60),
|
||||
'shop:hardware' => array('label' => 'Hardware', 'frequency' => 59),
|
||||
'shop:laundry' => array('label' => 'Laundry', 'frequency' => 51, 'icon' => 'shopping_laundrette'),
|
||||
'shop:shoes' => array('label' => 'Shoes', 'frequency' => 49),
|
||||
'shop:beverages' => array('label' => 'Beverages', 'frequency' => 48, 'icon' => 'shopping_alcohol'),
|
||||
'shop:dry_cleaning' => array('label' => 'Dry Cleaning', 'frequency' => 46),
|
||||
'shop:carpet' => array('label' => 'Carpet', 'frequency' => 45),
|
||||
'shop:computer' => array('label' => 'Computer', 'frequency' => 44),
|
||||
'shop:alcohol' => array('label' => 'Alcohol', 'frequency' => 44, 'icon' => 'shopping_alcohol'),
|
||||
'shop:optician' => array('label' => 'Optician', 'frequency' => 55, 'icon' => 'health_opticians'),
|
||||
'shop:chemist' => array('label' => 'Chemist', 'frequency' => 42, 'icon' => 'health_pharmacy'),
|
||||
'shop:gallery' => array('label' => 'Gallery', 'frequency' => 38, 'icon' => 'tourist_art_gallery2'),
|
||||
'shop:mobile_phone' => array('label' => 'Mobile Phone', 'frequency' => 37),
|
||||
'shop:sports' => array('label' => 'Sports', 'frequency' => 37),
|
||||
'shop:jewelry' => array('label' => 'Jewelry', 'frequency' => 32, 'icon' => 'shopping_jewelry'),
|
||||
'shop:pet' => array('label' => 'Pet', 'frequency' => 29),
|
||||
'shop:beauty' => array('label' => 'Beauty', 'frequency' => 28),
|
||||
'shop:stationery' => array('label' => 'Stationery', 'frequency' => 25),
|
||||
'shop:shopping_centre' => array('label' => 'Shopping Centre', 'frequency' => 25),
|
||||
'shop:general' => array('label' => 'General', 'frequency' => 25),
|
||||
'shop:electrical' => array('label' => 'Electrical', 'frequency' => 25),
|
||||
'shop:toys' => array('label' => 'Toys', 'frequency' => 23),
|
||||
'shop:jeweller' => array('label' => 'Jeweller', 'frequency' => 23),
|
||||
'shop:betting' => array('label' => 'Betting', 'frequency' => 23),
|
||||
'shop:household' => array('label' => 'Household', 'frequency' => 21),
|
||||
'shop:travel_agency' => array('label' => 'Travel Agency', 'frequency' => 21),
|
||||
'shop:hifi' => array('label' => 'Hifi', 'frequency' => 21),
|
||||
'amenity:shop' => array('label' => 'Shop', 'frequency' => 61),
|
||||
'tourism:information' => array('label' => 'Information', 'frequency' => 224, 'icon' => 'amenity_information'),
|
||||
|
||||
'place:house' => array('label' => 'House', 'frequency' => 2086, 'defzoom' => 18),
|
||||
'place:house_name' => array('label' => 'House', 'frequency' => 2086, 'defzoom' => 18),
|
||||
'place:house_number' => array('label' => 'House Number', 'frequency' => 2086, 'defzoom' => 18),
|
||||
'place:country_code' => array('label' => 'Country Code', 'frequency' => 2086, 'defzoom' => 18),
|
||||
|
||||
//
|
||||
|
||||
'leisure:pitch' => array('label' => 'Pitch', 'frequency' => 762),
|
||||
'highway:unsurfaced' => array('label' => 'Unsurfaced', 'frequency' => 492),
|
||||
'historic:ruins' => array('label' => 'Ruins', 'frequency' => 483, 'icon' => 'tourist_ruin'),
|
||||
'amenity:college' => array('label' => 'College', 'frequency' => 473, 'icon' => 'education_school'),
|
||||
'historic:monument' => array('label' => 'Monument', 'frequency' => 470, 'icon' => 'tourist_monument'),
|
||||
'railway:subway' => array('label' => 'Subway', 'frequency' => 385),
|
||||
'historic:memorial' => array('label' => 'Memorial', 'frequency' => 382, 'icon' => 'tourist_monument'),
|
||||
'leisure:nature_reserve' => array('label' => 'Nature Reserve', 'frequency' => 342),
|
||||
'leisure:common' => array('label' => 'Common', 'frequency' => 322),
|
||||
'waterway:lock_gate' => array('label' => 'Lock Gate', 'frequency' => 321),
|
||||
'natural:fell' => array('label' => 'Fell', 'frequency' => 308),
|
||||
'amenity:nightclub' => array('label' => 'Nightclub', 'frequency' => 292),
|
||||
'highway:path' => array('label' => 'Path', 'frequency' => 287),
|
||||
'leisure:garden' => array('label' => 'Garden', 'frequency' => 285),
|
||||
'landuse:reservoir' => array('label' => 'Reservoir', 'frequency' => 276),
|
||||
'leisure:playground' => array('label' => 'Playground', 'frequency' => 264),
|
||||
'leisure:stadium' => array('label' => 'Stadium', 'frequency' => 212),
|
||||
'historic:mine' => array('label' => 'Mine', 'frequency' => 193, 'icon' => 'poi_mine'),
|
||||
'natural:cliff' => array('label' => 'Cliff', 'frequency' => 193),
|
||||
'tourism:caravan_site' => array('label' => 'Caravan Site', 'frequency' => 183, 'icon' => 'accommodation_caravan_park'),
|
||||
'amenity:bus_station' => array('label' => 'Bus Station', 'frequency' => 181, 'icon' => 'transport_bus_station'),
|
||||
'amenity:kindergarten' => array('label' => 'Kindergarten', 'frequency' => 179),
|
||||
'highway:construction' => array('label' => 'Construction', 'frequency' => 176),
|
||||
'amenity:atm' => array('label' => 'Atm', 'frequency' => 172, 'icon' => 'money_atm2'),
|
||||
'amenity:emergency_phone' => array('label' => 'Emergency Phone', 'frequency' => 164),
|
||||
'waterway:lock' => array('label' => 'Lock', 'frequency' => 146),
|
||||
'waterway:riverbank' => array('label' => 'Riverbank', 'frequency' => 143),
|
||||
'natural:coastline' => array('label' => 'Coastline', 'frequency' => 142),
|
||||
'tourism:viewpoint' => array('label' => 'Viewpoint', 'frequency' => 140, 'icon' => 'tourist_view_point'),
|
||||
'tourism:hostel' => array('label' => 'Hostel', 'frequency' => 140),
|
||||
'tourism:bed_and_breakfast' => array('label' => 'Bed And Breakfast', 'frequency' => 140, 'icon' => 'accommodation_bed_and_breakfast'),
|
||||
'railway:halt' => array('label' => 'Halt', 'frequency' => 135),
|
||||
'railway:platform' => array('label' => 'Platform', 'frequency' => 134),
|
||||
'railway:tram' => array('label' => 'Tram', 'frequency' => 130, 'icon' => 'transport_tram_stop'),
|
||||
'amenity:courthouse' => array('label' => 'Courthouse', 'frequency' => 129, 'icon' => 'amenity_court'),
|
||||
'amenity:recycling' => array('label' => 'Recycling', 'frequency' => 126, 'icon' => 'amenity_recycling'),
|
||||
'amenity:dentist' => array('label' => 'Dentist', 'frequency' => 124, 'icon' => 'health_dentist'),
|
||||
'natural:beach' => array('label' => 'Beach', 'frequency' => 121, 'icon' => 'tourist_beach'),
|
||||
'place:moor' => array('label' => 'Moor', 'frequency' => 118),
|
||||
'amenity:grave_yard' => array('label' => 'Grave Yard', 'frequency' => 110),
|
||||
'waterway:drain' => array('label' => 'Drain', 'frequency' => 108),
|
||||
'landuse:grass' => array('label' => 'Grass', 'frequency' => 106),
|
||||
'landuse:village_green' => array('label' => 'Village Green', 'frequency' => 106),
|
||||
'natural:bay' => array('label' => 'Bay', 'frequency' => 102),
|
||||
'railway:tram_stop' => array('label' => 'Tram Stop', 'frequency' => 101, 'icon' => 'transport_tram_stop'),
|
||||
'leisure:marina' => array('label' => 'Marina', 'frequency' => 98),
|
||||
'highway:stile' => array('label' => 'Stile', 'frequency' => 97),
|
||||
'natural:moor' => array('label' => 'Moor', 'frequency' => 95),
|
||||
'railway:light_rail' => array('label' => 'Light Rail', 'frequency' => 91),
|
||||
'railway:narrow_gauge' => array('label' => 'Narrow Gauge', 'frequency' => 90),
|
||||
'natural:land' => array('label' => 'Land', 'frequency' => 86),
|
||||
'amenity:village_hall' => array('label' => 'Village Hall', 'frequency' => 82),
|
||||
'waterway:dock' => array('label' => 'Dock', 'frequency' => 80),
|
||||
'amenity:veterinary' => array('label' => 'Veterinary', 'frequency' => 79),
|
||||
'landuse:brownfield' => array('label' => 'Brownfield', 'frequency' => 77),
|
||||
'leisure:track' => array('label' => 'Track', 'frequency' => 76),
|
||||
'railway:historic_station' => array('label' => 'Historic Station', 'frequency' => 74),
|
||||
'landuse:construction' => array('label' => 'Construction', 'frequency' => 72),
|
||||
'amenity:prison' => array('label' => 'Prison', 'frequency' => 71, 'icon' => 'amenity_prison'),
|
||||
'landuse:quarry' => array('label' => 'Quarry', 'frequency' => 71),
|
||||
'amenity:telephone' => array('label' => 'Telephone', 'frequency' => 70),
|
||||
'highway:traffic_signals' => array('label' => 'Traffic Signals', 'frequency' => 66),
|
||||
'natural:heath' => array('label' => 'Heath', 'frequency' => 62),
|
||||
'historic:house' => array('label' => 'House', 'frequency' => 61),
|
||||
'amenity:social_club' => array('label' => 'Social Club', 'frequency' => 61),
|
||||
'landuse:military' => array('label' => 'Military', 'frequency' => 61),
|
||||
'amenity:health_centre' => array('label' => 'Health Centre', 'frequency' => 59),
|
||||
'historic:building' => array('label' => 'Building', 'frequency' => 58),
|
||||
'amenity:clinic' => array('label' => 'Clinic', 'frequency' => 57),
|
||||
'highway:services' => array('label' => 'Services', 'frequency' => 56),
|
||||
'amenity:ferry_terminal' => array('label' => 'Ferry Terminal', 'frequency' => 55),
|
||||
'natural:marsh' => array('label' => 'Marsh', 'frequency' => 55),
|
||||
'natural:hill' => array('label' => 'Hill', 'frequency' => 54),
|
||||
'highway:raceway' => array('label' => 'Raceway', 'frequency' => 53),
|
||||
'amenity:taxi' => array('label' => 'Taxi', 'frequency' => 47),
|
||||
'amenity:take_away' => array('label' => 'Take Away', 'frequency' => 45),
|
||||
'amenity:car_rental' => array('label' => 'Car Rental', 'frequency' => 44),
|
||||
'place:islet' => array('label' => 'Islet', 'frequency' => 44),
|
||||
'amenity:nursery' => array('label' => 'Nursery', 'frequency' => 44),
|
||||
'amenity:nursing_home' => array('label' => 'Nursing Home', 'frequency' => 43),
|
||||
'amenity:toilets' => array('label' => 'Toilets', 'frequency' => 38),
|
||||
'amenity:hall' => array('label' => 'Hall', 'frequency' => 38),
|
||||
'waterway:boatyard' => array('label' => 'Boatyard', 'frequency' => 36),
|
||||
'highway:mini_roundabout' => array('label' => 'Mini Roundabout', 'frequency' => 35),
|
||||
'historic:manor' => array('label' => 'Manor', 'frequency' => 35),
|
||||
'tourism:chalet' => array('label' => 'Chalet', 'frequency' => 34),
|
||||
'amenity:bicycle_parking' => array('label' => 'Bicycle Parking', 'frequency' => 34),
|
||||
'amenity:hotel' => array('label' => 'Hotel', 'frequency' => 34),
|
||||
'waterway:weir' => array('label' => 'Weir', 'frequency' => 33),
|
||||
'natural:wetland' => array('label' => 'Wetland', 'frequency' => 33),
|
||||
'natural:cave_entrance' => array('label' => 'Cave Entrance', 'frequency' => 32),
|
||||
'amenity:crematorium' => array('label' => 'Crematorium', 'frequency' => 31),
|
||||
'tourism:picnic_site' => array('label' => 'Picnic Site', 'frequency' => 31),
|
||||
'landuse:wood' => array('label' => 'Wood', 'frequency' => 30),
|
||||
'landuse:basin' => array('label' => 'Basin', 'frequency' => 30),
|
||||
'natural:tree' => array('label' => 'Tree', 'frequency' => 30),
|
||||
'leisure:slipway' => array('label' => 'Slipway', 'frequency' => 29),
|
||||
'landuse:meadow' => array('label' => 'Meadow', 'frequency' => 29),
|
||||
'landuse:piste' => array('label' => 'Piste', 'frequency' => 28),
|
||||
'amenity:care_home' => array('label' => 'Care Home', 'frequency' => 28),
|
||||
'amenity:club' => array('label' => 'Club', 'frequency' => 28),
|
||||
'amenity:medical_centre' => array('label' => 'Medical Centre', 'frequency' => 27),
|
||||
'historic:roman_road' => array('label' => 'Roman Road', 'frequency' => 27),
|
||||
'historic:fort' => array('label' => 'Fort', 'frequency' => 26),
|
||||
'railway:subway_entrance' => array('label' => 'Subway Entrance', 'frequency' => 26),
|
||||
'historic:yes' => array('label' => 'Historic', 'frequency' => 25),
|
||||
'highway:gate' => array('label' => 'Gate', 'frequency' => 25),
|
||||
'leisure:fishing' => array('label' => 'Fishing', 'frequency' => 24),
|
||||
'historic:museum' => array('label' => 'Museum', 'frequency' => 24),
|
||||
'amenity:car_wash' => array('label' => 'Car Wash', 'frequency' => 24),
|
||||
'railway:level_crossing' => array('label' => 'Level Crossing', 'frequency' => 23),
|
||||
'leisure:bird_hide' => array('label' => 'Bird Hide', 'frequency' => 23),
|
||||
'natural:headland' => array('label' => 'Headland', 'frequency' => 21),
|
||||
'tourism:apartments' => array('label' => 'Apartments', 'frequency' => 21),
|
||||
'amenity:shopping' => array('label' => 'Shopping', 'frequency' => 21),
|
||||
'natural:scrub' => array('label' => 'Scrub', 'frequency' => 20),
|
||||
'natural:fen' => array('label' => 'Fen', 'frequency' => 20),
|
||||
'building:yes' => array('label' => 'Building', 'frequency' => 200),
|
||||
'mountain_pass:yes' => array('label' => 'Mountain Pass', 'frequency' => 200),
|
||||
|
||||
'amenity:parking' => array('label' => 'Parking', 'frequency' => 3157),
|
||||
'highway:bus_stop' => array('label' => 'Bus Stop', 'frequency' => 35777, 'icon' => 'transport_bus_stop2'),
|
||||
'place:postcode' => array('label' => 'Postcode', 'frequency' => 27267),
|
||||
'amenity:post_box' => array('label' => 'Post Box', 'frequency' => 9613),
|
||||
|
||||
'place:houses' => array('label' => 'Houses', 'frequency' => 85),
|
||||
'railway:preserved' => array('label' => 'Preserved', 'frequency' => 227),
|
||||
'waterway:derelict_canal' => array('label' => 'Derelict Canal', 'frequency' => 21),
|
||||
'amenity:dead_pub' => array('label' => 'Dead Pub', 'frequency' => 20),
|
||||
'railway:disused_station' => array('label' => 'Disused Station', 'frequency' => 114),
|
||||
'railway:abandoned' => array('label' => 'Abandoned', 'frequency' => 641),
|
||||
'railway:disused' => array('label' => 'Disused', 'frequency' => 72),
|
||||
$aSpecialRadius = array(
|
||||
'place:continent' => 25,
|
||||
'place:country' => 7,
|
||||
'place:state' => 2.6,
|
||||
'place:province' => 2.6,
|
||||
'place:region' => 1.0,
|
||||
'place:county' => 0.7,
|
||||
'place:city' => 0.16,
|
||||
'place:municipality' => 0.16,
|
||||
'place:island' => 0.32,
|
||||
'place:postcode' => 0.16,
|
||||
'place:town' => 0.04,
|
||||
'place:village' => 0.02,
|
||||
'place:hamlet' => 0.02,
|
||||
'place:district' => 0.02,
|
||||
'place:borough' => 0.02,
|
||||
'place:suburb' => 0.02,
|
||||
'place:locality' => 0.01,
|
||||
'place:neighbourhood'=> 0.01,
|
||||
'place:quarter' => 0.01,
|
||||
'place:city_block' => 0.01,
|
||||
'landuse:farm' => 0.01,
|
||||
'place:farm' => 0.01,
|
||||
'place:airport' => 0.015,
|
||||
'aeroway:aerodrome' => 0.015,
|
||||
'railway:station' => 0.005
|
||||
);
|
||||
|
||||
$sClassPlace = $aPlace['class'].':'.$aPlace['type'];
|
||||
|
||||
return $aSpecialRadius[$sClassPlace] ?? 0.00005;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the icon to use with the given object.
|
||||
*/
|
||||
function getIcon($aPlace)
|
||||
{
|
||||
$aIcons = array(
|
||||
'boundary:administrative' => 'poi_boundary_administrative',
|
||||
'place:city' => 'poi_place_city',
|
||||
'place:town' => 'poi_place_town',
|
||||
'place:village' => 'poi_place_village',
|
||||
'place:hamlet' => 'poi_place_village',
|
||||
'place:suburb' => 'poi_place_village',
|
||||
'place:locality' => 'poi_place_village',
|
||||
'place:airport' => 'transport_airport2',
|
||||
'aeroway:aerodrome' => 'transport_airport2',
|
||||
'railway:station' => 'transport_train_station2',
|
||||
'amenity:place_of_worship' => 'place_of_worship_unknown3',
|
||||
'amenity:pub' => 'food_pub',
|
||||
'amenity:bar' => 'food_bar',
|
||||
'amenity:university' => 'education_university',
|
||||
'tourism:museum' => 'tourist_museum',
|
||||
'amenity:arts_centre' => 'tourist_art_gallery2',
|
||||
'tourism:zoo' => 'tourist_zoo',
|
||||
'tourism:theme_park' => 'poi_point_of_interest',
|
||||
'tourism:attraction' => 'poi_point_of_interest',
|
||||
'leisure:golf_course' => 'sport_golf',
|
||||
'historic:castle' => 'tourist_castle',
|
||||
'amenity:hospital' => 'health_hospital',
|
||||
'amenity:school' => 'education_school',
|
||||
'amenity:theatre' => 'tourist_theatre',
|
||||
'amenity:library' => 'amenity_library',
|
||||
'amenity:fire_station' => 'amenity_firestation3',
|
||||
'amenity:police' => 'amenity_police2',
|
||||
'amenity:bank' => 'money_bank2',
|
||||
'amenity:post_office' => 'amenity_post_office',
|
||||
'tourism:hotel' => 'accommodation_hotel2',
|
||||
'amenity:cinema' => 'tourist_cinema',
|
||||
'tourism:artwork' => 'tourist_art_gallery2',
|
||||
'historic:archaeological_site' => 'tourist_archaeological2',
|
||||
'amenity:doctors' => 'health_doctors',
|
||||
'leisure:sports_centre' => 'sport_leisure_centre',
|
||||
'leisure:swimming_pool' => 'sport_swimming_outdoor',
|
||||
'shop:supermarket' => 'shopping_supermarket',
|
||||
'shop:convenience' => 'shopping_convenience',
|
||||
'amenity:restaurant' => 'food_restaurant',
|
||||
'amenity:fast_food' => 'food_fastfood',
|
||||
'amenity:cafe' => 'food_cafe',
|
||||
'tourism:guest_house' => 'accommodation_bed_and_breakfast',
|
||||
'amenity:pharmacy' => 'health_pharmacy_dispensing',
|
||||
'amenity:fuel' => 'transport_fuel',
|
||||
'natural:peak' => 'poi_peak',
|
||||
'natural:wood' => 'landuse_coniferous_and_deciduous',
|
||||
'shop:bicycle' => 'shopping_bicycle',
|
||||
'shop:clothes' => 'shopping_clothes',
|
||||
'shop:hairdresser' => 'shopping_hairdresser',
|
||||
'shop:doityourself' => 'shopping_diy',
|
||||
'shop:estate_agent' => 'shopping_estateagent2',
|
||||
'shop:car' => 'shopping_car',
|
||||
'shop:garden_centre' => 'shopping_garden_centre',
|
||||
'shop:car_repair' => 'shopping_car_repair',
|
||||
'shop:bakery' => 'shopping_bakery',
|
||||
'shop:butcher' => 'shopping_butcher',
|
||||
'shop:apparel' => 'shopping_clothes',
|
||||
'shop:laundry' => 'shopping_laundrette',
|
||||
'shop:beverages' => 'shopping_alcohol',
|
||||
'shop:alcohol' => 'shopping_alcohol',
|
||||
'shop:optician' => 'health_opticians',
|
||||
'shop:chemist' => 'health_pharmacy',
|
||||
'shop:gallery' => 'tourist_art_gallery2',
|
||||
'shop:jewelry' => 'shopping_jewelry',
|
||||
'tourism:information' => 'amenity_information',
|
||||
'historic:ruins' => 'tourist_ruin',
|
||||
'amenity:college' => 'education_school',
|
||||
'historic:monument' => 'tourist_monument',
|
||||
'historic:memorial' => 'tourist_monument',
|
||||
'historic:mine' => 'poi_mine',
|
||||
'tourism:caravan_site' => 'accommodation_caravan_park',
|
||||
'amenity:bus_station' => 'transport_bus_station',
|
||||
'amenity:atm' => 'money_atm2',
|
||||
'tourism:viewpoint' => 'tourist_view_point',
|
||||
'tourism:guesthouse' => 'accommodation_bed_and_breakfast',
|
||||
'railway:tram' => 'transport_tram_stop',
|
||||
'amenity:courthouse' => 'amenity_court',
|
||||
'amenity:recycling' => 'amenity_recycling',
|
||||
'amenity:dentist' => 'health_dentist',
|
||||
'natural:beach' => 'tourist_beach',
|
||||
'railway:tram_stop' => 'transport_tram_stop',
|
||||
'amenity:prison' => 'amenity_prison',
|
||||
'highway:bus_stop' => 'transport_bus_stop2'
|
||||
);
|
||||
|
||||
$sClassPlace = $aPlace['class'].':'.$aPlace['type'];
|
||||
|
||||
return $aIcons[$sClassPlace] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an icon for the given object with its full URL.
|
||||
*/
|
||||
function getIconFile($aPlace)
|
||||
{
|
||||
$sIcon = getIcon($aPlace);
|
||||
|
||||
if (!isset($sIcon)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return CONST_Website_BaseURL.'images/mapicons/'.$sIcon.'.p.20.png';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a class importance value for the given place.
|
||||
*
|
||||
* @param array[] $aPlace Information about the place.
|
||||
*
|
||||
* @return int An importance value. The lower the value, the more
|
||||
* important the class.
|
||||
*/
|
||||
function getImportance($aPlace)
|
||||
{
|
||||
static $aWithImportance = null;
|
||||
|
||||
if ($aWithImportance === null) {
|
||||
$aWithImportance = array_flip(array(
|
||||
'place:country',
|
||||
'place:state',
|
||||
'place:province',
|
||||
'place:county',
|
||||
'place:city',
|
||||
'place:region',
|
||||
'place:island',
|
||||
'place:town',
|
||||
'place:village',
|
||||
'place:hamlet',
|
||||
'place:suburb',
|
||||
'place:locality',
|
||||
'landuse:farm',
|
||||
'place:farm',
|
||||
'highway:motorway_junction',
|
||||
'highway:motorway',
|
||||
'highway:trunk',
|
||||
'highway:primary',
|
||||
'highway:secondary',
|
||||
'highway:tertiary',
|
||||
'highway:residential',
|
||||
'highway:unclassified',
|
||||
'highway:living_street',
|
||||
'highway:service',
|
||||
'highway:track',
|
||||
'highway:road',
|
||||
'highway:byway',
|
||||
'highway:bridleway',
|
||||
'highway:cycleway',
|
||||
'highway:pedestrian',
|
||||
'highway:footway',
|
||||
'highway:steps',
|
||||
'highway:motorway_link',
|
||||
'highway:trunk_link',
|
||||
'highway:primary_link',
|
||||
'landuse:industrial',
|
||||
'landuse:residential',
|
||||
'landuse:retail',
|
||||
'landuse:commercial',
|
||||
'place:airport',
|
||||
'aeroway:aerodrome',
|
||||
'railway:station',
|
||||
'amenity:place_of_worship',
|
||||
'amenity:pub',
|
||||
'amenity:bar',
|
||||
'amenity:university',
|
||||
'tourism:museum',
|
||||
'amenity:arts_centre',
|
||||
'tourism:zoo',
|
||||
'tourism:theme_park',
|
||||
'tourism:attraction',
|
||||
'leisure:golf_course',
|
||||
'historic:castle',
|
||||
'amenity:hospital',
|
||||
'amenity:school',
|
||||
'amenity:theatre',
|
||||
'amenity:public_building',
|
||||
'amenity:library',
|
||||
'amenity:townhall',
|
||||
'amenity:community_centre',
|
||||
'amenity:fire_station',
|
||||
'amenity:police',
|
||||
'amenity:bank',
|
||||
'amenity:post_office',
|
||||
'leisure:park',
|
||||
'amenity:park',
|
||||
'landuse:park',
|
||||
'landuse:recreation_ground',
|
||||
'tourism:hotel',
|
||||
'tourism:motel',
|
||||
'amenity:cinema',
|
||||
'tourism:artwork',
|
||||
'historic:archaeological_site',
|
||||
'amenity:doctors',
|
||||
'leisure:sports_centre',
|
||||
'leisure:swimming_pool',
|
||||
'shop:supermarket',
|
||||
'shop:convenience',
|
||||
'amenity:restaurant',
|
||||
'amenity:fast_food',
|
||||
'amenity:cafe',
|
||||
'tourism:guest_house',
|
||||
'amenity:pharmacy',
|
||||
'amenity:fuel',
|
||||
'natural:peak',
|
||||
'waterway:waterfall',
|
||||
'natural:wood',
|
||||
'natural:water',
|
||||
'landuse:forest',
|
||||
'landuse:cemetery',
|
||||
'landuse:allotments',
|
||||
'landuse:farmyard',
|
||||
'railway:rail',
|
||||
'waterway:canal',
|
||||
'waterway:river',
|
||||
'waterway:stream',
|
||||
'shop:bicycle',
|
||||
'shop:clothes',
|
||||
'shop:hairdresser',
|
||||
'shop:doityourself',
|
||||
'shop:estate_agent',
|
||||
'shop:car',
|
||||
'shop:garden_centre',
|
||||
'shop:car_repair',
|
||||
'shop:newsagent',
|
||||
'shop:bakery',
|
||||
'shop:furniture',
|
||||
'shop:butcher',
|
||||
'shop:apparel',
|
||||
'shop:electronics',
|
||||
'shop:department_store',
|
||||
'shop:books',
|
||||
'shop:yes',
|
||||
'shop:outdoor',
|
||||
'shop:mall',
|
||||
'shop:florist',
|
||||
'shop:charity',
|
||||
'shop:hardware',
|
||||
'shop:laundry',
|
||||
'shop:shoes',
|
||||
'shop:beverages',
|
||||
'shop:dry_cleaning',
|
||||
'shop:carpet',
|
||||
'shop:computer',
|
||||
'shop:alcohol',
|
||||
'shop:optician',
|
||||
'shop:chemist',
|
||||
'shop:gallery',
|
||||
'shop:mobile_phone',
|
||||
'shop:sports',
|
||||
'shop:jewelry',
|
||||
'shop:pet',
|
||||
'shop:beauty',
|
||||
'shop:stationery',
|
||||
'shop:shopping_centre',
|
||||
'shop:general',
|
||||
'shop:electrical',
|
||||
'shop:toys',
|
||||
'shop:jeweller',
|
||||
'shop:betting',
|
||||
'shop:household',
|
||||
'shop:travel_agency',
|
||||
'shop:hifi',
|
||||
'amenity:shop',
|
||||
'tourism:information',
|
||||
'place:house',
|
||||
'place:house_name',
|
||||
'place:house_number',
|
||||
'place:country_code',
|
||||
'leisure:pitch',
|
||||
'highway:unsurfaced',
|
||||
'historic:ruins',
|
||||
'amenity:college',
|
||||
'historic:monument',
|
||||
'railway:subway',
|
||||
'historic:memorial',
|
||||
'leisure:nature_reserve',
|
||||
'leisure:common',
|
||||
'waterway:lock_gate',
|
||||
'natural:fell',
|
||||
'amenity:nightclub',
|
||||
'highway:path',
|
||||
'leisure:garden',
|
||||
'landuse:reservoir',
|
||||
'leisure:playground',
|
||||
'leisure:stadium',
|
||||
'historic:mine',
|
||||
'natural:cliff',
|
||||
'tourism:caravan_site',
|
||||
'amenity:bus_station',
|
||||
'amenity:kindergarten',
|
||||
'highway:construction',
|
||||
'amenity:atm',
|
||||
'amenity:emergency_phone',
|
||||
'waterway:lock',
|
||||
'waterway:riverbank',
|
||||
'natural:coastline',
|
||||
'tourism:viewpoint',
|
||||
'tourism:hostel',
|
||||
'tourism:bed_and_breakfast',
|
||||
'railway:halt',
|
||||
'railway:platform',
|
||||
'railway:tram',
|
||||
'amenity:courthouse',
|
||||
'amenity:recycling',
|
||||
'amenity:dentist',
|
||||
'natural:beach',
|
||||
'place:moor',
|
||||
'amenity:grave_yard',
|
||||
'waterway:drain',
|
||||
'landuse:grass',
|
||||
'landuse:village_green',
|
||||
'natural:bay',
|
||||
'railway:tram_stop',
|
||||
'leisure:marina',
|
||||
'highway:stile',
|
||||
'natural:moor',
|
||||
'railway:light_rail',
|
||||
'railway:narrow_gauge',
|
||||
'natural:land',
|
||||
'amenity:village_hall',
|
||||
'waterway:dock',
|
||||
'amenity:veterinary',
|
||||
'landuse:brownfield',
|
||||
'leisure:track',
|
||||
'railway:historic_station',
|
||||
'landuse:construction',
|
||||
'amenity:prison',
|
||||
'landuse:quarry',
|
||||
'amenity:telephone',
|
||||
'highway:traffic_signals',
|
||||
'natural:heath',
|
||||
'historic:house',
|
||||
'amenity:social_club',
|
||||
'landuse:military',
|
||||
'amenity:health_centre',
|
||||
'historic:building',
|
||||
'amenity:clinic',
|
||||
'highway:services',
|
||||
'amenity:ferry_terminal',
|
||||
'natural:marsh',
|
||||
'natural:hill',
|
||||
'highway:raceway',
|
||||
'amenity:taxi',
|
||||
'amenity:take_away',
|
||||
'amenity:car_rental',
|
||||
'place:islet',
|
||||
'amenity:nursery',
|
||||
'amenity:nursing_home',
|
||||
'amenity:toilets',
|
||||
'amenity:hall',
|
||||
'waterway:boatyard',
|
||||
'highway:mini_roundabout',
|
||||
'historic:manor',
|
||||
'tourism:chalet',
|
||||
'amenity:bicycle_parking',
|
||||
'amenity:hotel',
|
||||
'waterway:weir',
|
||||
'natural:wetland',
|
||||
'natural:cave_entrance',
|
||||
'amenity:crematorium',
|
||||
'tourism:picnic_site',
|
||||
'landuse:wood',
|
||||
'landuse:basin',
|
||||
'natural:tree',
|
||||
'leisure:slipway',
|
||||
'landuse:meadow',
|
||||
'landuse:piste',
|
||||
'amenity:care_home',
|
||||
'amenity:club',
|
||||
'amenity:medical_centre',
|
||||
'historic:roman_road',
|
||||
'historic:fort',
|
||||
'railway:subway_entrance',
|
||||
'historic:yes',
|
||||
'highway:gate',
|
||||
'leisure:fishing',
|
||||
'historic:museum',
|
||||
'amenity:car_wash',
|
||||
'railway:level_crossing',
|
||||
'leisure:bird_hide',
|
||||
'natural:headland',
|
||||
'tourism:apartments',
|
||||
'amenity:shopping',
|
||||
'natural:scrub',
|
||||
'natural:fen',
|
||||
'building:yes',
|
||||
'mountain_pass:yes',
|
||||
'amenity:parking',
|
||||
'highway:bus_stop',
|
||||
'place:postcode',
|
||||
'amenity:post_box',
|
||||
'place:houses',
|
||||
'railway:preserved',
|
||||
'waterway:derelict_canal',
|
||||
'amenity:dead_pub',
|
||||
'railway:disused_station',
|
||||
'railway:abandoned',
|
||||
'railway:disused'
|
||||
));
|
||||
}
|
||||
|
||||
$sClassPlace = $aPlace['class'].':'.$aPlace['type'];
|
||||
|
||||
return $aWithImportance[$sClassPlace] ?? null;
|
||||
}
|
||||
|
||||
131
lib/DB.php
131
lib/DB.php
@@ -135,7 +135,7 @@ class DB
|
||||
try {
|
||||
$stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
|
||||
|
||||
while ($val = $stmt->fetchColumn(0)) { // returns first column or false
|
||||
while (($val = $stmt->fetchColumn(0)) !== false) { // returns first column or false
|
||||
$aVals[] = $val;
|
||||
}
|
||||
} catch (\PDOException $e) {
|
||||
@@ -241,11 +241,103 @@ class DB
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the DSN includes the database name, checks if the connection works.
|
||||
* Returns a list of table names in the database
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function getListOfTables()
|
||||
{
|
||||
return $this->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a table. Returns true if deleted or didn't exist.
|
||||
*
|
||||
* @param string $sTableName
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function databaseExists()
|
||||
public function deleteTable($sTableName)
|
||||
{
|
||||
return $this->exec('DROP TABLE IF EXISTS '.$sTableName.' CASCADE') == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an index exists in the database. Optional filtered by tablename
|
||||
*
|
||||
* @param string $sTableName
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function indexExists($sIndexName, $sTableName = null)
|
||||
{
|
||||
return in_array($sIndexName, $this->getListOfIndices($sTableName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of index names in the database, optional filtered by tablename
|
||||
*
|
||||
* @param string $sTableName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getListOfIndices($sTableName = null)
|
||||
{
|
||||
// table_name | index_name | column_name
|
||||
// -----------------------+---------------------------------+--------------
|
||||
// country_name | idx_country_name_country_code | country_code
|
||||
// country_osm_grid | idx_country_osm_grid_geometry | geometry
|
||||
// import_polygon_delete | idx_import_polygon_delete_osmid | osm_id
|
||||
// import_polygon_delete | idx_import_polygon_delete_osmid | osm_type
|
||||
// import_polygon_error | idx_import_polygon_error_osmid | osm_id
|
||||
// import_polygon_error | idx_import_polygon_error_osmid | osm_type
|
||||
$sSql = <<< END
|
||||
SELECT
|
||||
t.relname as table_name,
|
||||
i.relname as index_name,
|
||||
a.attname as column_name
|
||||
FROM
|
||||
pg_class t,
|
||||
pg_class i,
|
||||
pg_index ix,
|
||||
pg_attribute a
|
||||
WHERE
|
||||
t.oid = ix.indrelid
|
||||
and i.oid = ix.indexrelid
|
||||
and a.attrelid = t.oid
|
||||
and a.attnum = ANY(ix.indkey)
|
||||
and t.relkind = 'r'
|
||||
and i.relname NOT LIKE 'pg_%'
|
||||
FILTERS
|
||||
ORDER BY
|
||||
t.relname,
|
||||
i.relname,
|
||||
a.attname
|
||||
END;
|
||||
|
||||
$aRows = null;
|
||||
if ($sTableName) {
|
||||
$sSql = str_replace('FILTERS', 'and t.relname = :tablename', $sSql);
|
||||
$aRows = $this->getAll($sSql, array(':tablename' => $sTableName));
|
||||
} else {
|
||||
$sSql = str_replace('FILTERS', '', $sSql);
|
||||
$aRows = $this->getAll($sSql);
|
||||
}
|
||||
|
||||
$aIndexNames = array_unique(array_map(function ($aRow) {
|
||||
return $aRow['index_name'];
|
||||
}, $aRows));
|
||||
sort($aIndexNames);
|
||||
|
||||
return $aIndexNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to connect to the database but on failure doesn't throw an exception.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function checkConnection()
|
||||
{
|
||||
$bExists = true;
|
||||
try {
|
||||
@@ -280,11 +372,18 @@ class DB
|
||||
return (float) ($aMatches[1].'.'.$aMatches[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an associate array of postgresql database connection settings. Keys can
|
||||
* be 'database', 'hostspec', 'port', 'username', 'password'.
|
||||
* Returns empty array on failure, thus check if at least 'database' is set.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public static function parseDSN($sDSN)
|
||||
{
|
||||
// https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php
|
||||
$aInfo = array();
|
||||
if (preg_match('/^pgsql:(.+)/', $sDSN, $aMatches)) {
|
||||
if (preg_match('/^pgsql:(.+)$/', $sDSN, $aMatches)) {
|
||||
foreach (explode(';', $aMatches[1]) as $sKeyVal) {
|
||||
list($sKey, $sVal) = explode('=', $sKeyVal, 2);
|
||||
if ($sKey == 'host') $sKey = 'hostspec';
|
||||
@@ -295,4 +394,28 @@ class DB
|
||||
}
|
||||
return $aInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an array of settings and return the DNS string. Key names can be
|
||||
* 'database', 'hostspec', 'port', 'username', 'password' but aliases
|
||||
* 'dbname', 'host' and 'user' are also supported.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public static function generateDSN($aInfo)
|
||||
{
|
||||
$sDSN = sprintf(
|
||||
'pgsql:host=%s;port=%s;dbname=%s;user=%s;password=%s;',
|
||||
$aInfo['host'] ?? $aInfo['hostspec'] ?? '',
|
||||
$aInfo['port'] ?? '',
|
||||
$aInfo['dbname'] ?? $aInfo['database'] ?? '',
|
||||
$aInfo['user'] ?? '',
|
||||
$aInfo['password'] ?? ''
|
||||
);
|
||||
$sDSN = preg_replace('/\b\w+=;/', '', $sDSN);
|
||||
$sDSN = preg_replace('/;\Z/', '', $sDSN);
|
||||
|
||||
return $sDSN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +245,6 @@ class Geocode
|
||||
}
|
||||
|
||||
$this->oPlaceLookup->loadParamArray($oParams, $sForceGeometryType);
|
||||
$this->oPlaceLookup->setIncludePolygonAsPoints($oParams->getBool('polygon'));
|
||||
$this->oPlaceLookup->setIncludeAddressDetails($oParams->getBool('addressdetails', false));
|
||||
}
|
||||
|
||||
@@ -808,9 +807,7 @@ class Geocode
|
||||
$sSQL .= 'WHERE place_id in ('.$sPlaceIds.') ';
|
||||
$sSQL .= ' AND (';
|
||||
$sSQL .= " placex.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank ";
|
||||
if (14 >= $this->iMinAddressRank && 14 <= $this->iMaxAddressRank) {
|
||||
$sSQL .= " OR (extratags->'place') = 'city'";
|
||||
}
|
||||
$sSQL .= " OR placex.rank_search between $this->iMinAddressRank and $this->iMaxAddressRank ";
|
||||
if ($this->aAddressRankList) {
|
||||
$sSQL .= ' OR placex.rank_address in ('.join(',', $this->aAddressRankList).')';
|
||||
}
|
||||
@@ -890,7 +887,6 @@ class Geocode
|
||||
|
||||
$aSearchResults = $this->oPlaceLookup->lookup($aResults);
|
||||
|
||||
$aClassType = ClassTypes\getListWithImportance();
|
||||
$aRecheckWords = preg_split('/\b[\s,\\-]*/u', $sQuery);
|
||||
foreach ($aRecheckWords as $i => $sWord) {
|
||||
if (!preg_match('/[\pL\pN]/', $sWord)) unset($aRecheckWords[$i]);
|
||||
@@ -899,33 +895,23 @@ class Geocode
|
||||
Debug::printVar('Recheck words', $aRecheckWords);
|
||||
|
||||
foreach ($aSearchResults as $iIdx => $aResult) {
|
||||
// Default
|
||||
$fDiameter = ClassTypes\getProperty($aResult, 'defdiameter', 0.0001);
|
||||
$fRadius = ClassTypes\getDefRadius($aResult);
|
||||
|
||||
$aOutlineResult = $this->oPlaceLookup->getOutlines($aResult['place_id'], $aResult['lon'], $aResult['lat'], $fDiameter/2);
|
||||
$aOutlineResult = $this->oPlaceLookup->getOutlines($aResult['place_id'], $aResult['lon'], $aResult['lat'], $fRadius);
|
||||
if ($aOutlineResult) {
|
||||
$aResult = array_merge($aResult, $aOutlineResult);
|
||||
}
|
||||
|
||||
if ($aResult['extra_place'] == 'city') {
|
||||
$aResult['class'] = 'place';
|
||||
$aResult['type'] = 'city';
|
||||
$aResult['rank_search'] = 16;
|
||||
}
|
||||
|
||||
// Is there an icon set for this type of result?
|
||||
$aClassInfo = ClassTypes\getInfo($aResult);
|
||||
|
||||
if ($aClassInfo) {
|
||||
if (isset($aClassInfo['icon'])) {
|
||||
$aResult['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aClassInfo['icon'].'.p.20.png';
|
||||
}
|
||||
|
||||
if (isset($aClassInfo['label'])) {
|
||||
$aResult['label'] = $aClassInfo['label'];
|
||||
}
|
||||
$sIcon = ClassTypes\getIconFile($aResult);
|
||||
if (isset($sIcon)) {
|
||||
$aResult['icon'] = $sIcon;
|
||||
}
|
||||
|
||||
$sLabel = ClassTypes\getLabel($aResult);
|
||||
if (isset($sLabel)) {
|
||||
$aResult['label'] = $sLabel;
|
||||
}
|
||||
$aResult['name'] = $aResult['langaddress'];
|
||||
|
||||
if ($oCtx->hasNearPoint()) {
|
||||
@@ -955,10 +941,9 @@ class Geocode
|
||||
// - number of exact matches from the query
|
||||
$aResult['foundorder'] -= $aResults[$aResult['place_id']]->iExactMatches;
|
||||
// - importance of the class/type
|
||||
if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
|
||||
&& $aClassType[$aResult['class'].':'.$aResult['type']]['importance']
|
||||
) {
|
||||
$aResult['foundorder'] += 0.0001 * $aClassType[$aResult['class'].':'.$aResult['type']]['importance'];
|
||||
$iClassImportance = ClassTypes\getImportance($aResult);
|
||||
if (isset($iClassImportance)) {
|
||||
$aResult['foundorder'] += 0.0001 * $iClassImportance;
|
||||
} else {
|
||||
$aResult['foundorder'] += 0.01;
|
||||
}
|
||||
|
||||
@@ -104,18 +104,29 @@ class ParameterParser
|
||||
}
|
||||
|
||||
foreach ($aLanguages as $sLanguage => $fLanguagePref) {
|
||||
$aLangPrefOrder['short_name:'.$sLanguage] = 'short_name:'.$sLanguage;
|
||||
$aLangPrefOrder['name:'.$sLanguage] = 'name:'.$sLanguage;
|
||||
}
|
||||
$aLangPrefOrder['short_name'] = 'short_name';
|
||||
$aLangPrefOrder['name'] = 'name';
|
||||
$aLangPrefOrder['brand'] = 'brand';
|
||||
foreach ($aLanguages as $sLanguage => $fLanguagePref) {
|
||||
$aLangPrefOrder['official_name:'.$sLanguage] = 'official_name:'.$sLanguage;
|
||||
$aLangPrefOrder['short_name:'.$sLanguage] = 'short_name:'.$sLanguage;
|
||||
}
|
||||
$aLangPrefOrder['official_name'] = 'official_name';
|
||||
$aLangPrefOrder['short_name'] = 'short_name';
|
||||
$aLangPrefOrder['ref'] = 'ref';
|
||||
$aLangPrefOrder['type'] = 'type';
|
||||
return $aLangPrefOrder;
|
||||
}
|
||||
|
||||
public function hasSetAny($aParamNames)
|
||||
{
|
||||
foreach ($aParamNames as $sName) {
|
||||
if ($this->getBool($sName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ class PlaceLookup
|
||||
protected $bExtraTags = false;
|
||||
protected $bNameDetails = false;
|
||||
|
||||
protected $bIncludePolygonAsPoints = false;
|
||||
protected $bIncludePolygonAsText = false;
|
||||
protected $bIncludePolygonAsGeoJSON = false;
|
||||
protected $bIncludePolygonAsKML = false;
|
||||
@@ -38,11 +37,6 @@ class PlaceLookup
|
||||
return $this->bDeDupe;
|
||||
}
|
||||
|
||||
public function setIncludePolygonAsPoints($b = true)
|
||||
{
|
||||
$this->bIncludePolygonAsPoints = $b;
|
||||
}
|
||||
|
||||
public function setIncludeAddressDetails($b)
|
||||
{
|
||||
$this->bAddressDetails = $b;
|
||||
@@ -61,7 +55,6 @@ class PlaceLookup
|
||||
|
||||
if ($sGeomType === null || $sGeomType == 'geojson') {
|
||||
$this->bIncludePolygonAsGeoJSON = $oParams->getBool('polygon_geojson');
|
||||
$this->bIncludePolygonAsPoints = false;
|
||||
}
|
||||
|
||||
if ($oParams->getString('format', '') !== 'geojson') {
|
||||
@@ -100,7 +93,6 @@ class PlaceLookup
|
||||
if ($this->bExtraTags) $aParams['extratags'] = '1';
|
||||
if ($this->bNameDetails) $aParams['namedetails'] = '1';
|
||||
|
||||
if ($this->bIncludePolygonAsPoints) $aParams['polygon'] = '1';
|
||||
if ($this->bIncludePolygonAsText) $aParams['polygon_text'] = '1';
|
||||
if ($this->bIncludePolygonAsGeoJSON) $aParams['polygon_geojson'] = '1';
|
||||
if ($this->bIncludePolygonAsKML) $aParams['polygon_kml'] = '1';
|
||||
@@ -215,7 +207,7 @@ class PlaceLookup
|
||||
'ST_Collect(centroid)',
|
||||
'min(CASE WHEN placex.rank_search < 28 THEN placex.place_id ELSE placex.parent_place_id END)'
|
||||
);
|
||||
$sSQL .= " (extratags->'place') AS extra_place ";
|
||||
$sSQL .= " COALESCE(extratags->'place', extratags->'linked_place') AS extra_place ";
|
||||
$sSQL .= ' FROM placex';
|
||||
$sSQL .= " WHERE place_id in ($sPlaceIDs) ";
|
||||
$sSQL .= ' AND (';
|
||||
@@ -248,7 +240,7 @@ class PlaceLookup
|
||||
$sSQL .= ' ref, ';
|
||||
if ($this->bExtraTags) $sSQL .= 'extratags, ';
|
||||
if ($this->bNameDetails) $sSQL .= 'name, ';
|
||||
$sSQL .= " extratags->'place' ";
|
||||
$sSQL .= ' extra_place ';
|
||||
|
||||
$aSubSelects[] = $sSQL;
|
||||
}
|
||||
@@ -456,10 +448,9 @@ class PlaceLookup
|
||||
}
|
||||
}
|
||||
|
||||
$aPlace['addresstype'] = ClassTypes\getProperty(
|
||||
$aPlace['addresstype'] = ClassTypes\getLabelTag(
|
||||
$aPlace,
|
||||
'simplelabel',
|
||||
$aPlace['class']
|
||||
$aPlace['country_code']
|
||||
);
|
||||
}
|
||||
|
||||
@@ -500,7 +491,7 @@ class PlaceLookup
|
||||
if ($this->bIncludePolygonAsGeoJSON) $sSQL .= ',ST_AsGeoJSON(geometry) as asgeojson';
|
||||
if ($this->bIncludePolygonAsKML) $sSQL .= ',ST_AsKML(geometry) as askml';
|
||||
if ($this->bIncludePolygonAsSVG) $sSQL .= ',ST_AsSVG(geometry) as assvg';
|
||||
if ($this->bIncludePolygonAsText || $this->bIncludePolygonAsPoints) $sSQL .= ',ST_AsText(geometry) as astext';
|
||||
if ($this->bIncludePolygonAsText) $sSQL .= ',ST_AsText(geometry) as astext';
|
||||
if ($fLonReverse != null && $fLatReverse != null) {
|
||||
$sFrom = ' from (SELECT * , CASE WHEN (class = \'highway\') AND (ST_GeometryType(geometry) = \'ST_LineString\') THEN ';
|
||||
$sFrom .=' ST_ClosestPoint(geometry, ST_SetSRID(ST_Point('.$fLatReverse.','.$fLonReverse.'),4326))';
|
||||
@@ -527,8 +518,6 @@ class PlaceLookup
|
||||
if ($this->bIncludePolygonAsKML) $aOutlineResult['askml'] = $aPointPolygon['askml'];
|
||||
if ($this->bIncludePolygonAsSVG) $aOutlineResult['assvg'] = $aPointPolygon['assvg'];
|
||||
if ($this->bIncludePolygonAsText) $aOutlineResult['astext'] = $aPointPolygon['astext'];
|
||||
if ($this->bIncludePolygonAsPoints) $aOutlineResult['aPolyPoints'] = geometryText2Points($aPointPolygon['astext'], $fRadius);
|
||||
|
||||
|
||||
if (abs($aPointPolygon['minlat'] - $aPointPolygon['maxlat']) < 0.0000001) {
|
||||
$aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
|
||||
@@ -551,17 +540,12 @@ class PlaceLookup
|
||||
|
||||
// as a fallback we generate a bounding box without knowing the size of the geometry
|
||||
if ((!isset($aOutlineResult['aBoundingBox'])) && isset($fLon)) {
|
||||
//
|
||||
if ($this->bIncludePolygonAsPoints) {
|
||||
$sGeometryText = 'POINT('.$fLon.','.$fLat.')';
|
||||
$aOutlineResult['aPolyPoints'] = geometryText2Points($sGeometryText, $fRadius);
|
||||
}
|
||||
|
||||
$aBounds = array();
|
||||
$aBounds['minlat'] = $fLat - $fRadius;
|
||||
$aBounds['maxlat'] = $fLat + $fRadius;
|
||||
$aBounds['minlon'] = $fLon - $fRadius;
|
||||
$aBounds['maxlon'] = $fLon + $fRadius;
|
||||
$aBounds = array(
|
||||
'minlat' => $fLat - $fRadius,
|
||||
'maxlat' => $fLat + $fRadius,
|
||||
'minlon' => $fLon - $fRadius,
|
||||
'maxlon' => $fLon + $fRadius
|
||||
);
|
||||
|
||||
$aOutlineResult['aBoundingBox'] = array(
|
||||
(string)$aBounds['minlat'],
|
||||
|
||||
@@ -203,7 +203,7 @@ class SearchContext
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an SQL snipped for computing the distance from the reference point.
|
||||
* Get an SQL snippet for computing the distance from the reference point.
|
||||
*
|
||||
* @param string $sObj SQL variable name to compute the distance from.
|
||||
*
|
||||
@@ -215,7 +215,7 @@ class SearchContext
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an SQL snipped for checking if something is within range of the
|
||||
* Get an SQL snippet for checking if something is within range of the
|
||||
* reference point.
|
||||
*
|
||||
* @param string $sObj SQL variable name to compute if it is within range.
|
||||
@@ -228,14 +228,14 @@ class SearchContext
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an SQL snipped of the importance factor of the viewbox.
|
||||
* Get an SQL snippet of the importance factor of the viewbox.
|
||||
*
|
||||
* The importance factor is computed by checking if an object is within
|
||||
* the viewbox and/or the extended version of the viewbox.
|
||||
*
|
||||
* @param string $sObj SQL variable name of object to weight the importance
|
||||
*
|
||||
* @return string SQL snipped of the factor with a leading multiply sign.
|
||||
* @return string SQL snippet of the factor with a leading multiply sign.
|
||||
*/
|
||||
public function viewboxImportanceSQL($sObj)
|
||||
{
|
||||
@@ -252,7 +252,7 @@ class SearchContext
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL snipped checking if a place ID should be excluded.
|
||||
* SQL snippet checking if a place ID should be excluded.
|
||||
*
|
||||
* @param string $sVariable SQL variable name of place ID to check,
|
||||
* potentially prefixed with more SQL.
|
||||
|
||||
@@ -447,8 +447,8 @@ class SearchDescription
|
||||
$iLimit
|
||||
);
|
||||
|
||||
//now search for housenumber, if housenumber provided
|
||||
if ($this->sHouseNumber && !empty($aResults)) {
|
||||
// Now search for housenumber, if housenumber provided. Can be zero.
|
||||
if (($this->sHouseNumber || $this->sHouseNumber === '0') && !empty($aResults)) {
|
||||
// Downgrade the rank of the street results, they are missing
|
||||
// the housenumber.
|
||||
foreach ($aResults as $oRes) {
|
||||
@@ -660,10 +660,7 @@ class SearchDescription
|
||||
$aTerms[] = 'address_rank between 16 and 27';
|
||||
} elseif (!$this->sClass || $this->iOperator == Operator::NAME) {
|
||||
if ($iMinAddressRank > 0) {
|
||||
$aTerms[] = 'address_rank >= '.$iMinAddressRank;
|
||||
}
|
||||
if ($iMaxAddressRank < 30) {
|
||||
$aTerms[] = 'address_rank <= '.$iMaxAddressRank;
|
||||
$aTerms[] = "((address_rank between $iMinAddressRank and $iMaxAddressRank) or (search_rank between $iMinAddressRank and $iMaxAddressRank))";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
80
lib/Shell.php
Normal file
80
lib/Shell.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim;
|
||||
|
||||
class Shell
|
||||
{
|
||||
public function __construct($sBaseCmd, ...$aParams)
|
||||
{
|
||||
if (!$sBaseCmd) {
|
||||
throw new Exception('Command missing in new() call');
|
||||
}
|
||||
$this->baseCmd = $sBaseCmd;
|
||||
$this->aParams = array();
|
||||
$this->aEnv = null; // null = use the same environment as the current PHP process
|
||||
|
||||
$this->stdoutString = null;
|
||||
|
||||
foreach ($aParams as $sParam) {
|
||||
$this->addParams($sParam);
|
||||
}
|
||||
}
|
||||
|
||||
public function addParams(...$aParams)
|
||||
{
|
||||
foreach ($aParams as $sParam) {
|
||||
if (isset($sParam) && $sParam !== null && $sParam !== '') {
|
||||
array_push($this->aParams, $sParam);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addEnvPair($sKey, $sVal)
|
||||
{
|
||||
if (isset($sKey) && $sKey && isset($sVal)) {
|
||||
if (!isset($this->aEnv)) $this->aEnv = $_ENV;
|
||||
$this->aEnv = array_merge($this->aEnv, array($sKey => $sVal), $_ENV);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function escapedCmd()
|
||||
{
|
||||
$aEscaped = array_map(function ($sParam) {
|
||||
return $this->escapeParam($sParam);
|
||||
}, array_merge(array($this->baseCmd), $this->aParams));
|
||||
|
||||
return join(' ', $aEscaped);
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$sCmd = $this->escapedCmd();
|
||||
// $aEnv does not need escaping, proc_open seems to handle it fine
|
||||
|
||||
$aFDs = array(
|
||||
0 => array('pipe', 'r'),
|
||||
1 => STDOUT,
|
||||
2 => STDERR
|
||||
);
|
||||
$aPipes = null;
|
||||
$hProc = @proc_open($sCmd, $aFDs, $aPipes, null, $this->aEnv);
|
||||
if (!is_resource($hProc)) {
|
||||
throw new \Exception('Unable to run command: ' . $sCmd);
|
||||
}
|
||||
|
||||
fclose($aPipes[0]); // no stdin
|
||||
|
||||
$iStat = proc_close($hProc);
|
||||
return $iStat;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function escapeParam($sParam)
|
||||
{
|
||||
if (preg_match('/^-*\w+$/', $sParam)) return $sParam;
|
||||
return escapeshellarg($sParam);
|
||||
}
|
||||
}
|
||||
42
lib/cmd.php
42
lib/cmd.php
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
require_once(CONST_BasePath.'/lib/Shell.php');
|
||||
|
||||
function getCmdOpt($aArg, $aSpec, &$aResult, $bExitOnError = false, $bExitOnUnknown = false)
|
||||
{
|
||||
@@ -148,30 +149,33 @@ function runSQLScript($sScript, $bfatal = true, $bVerbose = false, $bIgnoreError
|
||||
// Convert database DSN to psql parameters
|
||||
$aDSNInfo = \Nominatim\DB::parseDSN(CONST_Database_DSN);
|
||||
if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
|
||||
$sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
|
||||
|
||||
$oCmd = new \Nominatim\Shell('psql');
|
||||
$oCmd->addParams('--port', $aDSNInfo['port']);
|
||||
$oCmd->addParams('--dbname', $aDSNInfo['database']);
|
||||
if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
|
||||
$sCMD .= ' -h ' . $aDSNInfo['hostspec'];
|
||||
$oCmd->addParams('--host', $aDSNInfo['hostspec']);
|
||||
}
|
||||
if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
|
||||
$sCMD .= ' -U ' . $aDSNInfo['username'];
|
||||
$oCmd->addParams('--username', $aDSNInfo['username']);
|
||||
}
|
||||
$aProcEnv = null;
|
||||
if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
|
||||
$aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
|
||||
if (isset($aDSNInfo['password'])) {
|
||||
$oCmd->addEnvPair('PGPASSWORD', $aDSNInfo['password']);
|
||||
}
|
||||
if (!$bVerbose) {
|
||||
$sCMD .= ' -q';
|
||||
$oCmd->addParams('--quiet');
|
||||
}
|
||||
if ($bfatal && !$bIgnoreErrors) {
|
||||
$sCMD .= ' -v ON_ERROR_STOP=1';
|
||||
$oCmd->addParams('-v', 'ON_ERROR_STOP=1');
|
||||
}
|
||||
|
||||
$aDescriptors = array(
|
||||
0 => array('pipe', 'r'),
|
||||
1 => STDOUT,
|
||||
2 => STDERR
|
||||
);
|
||||
$ahPipes = null;
|
||||
$hProcess = @proc_open($sCMD, $aDescriptors, $ahPipes, null, $aProcEnv);
|
||||
$hProcess = @proc_open($oCmd->escapedCmd(), $aDescriptors, $ahPipes, null, $oCmd->aEnv);
|
||||
if (!is_resource($hProcess)) {
|
||||
fail('unable to start pgsql');
|
||||
}
|
||||
@@ -191,23 +195,3 @@ function runSQLScript($sScript, $bfatal = true, $bVerbose = false, $bIgnoreError
|
||||
fail("pgsql returned with error code ($iReturn)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function runWithEnv($sCmd, $aEnv)
|
||||
{
|
||||
$aFDs = array(
|
||||
0 => array('pipe', 'r'),
|
||||
1 => STDOUT,
|
||||
2 => STDERR
|
||||
);
|
||||
$aPipes = null;
|
||||
$hProc = @proc_open($sCmd, $aFDs, $aPipes, null, $aEnv);
|
||||
if (!is_resource($hProc)) {
|
||||
fail('unable to run command:' . $sCmd);
|
||||
}
|
||||
|
||||
fclose($aPipes[0]); // no stdin
|
||||
|
||||
$iStat = proc_close($hProc);
|
||||
return $iStat;
|
||||
}
|
||||
|
||||
61
lib/lib.php
61
lib/lib.php
@@ -95,8 +95,8 @@ function parseLatLon($sQuery)
|
||||
$fQueryLat = null;
|
||||
$fQueryLon = null;
|
||||
|
||||
if (preg_match('/\\s*([NS])[ ]+([0-9]+[0-9.]*)[° ]+([0-9.]+)?[′\']*[, ]+([EW])[ ]+([0-9]+)[° ]+([0-9]+[0-9.]*)[′\']*\\s*/', $sQuery, $aData)) {
|
||||
/* 1 2 3 4 5 6
|
||||
if (preg_match('/\\s*([NS])[\s]+([0-9]+[0-9.]*)[°\s]+([0-9.]+)?[′\']*[,\s]+([EW])[\s]+([0-9]+)[°\s]+([0-9]+[0-9.]*)[′\']*\\s*/', $sQuery, $aData)) {
|
||||
/* 1 2 3 4 5 6
|
||||
* degrees decimal minutes
|
||||
* N 40 26.767, W 79 58.933
|
||||
* N 40°26.767′, W 79°58.933′
|
||||
@@ -104,8 +104,8 @@ function parseLatLon($sQuery)
|
||||
$sFound = $aData[0];
|
||||
$fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60);
|
||||
$fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60);
|
||||
} elseif (preg_match('/\\s*([0-9]+)[° ]+([0-9]+[0-9.]*)?[′\']*[ ]+([NS])[, ]+([0-9]+)[° ]+([0-9]+[0-9.]*)?[′\' ]+([EW])\\s*/', $sQuery, $aData)) {
|
||||
/* 1 2 3 4 5 6
|
||||
} elseif (preg_match('/\\s*([0-9]+)[°\s]+([0-9]+[0-9.]*)?[′\']*[\s]+([NS])[,\s]+([0-9]+)[°\s]+([0-9]+[0-9.]*)?[′\'\s]+([EW])\\s*/', $sQuery, $aData)) {
|
||||
/* 1 2 3 4 5 6
|
||||
* degrees decimal minutes
|
||||
* 40 26.767 N, 79 58.933 W
|
||||
* 40° 26.767′ N 79° 58.933′ W
|
||||
@@ -113,8 +113,8 @@ function parseLatLon($sQuery)
|
||||
$sFound = $aData[0];
|
||||
$fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60);
|
||||
$fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60);
|
||||
} elseif (preg_match('/\\s*([NS])[ ]([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″"]*[, ]+([EW])[ ]([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″"]*\\s*/', $sQuery, $aData)) {
|
||||
/* 1 2 3 4 5 6 7 8
|
||||
} elseif (preg_match('/\\s*([NS])[\s]+([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+)[″"]*[,\s]+([EW])[\s]+([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+)[″"]*\\s*/', $sQuery, $aData)) {
|
||||
/* 1 2 3 4 5 6 7 8
|
||||
* degrees decimal seconds
|
||||
* N 40 26 46 W 79 58 56
|
||||
* N 40° 26′ 46″, W 79° 58′ 56″
|
||||
@@ -122,8 +122,8 @@ function parseLatLon($sQuery)
|
||||
$sFound = $aData[0];
|
||||
$fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60 + $aData[4]/3600);
|
||||
$fQueryLon = ($aData[5]=='E'?1:-1) * ($aData[6] + $aData[7]/60 + $aData[8]/3600);
|
||||
} elseif (preg_match('/\\s*([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+[0-9.]*)[″" ]+([NS])[, ]+([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+[0-9.]*)[″" ]+([EW])\\s*/', $sQuery, $aData)) {
|
||||
/* 1 2 3 4 5 6 7 8
|
||||
} elseif (preg_match('/\\s*([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+[0-9.]*)[″"\s]+([NS])[,\s]+([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+[0-9.]*)[″"\s]+([EW])\\s*/', $sQuery, $aData)) {
|
||||
/* 1 2 3 4 5 6 7 8
|
||||
* degrees decimal seconds
|
||||
* 40 26 46 N 79 58 56 W
|
||||
* 40° 26′ 46″ N, 79° 58′ 56″ W
|
||||
@@ -132,24 +132,24 @@ function parseLatLon($sQuery)
|
||||
$sFound = $aData[0];
|
||||
$fQueryLat = ($aData[4]=='N'?1:-1) * ($aData[1] + $aData[2]/60 + $aData[3]/3600);
|
||||
$fQueryLon = ($aData[8]=='E'?1:-1) * ($aData[5] + $aData[6]/60 + $aData[7]/3600);
|
||||
} elseif (preg_match('/\\s*([NS])[ ]([0-9]+[0-9]*\\.[0-9]+)[°]*[, ]+([EW])[ ]([0-9]+[0-9]*\\.[0-9]+)[°]*\\s*/', $sQuery, $aData)) {
|
||||
/* 1 2 3 4
|
||||
} elseif (preg_match('/\\s*([NS])[\s]+([0-9]+[0-9]*\\.[0-9]+)[°]*[,\s]+([EW])[\s]+([0-9]+[0-9]*\\.[0-9]+)[°]*\\s*/', $sQuery, $aData)) {
|
||||
/* 1 2 3 4
|
||||
* degrees decimal
|
||||
* N 40.446° W 79.982°
|
||||
*/
|
||||
$sFound = $aData[0];
|
||||
$fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2]);
|
||||
$fQueryLon = ($aData[3]=='E'?1:-1) * ($aData[4]);
|
||||
} elseif (preg_match('/\\s*([0-9]+[0-9]*\\.[0-9]+)[° ]+([NS])[, ]+([0-9]+[0-9]*\\.[0-9]+)[° ]+([EW])\\s*/', $sQuery, $aData)) {
|
||||
/* 1 2 3 4
|
||||
} elseif (preg_match('/\\s*([0-9]+[0-9]*\\.[0-9]+)[°\s]+([NS])[,\s]+([0-9]+[0-9]*\\.[0-9]+)[°\s]+([EW])\\s*/', $sQuery, $aData)) {
|
||||
/* 1 2 3 4
|
||||
* degrees decimal
|
||||
* 40.446° N 79.982° W
|
||||
*/
|
||||
$sFound = $aData[0];
|
||||
$fQueryLat = ($aData[2]=='N'?1:-1) * ($aData[1]);
|
||||
$fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[3]);
|
||||
} elseif (preg_match('/(\\s*\\[|^\\s*|\\s*)(-?[0-9]+[0-9]*\\.[0-9]+)[, ]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]\\s*|\\s*$|\\s*)/', $sQuery, $aData)) {
|
||||
/* 1 2 3 4
|
||||
} elseif (preg_match('/(\\s*\\[|^\\s*|\\s*)(-?[0-9]+[0-9]*\\.[0-9]+)[,\s]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]\\s*|\\s*$|\\s*)/', $sQuery, $aData)) {
|
||||
/* 1 2 3 4
|
||||
* degrees decimal
|
||||
* 12.34, 56.78
|
||||
* 12.34 56.78
|
||||
@@ -165,39 +165,6 @@ function parseLatLon($sQuery)
|
||||
return array($sFound, $fQueryLat, $fQueryLon);
|
||||
}
|
||||
|
||||
|
||||
function geometryText2Points($geometry_as_text, $fRadius)
|
||||
{
|
||||
$aPolyPoints = null;
|
||||
if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch)) {
|
||||
//
|
||||
preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/', $aMatch[1], $aPolyPoints, PREG_SET_ORDER);
|
||||
//
|
||||
} elseif (preg_match('#LINESTRING\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch)) {
|
||||
//
|
||||
preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/', $aMatch[1], $aPolyPoints, PREG_SET_ORDER);
|
||||
//
|
||||
} elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch)) {
|
||||
//
|
||||
preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/', $aMatch[1], $aPolyPoints, PREG_SET_ORDER);
|
||||
//
|
||||
} elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#', $geometry_as_text, $aMatch)) {
|
||||
//
|
||||
$aPolyPoints = createPointsAroundCenter($aMatch[1], $aMatch[2], $fRadius);
|
||||
//
|
||||
}
|
||||
|
||||
if (isset($aPolyPoints)) {
|
||||
$aResultPoints = array();
|
||||
foreach ($aPolyPoints as $aPoint) {
|
||||
$aResultPoints[] = array($aPoint[1], $aPoint[2]);
|
||||
}
|
||||
return $aResultPoints;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function createPointsAroundCenter($fLon, $fLat, $fRadius)
|
||||
{
|
||||
$iSteps = max(8, min(100, ($fRadius * 40000)^2));
|
||||
|
||||
@@ -12,6 +12,8 @@ function formatOSMType($sType, $bIncludeExternal = true)
|
||||
if ($sType == 'T') return 'way';
|
||||
if ($sType == 'I') return 'way';
|
||||
|
||||
// not handled: P, L
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -33,20 +35,39 @@ function wikipediaLink($aFeature)
|
||||
return '';
|
||||
}
|
||||
|
||||
function detailsLink($aFeature, $sTitle = false)
|
||||
function detailsLink($aFeature, $sTitle = false, $sExtraProperties = false)
|
||||
{
|
||||
if (!$aFeature['place_id']) return '';
|
||||
|
||||
return '<a href="details.php?place_id='.$aFeature['place_id'].'">'.($sTitle?$sTitle:$aFeature['place_id']).'</a>';
|
||||
$sHtml = '<a ';
|
||||
if ($sExtraProperties) {
|
||||
$sHtml .= $sExtraProperties.' ';
|
||||
}
|
||||
|
||||
$sHtml .= 'href="details.php?place_id='.$aFeature['place_id'].'">'.($sTitle?$sTitle:$aFeature['place_id']).'</a>';
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
function detailsPermaLink($aFeature, $sRefText = false)
|
||||
function detailsPermaLink($aFeature, $sRefText = false, $sExtraProperties = false)
|
||||
{
|
||||
$sOSMType = formatOSMType($aFeature['osm_type'], false);
|
||||
|
||||
if ($sOSMType) {
|
||||
$sLabel = $sRefText ? $sRefText : $sOSMType.' '.$aFeature['osm_id'];
|
||||
return '<a href="details.php?osmtype='.$aFeature['osm_type'].'&osmid='.$aFeature['osm_id'].'&class='.$aFeature['class'].'">'.$sLabel.'</a>';
|
||||
$sHtml = '<a ';
|
||||
if ($sExtraProperties) {
|
||||
$sHtml .= $sExtraProperties.' ';
|
||||
}
|
||||
$sHtml .= 'href="details.php?osmtype='.$aFeature['osm_type']
|
||||
.'&osmid='.$aFeature['osm_id'].'&class='.$aFeature['class'].'">';
|
||||
|
||||
if ($sRefText) {
|
||||
$sHtml .= $sRefText.'</a>';
|
||||
} else {
|
||||
$sHtml .= $sOSMType.' '.$aFeature['osm_id'].'</a>';
|
||||
}
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
return '';
|
||||
return detailsLink($aFeature, $sRefText, $sExtraProperties);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Nominatim\Setup;
|
||||
|
||||
require_once(CONST_BasePath.'/lib/setup/AddressLevelParser.php');
|
||||
require_once(CONST_BasePath.'/lib/Shell.php');
|
||||
|
||||
class SetupFunctions
|
||||
{
|
||||
@@ -10,11 +11,13 @@ class SetupFunctions
|
||||
protected $iInstances;
|
||||
protected $sModulePath;
|
||||
protected $aDSNInfo;
|
||||
protected $bQuiet;
|
||||
protected $bVerbose;
|
||||
protected $sIgnoreErrors;
|
||||
protected $bEnableDiffUpdates;
|
||||
protected $bEnableDebugStatements;
|
||||
protected $bNoPartitions;
|
||||
protected $bDrop;
|
||||
protected $oDB = null;
|
||||
|
||||
public function __construct(array $aCMDResult)
|
||||
@@ -29,10 +32,13 @@ class SetupFunctions
|
||||
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'];
|
||||
} elseif (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
|
||||
// When flatnode files are enabled then disable cache per default.
|
||||
$this->iCacheMemory = 0;
|
||||
} else {
|
||||
// Otherwise: Assume we can steal all the cache memory in the box.
|
||||
$this->iCacheMemory = getCacheMemoryMB();
|
||||
}
|
||||
|
||||
@@ -46,6 +52,7 @@ class SetupFunctions
|
||||
}
|
||||
|
||||
// setting member variables based on command line options stored in $aCMDResult
|
||||
$this->bQuiet = isset($aCMDResult['quiet']) && $aCMDResult['quiet'];
|
||||
$this->bVerbose = $aCMDResult['verbose'];
|
||||
|
||||
//setting default values which are not set by the update.php array
|
||||
@@ -69,6 +76,8 @@ class SetupFunctions
|
||||
} else {
|
||||
$this->bEnableDiffUpdates = false;
|
||||
}
|
||||
|
||||
$this->bDrop = isset($aCMDResult['drop']) && $aCMDResult['drop'];
|
||||
}
|
||||
|
||||
public function createDB()
|
||||
@@ -76,21 +85,27 @@ class SetupFunctions
|
||||
info('Create DB');
|
||||
$oDB = new \Nominatim\DB;
|
||||
|
||||
if ($oDB->databaseExists()) {
|
||||
if ($oDB->checkConnection()) {
|
||||
fail('database already exists ('.CONST_Database_DSN.')');
|
||||
}
|
||||
|
||||
$sCreateDBCmd = 'createdb -E UTF-8 -p '.$this->aDSNInfo['port'].' '.$this->aDSNInfo['database'];
|
||||
$oCmd = (new \Nominatim\Shell('createdb'))
|
||||
->addParams('-E', 'UTF-8')
|
||||
->addParams('-p', $this->aDSNInfo['port']);
|
||||
|
||||
if (isset($this->aDSNInfo['username'])) {
|
||||
$sCreateDBCmd .= ' -U '.$this->aDSNInfo['username'];
|
||||
$oCmd->addParams('-U', $this->aDSNInfo['username']);
|
||||
}
|
||||
if (isset($this->aDSNInfo['password'])) {
|
||||
$oCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
|
||||
}
|
||||
|
||||
if (isset($this->aDSNInfo['hostspec'])) {
|
||||
$sCreateDBCmd .= ' -h '.$this->aDSNInfo['hostspec'];
|
||||
$oCmd->addParams('-h', $this->aDSNInfo['hostspec']);
|
||||
}
|
||||
$oCmd->addParams($this->aDSNInfo['database']);
|
||||
|
||||
$result = $this->runWithPgEnv($sCreateDBCmd);
|
||||
if ($result != 0) fail('Error executing external command: '.$sCreateDBCmd);
|
||||
$result = $oCmd->run();
|
||||
if ($result != 0) fail('Error executing external command: '.$oCmd->escapedCmd());
|
||||
}
|
||||
|
||||
public function connect()
|
||||
@@ -137,7 +152,7 @@ class SetupFunctions
|
||||
exit(1);
|
||||
}
|
||||
$this->pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
|
||||
$this->pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql.gz');
|
||||
$this->pgsqlRunScriptFile(CONST_ExtraDataPath.'/country_osm_grid.sql.gz');
|
||||
$this->pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
|
||||
$this->pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode_table.sql');
|
||||
|
||||
@@ -158,56 +173,64 @@ class SetupFunctions
|
||||
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)) {
|
||||
if (!file_exists(CONST_Osm2pgsql_Binary)) {
|
||||
echo "Check CONST_Osm2pgsql_Binary in your local settings file.\n";
|
||||
echo "Normally you should not need to set this manually.\n";
|
||||
fail("osm2pgsql not found in '$osm2pgsql'");
|
||||
fail("osm2pgsql not found in '".CONST_Osm2pgsql_Binary."'");
|
||||
}
|
||||
|
||||
$osm2pgsql .= ' -S '.CONST_Import_Style;
|
||||
$oCmd = new \Nominatim\Shell(CONST_Osm2pgsql_Binary);
|
||||
$oCmd->addParams('--style', CONST_Import_Style);
|
||||
|
||||
if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
|
||||
$osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
|
||||
$oCmd->addParams('--flat-nodes', CONST_Osm2pgsql_Flatnode_File);
|
||||
}
|
||||
if (CONST_Tablespace_Osm2pgsql_Data) {
|
||||
$oCmd->addParams('--tablespace-slim-data', CONST_Tablespace_Osm2pgsql_Data);
|
||||
}
|
||||
if (CONST_Tablespace_Osm2pgsql_Index) {
|
||||
$oCmd->addParams('--tablespace-slim-index', CONST_Tablespace_Osm2pgsql_Index);
|
||||
}
|
||||
if (CONST_Tablespace_Place_Data) {
|
||||
$oCmd->addParams('--tablespace-main-data', CONST_Tablespace_Place_Data);
|
||||
}
|
||||
if (CONST_Tablespace_Place_Index) {
|
||||
$oCmd->addParams('--tablespace-main-index', CONST_Tablespace_Place_Index);
|
||||
}
|
||||
$oCmd->addParams('--latlong', '--slim', '--create');
|
||||
$oCmd->addParams('--output', 'gazetteer');
|
||||
$oCmd->addParams('--hstore');
|
||||
$oCmd->addParams('--number-processes', 1);
|
||||
$oCmd->addParams('--cache', $this->iCacheMemory);
|
||||
$oCmd->addParams('--port', $this->aDSNInfo['port']);
|
||||
|
||||
if (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'];
|
||||
$oCmd->addParams('--username', $this->aDSNInfo['username']);
|
||||
}
|
||||
if (isset($this->aDSNInfo['password'])) {
|
||||
$oCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
|
||||
}
|
||||
if (isset($this->aDSNInfo['hostspec'])) {
|
||||
$osm2pgsql .= ' -H '.$this->aDSNInfo['hostspec'];
|
||||
$oCmd->addParams('--host', $this->aDSNInfo['hostspec']);
|
||||
}
|
||||
$osm2pgsql .= ' -d '.$this->aDSNInfo['database'].' '.$sOSMFile;
|
||||
|
||||
$this->runWithPgEnv($osm2pgsql);
|
||||
$oCmd->addParams('--database', $this->aDSNInfo['database']);
|
||||
$oCmd->addParams($sOSMFile);
|
||||
$oCmd->run();
|
||||
|
||||
if (!$this->sIgnoreErrors && !$this->oDB->getRow('select * from place limit 1')) {
|
||||
fail('No Data');
|
||||
}
|
||||
|
||||
if ($this->bDrop) {
|
||||
$this->dropTable('planet_osm_nodes');
|
||||
$this->removeFlatnodeFile();
|
||||
}
|
||||
}
|
||||
|
||||
public function createFunctions()
|
||||
@@ -225,88 +248,34 @@ class SetupFunctions
|
||||
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
|
||||
);
|
||||
$sTemplate = $this->replaceSqlPatterns($sTemplate);
|
||||
|
||||
$this->pgsqlRunScript($sTemplate, false);
|
||||
|
||||
if ($bReverseOnly) {
|
||||
$this->pgExec('DROP TABLE search_name');
|
||||
$this->dropTable('search_name');
|
||||
}
|
||||
|
||||
$oAlParser = new AddressLevelParser(CONST_Address_Level_Config);
|
||||
$oAlParser->createTable($this->oDB, 'address_levels');
|
||||
}
|
||||
|
||||
public function createTableTriggers()
|
||||
{
|
||||
info('Create Tables');
|
||||
|
||||
$sTemplate = file_get_contents(CONST_BasePath.'/sql/table-triggers.sql');
|
||||
$sTemplate = $this->replaceSqlPatterns($sTemplate);
|
||||
|
||||
$this->pgsqlRunScript($sTemplate, false);
|
||||
}
|
||||
|
||||
public function createPartitionTables()
|
||||
{
|
||||
info('Create Partition Tables');
|
||||
|
||||
$sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
|
||||
$sTemplate = $this->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
|
||||
);
|
||||
$sTemplate = $this->replaceSqlPatterns($sTemplate);
|
||||
|
||||
$this->pgsqlRunPartitionScript($sTemplate);
|
||||
}
|
||||
@@ -321,19 +290,14 @@ class SetupFunctions
|
||||
|
||||
public function importWikipediaArticles()
|
||||
{
|
||||
$sWikiArticlesFile = CONST_Wikipedia_Data_Path.'/wikipedia_article.sql.bin';
|
||||
$sWikiRedirectsFile = CONST_Wikipedia_Data_Path.'/wikipedia_redirect.sql.bin';
|
||||
$sWikiArticlesFile = CONST_Wikipedia_Data_Path.'/wikimedia-importance.sql.gz';
|
||||
if (file_exists($sWikiArticlesFile)) {
|
||||
info('Importing wikipedia articles');
|
||||
$this->pgsqlRunDropAndRestore($sWikiArticlesFile);
|
||||
info('Importing wikipedia articles and redirects');
|
||||
$this->dropTable('wikipedia_article');
|
||||
$this->dropTable('wikipedia_redirect');
|
||||
$this->pgsqlRunScriptFile($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');
|
||||
warn('wikipedia importance dump file not found - places will have default importance');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,27 +305,25 @@ class SetupFunctions
|
||||
{
|
||||
info('Drop old Data');
|
||||
|
||||
$this->pgExec('TRUNCATE word');
|
||||
$this->oDB->exec('TRUNCATE word');
|
||||
echo '.';
|
||||
$this->pgExec('TRUNCATE placex');
|
||||
$this->oDB->exec('TRUNCATE placex');
|
||||
echo '.';
|
||||
$this->pgExec('TRUNCATE location_property_osmline');
|
||||
$this->oDB->exec('TRUNCATE location_property_osmline');
|
||||
echo '.';
|
||||
$this->pgExec('TRUNCATE place_addressline');
|
||||
$this->oDB->exec('TRUNCATE place_addressline');
|
||||
echo '.';
|
||||
$this->pgExec('TRUNCATE place_boundingbox');
|
||||
echo '.';
|
||||
$this->pgExec('TRUNCATE location_area');
|
||||
$this->oDB->exec('TRUNCATE location_area');
|
||||
echo '.';
|
||||
if (!$this->dbReverseOnly()) {
|
||||
$this->pgExec('TRUNCATE search_name');
|
||||
$this->oDB->exec('TRUNCATE search_name');
|
||||
echo '.';
|
||||
}
|
||||
$this->pgExec('TRUNCATE search_name_blank');
|
||||
$this->oDB->exec('TRUNCATE search_name_blank');
|
||||
echo '.';
|
||||
$this->pgExec('DROP SEQUENCE seq_place');
|
||||
$this->oDB->exec('DROP SEQUENCE seq_place');
|
||||
echo '.';
|
||||
$this->pgExec('CREATE SEQUENCE seq_place start 100000');
|
||||
$this->oDB->exec('CREATE SEQUENCE seq_place start 100000');
|
||||
echo '.';
|
||||
|
||||
$sSQL = 'select distinct partition from country_name';
|
||||
@@ -369,14 +331,14 @@ class SetupFunctions
|
||||
|
||||
if (!$this->bNoPartitions) $aPartitions[] = 0;
|
||||
foreach ($aPartitions as $sPartition) {
|
||||
$this->pgExec('TRUNCATE location_road_'.$sPartition);
|
||||
$this->oDB->exec('TRUNCATE location_road_'.$sPartition);
|
||||
echo '.';
|
||||
}
|
||||
|
||||
// used by getorcreate_word_id to ignore frequent partial words
|
||||
$sSQL = 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS ';
|
||||
$sSQL .= '$$ SELECT '.CONST_Max_Word_Frequency.' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE';
|
||||
$this->pgExec($sSQL);
|
||||
$this->oDB->exec($sSQL);
|
||||
echo ".\n";
|
||||
|
||||
// pre-create the word list
|
||||
@@ -468,18 +430,15 @@ class SetupFunctions
|
||||
{
|
||||
info('Import Tiger data');
|
||||
|
||||
$aFilenames = glob(CONST_Tiger_Data_Path.'/*.sql');
|
||||
info('Found '.count($aFilenames).' SQL files in path '.CONST_Tiger_Data_Path);
|
||||
if (empty($aFilenames)) {
|
||||
warn('Tiger data import selected but no files found in path '.CONST_Tiger_Data_Path);
|
||||
return;
|
||||
}
|
||||
$sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
|
||||
$sTemplate = 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
|
||||
);
|
||||
$sTemplate = $this->replaceSqlPatterns($sTemplate);
|
||||
|
||||
$this->pgsqlRunScript($sTemplate, false);
|
||||
|
||||
$aDBInstances = array();
|
||||
@@ -492,7 +451,7 @@ class SetupFunctions
|
||||
pg_ping($aDBInstances[$i]);
|
||||
}
|
||||
|
||||
foreach (glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile) {
|
||||
foreach ($aFilenames as $sFile) {
|
||||
echo $sFile.': ';
|
||||
$hFile = fopen($sFile, 'r');
|
||||
$sSQL = fgets($hFile, 100000);
|
||||
@@ -532,24 +491,15 @@ class SetupFunctions
|
||||
|
||||
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
|
||||
);
|
||||
$sTemplate = $this->replaceSqlPatterns($sTemplate);
|
||||
|
||||
$this->pgsqlRunScript($sTemplate, false);
|
||||
}
|
||||
|
||||
public function calculatePostcodes($bCMDResultAll)
|
||||
{
|
||||
info('Calculate Postcodes');
|
||||
$this->pgExec('TRUNCATE location_postcode');
|
||||
$this->oDB->exec('TRUNCATE location_postcode');
|
||||
|
||||
$sSQL = 'INSERT INTO location_postcode';
|
||||
$sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
|
||||
@@ -560,7 +510,7 @@ class SetupFunctions
|
||||
$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);
|
||||
$this->oDB->exec($sSQL);
|
||||
|
||||
// only add postcodes that are not yet available in OSM
|
||||
$sSQL = 'INSERT INTO location_postcode';
|
||||
@@ -570,7 +520,7 @@ class SetupFunctions
|
||||
$sSQL .= ' FROM us_postcode WHERE postcode NOT IN';
|
||||
$sSQL .= ' (SELECT postcode FROM location_postcode';
|
||||
$sSQL .= " WHERE country_code = 'us')";
|
||||
$this->pgExec($sSQL);
|
||||
$this->oDB->exec($sSQL);
|
||||
|
||||
// add missing postcodes for GB (if available)
|
||||
$sSQL = 'INSERT INTO location_postcode';
|
||||
@@ -579,80 +529,94 @@ class SetupFunctions
|
||||
$sSQL .= ' FROM gb_postcode WHERE postcode NOT IN';
|
||||
$sSQL .= ' (SELECT postcode FROM location_postcode';
|
||||
$sSQL .= " WHERE country_code = 'gb')";
|
||||
$this->pgExec($sSQL);
|
||||
$this->oDB->exec($sSQL);
|
||||
|
||||
if (!$bCMDResultAll) {
|
||||
$sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
|
||||
$sSQL .= 'and word NOT IN (SELECT postcode FROM location_postcode)';
|
||||
$this->pgExec($sSQL);
|
||||
$this->oDB->exec($sSQL);
|
||||
}
|
||||
|
||||
$sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
|
||||
$sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
|
||||
$this->pgExec($sSQL);
|
||||
$this->oDB->exec($sSQL);
|
||||
}
|
||||
|
||||
public function index($bIndexNoanalyse)
|
||||
{
|
||||
$sOutputFile = '';
|
||||
$sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$this->aDSNInfo['database'].' -P '
|
||||
.$this->aDSNInfo['port'].' -t '.$this->iInstances.$sOutputFile;
|
||||
$oBaseCmd = (new \Nominatim\Shell(CONST_BasePath.'/nominatim/nominatim.py'))
|
||||
->addParams('--database', $this->aDSNInfo['database'])
|
||||
->addParams('--port', $this->aDSNInfo['port'])
|
||||
->addParams('--threads', $this->iInstances);
|
||||
|
||||
if (!$this->bQuiet) {
|
||||
$oBaseCmd->addParams('-v');
|
||||
}
|
||||
if ($this->bVerbose) {
|
||||
$oBaseCmd->addParams('-v');
|
||||
}
|
||||
if (isset($this->aDSNInfo['hostspec'])) {
|
||||
$sBaseCmd .= ' -H '.$this->aDSNInfo['hostspec'];
|
||||
$oBaseCmd->addParams('--host', $this->aDSNInfo['hostspec']);
|
||||
}
|
||||
if (isset($this->aDSNInfo['username'])) {
|
||||
$sBaseCmd .= ' -U '.$this->aDSNInfo['username'];
|
||||
$oBaseCmd->addParams('--user', $this->aDSNInfo['username']);
|
||||
}
|
||||
if (isset($this->aDSNInfo['password'])) {
|
||||
$oBaseCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
|
||||
}
|
||||
|
||||
info('Index ranks 0 - 4');
|
||||
$iStatus = $this->runWithPgEnv($sBaseCmd.' -R 4');
|
||||
$oCmd = (clone $oBaseCmd)->addParams('--maxrank', 4);
|
||||
echo $oCmd->escapedCmd();
|
||||
|
||||
$iStatus = $oCmd->run();
|
||||
if ($iStatus != 0) {
|
||||
fail('error status ' . $iStatus . ' running nominatim!');
|
||||
}
|
||||
if (!$bIndexNoanalyse) $this->pgsqlRunScript('ANALYSE');
|
||||
|
||||
info('Index ranks 5 - 25');
|
||||
$iStatus = $this->runWithPgEnv($sBaseCmd.' -r 5 -R 25');
|
||||
$oCmd = (clone $oBaseCmd)->addParams('--minrank', 5, '--maxrank', 25);
|
||||
$iStatus = $oCmd->run();
|
||||
if ($iStatus != 0) {
|
||||
fail('error status ' . $iStatus . ' running nominatim!');
|
||||
}
|
||||
if (!$bIndexNoanalyse) $this->pgsqlRunScript('ANALYSE');
|
||||
|
||||
info('Index ranks 26 - 30');
|
||||
$iStatus = $this->runWithPgEnv($sBaseCmd.' -r 26');
|
||||
$oCmd = (clone $oBaseCmd)->addParams('--minrank', 26);
|
||||
$iStatus = $oCmd->run();
|
||||
if ($iStatus != 0) {
|
||||
fail('error status ' . $iStatus . ' running nominatim!');
|
||||
}
|
||||
|
||||
info('Index postcodes');
|
||||
$sSQL = 'UPDATE location_postcode SET indexed_status = 0';
|
||||
$this->pgExec($sSQL);
|
||||
$this->oDB->exec($sSQL);
|
||||
}
|
||||
|
||||
public function createSearchIndices()
|
||||
{
|
||||
info('Create Search indices');
|
||||
|
||||
$sSQL = 'SELECT relname FROM pg_class, pg_index ';
|
||||
$sSQL .= 'WHERE pg_index.indisvalid = false AND pg_index.indexrelid = pg_class.oid';
|
||||
$aInvalidIndices = $this->oDB->getCol($sSQL);
|
||||
|
||||
foreach ($aInvalidIndices as $sIndexName) {
|
||||
info("Cleaning up invalid index $sIndexName");
|
||||
$this->oDB->exec("DROP INDEX $sIndexName;");
|
||||
}
|
||||
|
||||
$sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
|
||||
if (!$this->bDrop) {
|
||||
$sTemplate .= file_get_contents(CONST_BasePath.'/sql/indices_updates.src.sql');
|
||||
}
|
||||
if (!$this->dbReverseOnly()) {
|
||||
$sTemplate .= file_get_contents(CONST_BasePath.'/sql/indices_search.src.sql');
|
||||
}
|
||||
$sTemplate = 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
|
||||
);
|
||||
$sTemplate = $this->replaceSqlPatterns($sTemplate);
|
||||
|
||||
$this->pgsqlRunScript($sTemplate);
|
||||
}
|
||||
|
||||
@@ -711,7 +675,7 @@ class SetupFunctions
|
||||
);
|
||||
|
||||
$aDropTables = array();
|
||||
$aHaveTables = $this->oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'");
|
||||
$aHaveTables = $this->oDB->getListOfTables();
|
||||
|
||||
foreach ($aHaveTables as $sTable) {
|
||||
$bFound = false;
|
||||
@@ -724,10 +688,14 @@ class SetupFunctions
|
||||
if (!$bFound) array_push($aDropTables, $sTable);
|
||||
}
|
||||
foreach ($aDropTables as $sDrop) {
|
||||
if ($this->bVerbose) echo "Dropping table $sDrop\n";
|
||||
$this->oDB->exec("DROP TABLE IF EXISTS $sDrop CASCADE");
|
||||
$this->dropTable($sDrop);
|
||||
}
|
||||
|
||||
$this->removeFlatnodeFile();
|
||||
}
|
||||
|
||||
private function removeFlatnodeFile()
|
||||
{
|
||||
if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
|
||||
if (file_exists(CONST_Osm2pgsql_Flatnode_File)) {
|
||||
if ($this->bVerbose) echo 'Deleting '.CONST_Osm2pgsql_Flatnode_File."\n";
|
||||
@@ -736,22 +704,6 @@ class SetupFunctions
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
@@ -764,7 +716,22 @@ class SetupFunctions
|
||||
|
||||
private function createSqlFunctions()
|
||||
{
|
||||
$sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
|
||||
$sBasePath = CONST_BasePath.'/sql/functions/';
|
||||
$sTemplate = file_get_contents($sBasePath.'utils.sql');
|
||||
$sTemplate .= file_get_contents($sBasePath.'normalization.sql');
|
||||
$sTemplate .= file_get_contents($sBasePath.'ranking.sql');
|
||||
$sTemplate .= file_get_contents($sBasePath.'importance.sql');
|
||||
$sTemplate .= file_get_contents($sBasePath.'address_lookup.sql');
|
||||
$sTemplate .= file_get_contents($sBasePath.'interpolation.sql');
|
||||
if ($this->oDB->tableExists('place')) {
|
||||
$sTemplate .= file_get_contents($sBasePath.'place_triggers.sql');
|
||||
}
|
||||
if ($this->oDB->tableExists('placex')) {
|
||||
$sTemplate .= file_get_contents($sBasePath.'placex_triggers.sql');
|
||||
}
|
||||
if ($this->oDB->tableExists('location_postcode')) {
|
||||
$sTemplate .= file_get_contents($sBasePath.'postcode_triggers.sql');
|
||||
}
|
||||
$sTemplate = str_replace('{modulepath}', $this->sModulePath, $sTemplate);
|
||||
if ($this->bEnableDiffUpdates) {
|
||||
$sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
|
||||
@@ -810,19 +777,21 @@ class SetupFunctions
|
||||
{
|
||||
if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
|
||||
|
||||
$sCMD = 'psql -p '.$this->aDSNInfo['port'].' -d '.$this->aDSNInfo['database'];
|
||||
$oCmd = (new \Nominatim\Shell('psql'))
|
||||
->addParams('--port', $this->aDSNInfo['port'])
|
||||
->addParams('--dbname', $this->aDSNInfo['database']);
|
||||
|
||||
if (!$this->bVerbose) {
|
||||
$sCMD .= ' -q';
|
||||
$oCmd->addParams('--quiet');
|
||||
}
|
||||
if (isset($this->aDSNInfo['hostspec'])) {
|
||||
$sCMD .= ' -h '.$this->aDSNInfo['hostspec'];
|
||||
$oCmd->addParams('--host', $this->aDSNInfo['hostspec']);
|
||||
}
|
||||
if (isset($this->aDSNInfo['username'])) {
|
||||
$sCMD .= ' -U '.$this->aDSNInfo['username'];
|
||||
$oCmd->addParams('--username', $this->aDSNInfo['username']);
|
||||
}
|
||||
$aProcEnv = null;
|
||||
if (isset($this->aDSNInfo['password'])) {
|
||||
$aProcEnv = array_merge(array('PGPASSWORD' => $this->aDSNInfo['password']), $_ENV);
|
||||
$oCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
|
||||
}
|
||||
$ahGzipPipes = null;
|
||||
if (preg_match('/\\.gz$/', $sFilename)) {
|
||||
@@ -831,12 +800,14 @@ class SetupFunctions
|
||||
1 => array('pipe', 'w'),
|
||||
2 => array('file', '/dev/null', 'a')
|
||||
);
|
||||
$hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
|
||||
$oZcatCmd = new \Nominatim\Shell('zcat', $sFilename);
|
||||
|
||||
$hGzipProcess = proc_open($oZcatCmd->escapedCmd(), $aDescriptors, $ahGzipPipes);
|
||||
if (!is_resource($hGzipProcess)) fail('unable to start zcat');
|
||||
$aReadPipe = $ahGzipPipes[1];
|
||||
fclose($ahGzipPipes[0]);
|
||||
} else {
|
||||
$sCMD .= ' -f '.$sFilename;
|
||||
$oCmd->addParams('--file', $sFilename);
|
||||
$aReadPipe = array('pipe', 'r');
|
||||
}
|
||||
$aDescriptors = array(
|
||||
@@ -845,7 +816,8 @@ class SetupFunctions
|
||||
2 => array('file', '/dev/null', 'a')
|
||||
);
|
||||
$ahPipes = null;
|
||||
$hProcess = proc_open($sCMD, $aDescriptors, $ahPipes, null, $aProcEnv);
|
||||
|
||||
$hProcess = proc_open($oCmd->escapedCmd(), $aDescriptors, $ahPipes, null, $oCmd->aEnv);
|
||||
if (!is_resource($hProcess)) fail('unable to start pgsql');
|
||||
// TODO: error checking
|
||||
while (!feof($ahPipes[1])) {
|
||||
@@ -862,43 +834,43 @@ class SetupFunctions
|
||||
}
|
||||
}
|
||||
|
||||
private function replaceTablespace($sTemplate, $sTablespace, $sSql)
|
||||
private function replaceSqlPatterns($sSql)
|
||||
{
|
||||
if ($sTablespace) {
|
||||
$sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"', $sSql);
|
||||
} else {
|
||||
$sSql = str_replace($sTemplate, '', $sSql);
|
||||
$sSql = str_replace('{www-user}', CONST_Database_Web_User, $sSql);
|
||||
|
||||
$aPatterns = array(
|
||||
'{ts:address-data}' => CONST_Tablespace_Address_Data,
|
||||
'{ts:address-index}' => CONST_Tablespace_Address_Index,
|
||||
'{ts:search-data}' => CONST_Tablespace_Search_Data,
|
||||
'{ts:search-index}' => CONST_Tablespace_Search_Index,
|
||||
'{ts:aux-data}' => CONST_Tablespace_Aux_Data,
|
||||
'{ts:aux-index}' => CONST_Tablespace_Aux_Index,
|
||||
);
|
||||
|
||||
foreach ($aPatterns as $sPattern => $sTablespace) {
|
||||
if ($sTablespace) {
|
||||
$sSql = str_replace($sPattern, 'TABLESPACE "'.$sTablespace.'"', $sSql);
|
||||
} else {
|
||||
$sSql = str_replace($sPattern, '', $sSql);
|
||||
}
|
||||
}
|
||||
|
||||
return $sSql;
|
||||
}
|
||||
|
||||
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.
|
||||
* Drop table with the given name if it exists.
|
||||
*
|
||||
* @param string $sSQL SQL command to execute.
|
||||
* @param string $sName Name of table to remove.
|
||||
*
|
||||
* @return null
|
||||
*
|
||||
* @pre connect() must have been called.
|
||||
*/
|
||||
private function pgExec($sSQL)
|
||||
private function dropTable($sName)
|
||||
{
|
||||
$this->oDB->exec($sSQL);
|
||||
if ($this->bVerbose) echo "Dropping table $sName\n";
|
||||
$this->oDB->deleteTable($sName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,27 +30,14 @@ if (empty($aPlace)) {
|
||||
|
||||
$aFilteredPlaces['properties']['geocoding']['label'] = $aPlace['langaddress'];
|
||||
|
||||
$aFilteredPlaces['properties']['geocoding']['name'] = $aPlace['placename'];
|
||||
if ($aPlace['placename'] !== null) {
|
||||
$aFilteredPlaces['properties']['geocoding']['name'] = $aPlace['placename'];
|
||||
}
|
||||
|
||||
if (isset($aPlace['address'])) {
|
||||
$aFieldMappings = array(
|
||||
'house_number' => 'housenumber',
|
||||
'road' => 'street',
|
||||
'locality' => 'locality',
|
||||
'postcode' => 'postcode',
|
||||
'city' => 'city',
|
||||
'district' => 'district',
|
||||
'county' => 'county',
|
||||
'state' => 'state',
|
||||
'country' => 'country'
|
||||
);
|
||||
|
||||
$aAddressNames = $aPlace['address']->getAddressNames();
|
||||
foreach ($aFieldMappings as $sFrom => $sTo) {
|
||||
if (isset($aAddressNames[$sFrom])) {
|
||||
$aFilteredPlaces['properties']['geocoding'][$sTo] = $aAddressNames[$sFrom];
|
||||
}
|
||||
}
|
||||
$aPlace['address']->addGeocodeJsonAddressParts(
|
||||
$aFilteredPlaces['properties']['geocoding']
|
||||
);
|
||||
|
||||
$aFilteredPlaces['properties']['geocoding']['admin']
|
||||
= $aPlace['address']->getAdminLevels();
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<body id="reverse-page">
|
||||
|
||||
<?php include(CONST_BasePath.'/lib/template/includes/html-top-navigation.php'); ?>
|
||||
|
||||
<div class="top-bar">
|
||||
<form class="form-inline" role="search" accept-charset="UTF-8" action="<?php echo CONST_Website_BaseURL; ?>reverse.php">
|
||||
<div class="form-group">
|
||||
<input name="format" type="hidden" value="html">
|
||||
@@ -64,7 +64,7 @@
|
||||
<a href="<?php echo CONST_Website_BaseURL; ?>search.php">forward search</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
else
|
||||
echo ' <span class="type">('.ucwords(str_replace('_',' ',$aResult['type'])).')</span>';
|
||||
echo '<p>'.$aResult['lat'].','.$aResult['lon'].'</p>';
|
||||
echo ' <a class="btn btn-default btn-xs details" href="details.php?place_id='.$aResult['place_id'].'">details</a>';
|
||||
echo detailsPermaLink($aResult, 'details', 'class="btn btn-default btn-xs details"');
|
||||
echo '</div>';
|
||||
?>
|
||||
</div>
|
||||
|
||||
48
lib/template/deletable-html.php
Normal file
48
lib/template/deletable-html.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
header("content-type: text/html; charset=UTF-8");
|
||||
include(CONST_BasePath.'/lib/template/includes/html-header.php');
|
||||
?>
|
||||
<title>Nominatim Deleted Data</title>
|
||||
<meta name="description" content="List of OSM data that has been deleted" lang="en-US" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Deletable</h1>
|
||||
<p>
|
||||
<?php echo sizeof($aPolygons) ?> objects have been deleted in OSM but are still in the Nominatim database.
|
||||
Also available in <a href="<?php echo CONST_Website_BaseURL; ?>deletable.php?format=json">JSON format</a>.
|
||||
</p>
|
||||
|
||||
<table class="table table-striped table-hover">
|
||||
<?php
|
||||
|
||||
if (!empty($aPolygons)) {
|
||||
echo '<tr>';
|
||||
foreach (array_keys($aPolygons[0]) as $sCol) {
|
||||
echo '<th>'.$sCol.'</th>';
|
||||
}
|
||||
echo '</tr>';
|
||||
foreach ($aPolygons as $aRow) {
|
||||
echo '<tr>';
|
||||
foreach ($aRow as $sCol => $sVal) {
|
||||
switch ($sCol) {
|
||||
case 'osm_id':
|
||||
echo '<td>'.osmLink($aRow).'</td>';
|
||||
break;
|
||||
case 'place_id':
|
||||
echo '<td>'.detailsLink($aRow).'</td>';
|
||||
break;
|
||||
default:
|
||||
echo '<td>'.($sVal?$sVal:' ').'</td>';
|
||||
break;
|
||||
}
|
||||
}
|
||||
echo '</tr>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -20,20 +20,25 @@
|
||||
}
|
||||
|
||||
|
||||
function format_distance($fDistance)
|
||||
function format_distance($fDistance, $bInMeters = false)
|
||||
{
|
||||
// $fDistance is in meters
|
||||
if ($fDistance < 1)
|
||||
{
|
||||
return '0';
|
||||
}
|
||||
elseif ($fDistance < 1000)
|
||||
{
|
||||
return'<abbr class="distance" title="'.$fDistance.'">~'.(round($fDistance,0)).' m</abbr>';
|
||||
}
|
||||
else
|
||||
{
|
||||
return'<abbr class="distance" title="'.$fDistance.'">~'.(round($fDistance/1000,1)).' km</abbr>';
|
||||
if ($bInMeters) {
|
||||
// $fDistance is in meters
|
||||
if ($fDistance < 1) {
|
||||
return '0';
|
||||
}
|
||||
elseif ($fDistance < 1000) {
|
||||
return '<abbr class="distance" title="'.$fDistance.' meters">~'.(round($fDistance,0)).' m</abbr>';
|
||||
}
|
||||
else {
|
||||
return '<abbr class="distance" title="'.$fDistance.' meters">~'.(round($fDistance/1000,1)).' km</abbr>';
|
||||
}
|
||||
} else {
|
||||
if ($fDistance == 0) {
|
||||
return '0';
|
||||
} else {
|
||||
return '<abbr class="distance" title="spheric distance '.$fDistance.'">'.(round($fDistance,4)).'</abbr>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,25 +57,33 @@
|
||||
return $sHTML;
|
||||
}
|
||||
|
||||
function map_icon($sIcon)
|
||||
function map_icon($aPlace)
|
||||
{
|
||||
if ($sIcon){
|
||||
echo '<img id="mapicon" src="'.CONST_Website_BaseURL.'images/mapicons/'.$sIcon.'.n.32.png'.'" alt="'.$sIcon.'" />';
|
||||
$sIcon = Nominatim\ClassTypes\getIconFile($aPlace);
|
||||
if (isset($sIcon)) {
|
||||
$sLabel = Nominatim\ClassTypes\getIcon($aPlace);
|
||||
echo '<img id="mapicon" src="'.$sIcon.'" alt="'.$sLabel.'" />';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _one_row($aAddressLine){
|
||||
function _one_row($aAddressLine, $bDistanceInMeters = false){
|
||||
$bNotUsed = isset($aAddressLine['isaddress']) && !$aAddressLine['isaddress'];
|
||||
|
||||
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>' . $aAddressLine['class'].':'.$aAddressLine['type'] . "</td>\n";
|
||||
echo ' <td class="name">'.(trim($aAddressLine['localname'])!==null?$aAddressLine['localname']:'<span class="noname">No Name</span>')."</td>\n";
|
||||
echo ' <td>' . $aAddressLine['class'].':'.$aAddressLine['type'];
|
||||
if ($aAddressLine['type'] == 'administrative'
|
||||
&& isset($aAddressLine['place_type']))
|
||||
{
|
||||
echo '('.$aAddressLine['place_type'].')';
|
||||
}
|
||||
echo "</td>\n";
|
||||
echo ' <td>' . osmLink($aAddressLine) . "</td>\n";
|
||||
echo ' <td>' . (isset($aAddressLine['rank_address']) ? $aAddressLine['rank_address'] : '') . "</td>\n";
|
||||
echo ' <td>' . ($aAddressLine['admin_level'] < 15 ? $aAddressLine['admin_level'] : '') . "</td>\n";
|
||||
echo ' <td>' . format_distance($aAddressLine['distance'])."</td>\n";
|
||||
echo ' <td>' . detailsLink($aAddressLine,'details >') . "</td>\n";
|
||||
echo ' <td>' . format_distance($aAddressLine['distance'], $bDistanceInMeters)."</td>\n";
|
||||
echo ' <td>' . detailsPermaLink($aAddressLine,'details >') . "</td>\n";
|
||||
echo "</tr>\n";
|
||||
}
|
||||
|
||||
@@ -98,11 +111,10 @@
|
||||
<div class="col-sm-10">
|
||||
<h1>
|
||||
<?php echo $aPointDetails['localname'] ?>
|
||||
<small><?php echo detailsPermaLink($aPointDetails, 'link to this page') ?></small>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col-sm-2 text-right">
|
||||
<?php map_icon($aPointDetails['icon']) ?>
|
||||
<?php map_icon($aPointDetails) ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -122,6 +134,8 @@
|
||||
kv('Coverage' , ($aPointDetails['isarea']?'Polygon':'Point') );
|
||||
kv('Centre Point' , $aPointDetails['lat'].','.$aPointDetails['lon'] );
|
||||
kv('OSM' , osmLink($aPointDetails) );
|
||||
kv('Place Id (<a href="https://nominatim.org/release-docs/develop/api/Output/#place_id-is-not-a-persistent-id">on this server</a>)'
|
||||
, $aPointDetails['place_id'] );
|
||||
if ($aPointDetails['wikipedia'])
|
||||
{
|
||||
kv('Wikipedia Calculated' , wikipediaLink($aPointDetails) );
|
||||
@@ -173,7 +187,7 @@
|
||||
{
|
||||
headline('Linked Places');
|
||||
foreach ($aLinkedLines as $aAddressLine) {
|
||||
_one_row($aAddressLine);
|
||||
_one_row($aAddressLine, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +226,7 @@
|
||||
headline3($sGroupHeading);
|
||||
|
||||
foreach ($aHierarchyLines as $aAddressLine) {
|
||||
_one_row($aAddressLine);
|
||||
_one_row($aAddressLine, true);
|
||||
}
|
||||
}
|
||||
if (count($aHierarchyLines) >= 500) {
|
||||
|
||||
55
lib/template/details-index-html.php
Normal file
55
lib/template/details-index-html.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
header("content-type: text/html; charset=UTF-8");
|
||||
?>
|
||||
<?php include(CONST_BasePath.'/lib/template/includes/html-header.php'); ?>
|
||||
<link href="css/common.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/details.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
|
||||
|
||||
<body id="details-index-page">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<h1>Show details for place</h1>
|
||||
|
||||
<div class="search-form">
|
||||
<h4>Search by place id</h4>
|
||||
|
||||
<form class="form-inline" action="details.php">
|
||||
<input type="edit" class="form-control input-sm" pattern="^[0-9]+$" name="place_id" placeholder="12345" />
|
||||
<input type="submit" class="btn btn-primary btn-sm" value="Show" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="search-form">
|
||||
<h4>Search by OSM type and OSM id</h4>
|
||||
|
||||
<form id="form-by-type-and-id" class="form-inline" action="details.php">
|
||||
<input type="edit" class="form-control input-sm" pattern="^[NWR][0-9]+$" placeholder="N123 or W123 or R123" />
|
||||
<input type="hidden" name="osmtype" />
|
||||
<input type="hidden" name="osmid" />
|
||||
<input type="submit" class="btn btn-primary btn-sm" value="Show" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="search-form">
|
||||
<h4>Search by openstreetmap.org URL</h4>
|
||||
|
||||
<form id="form-by-osm-url" class="form-inline" action="details.php">
|
||||
<input type="edit" class="form-control input-sm" pattern=".*openstreetmap.*" placeholder="https://www.openstreetmap.org/relation/123" />
|
||||
<input type="hidden" name="osmtype" />
|
||||
<input type="hidden" name="osmid" />
|
||||
<input type="submit" class="btn btn-primary btn-sm" value="Show" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<?php include(CONST_BasePath.'/lib/template/includes/html-footer.php'); ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -26,8 +26,9 @@ $aPlaceDetails['calculated_importance'] = (float) $aPointDetails['calculated_imp
|
||||
|
||||
$aPlaceDetails['extratags'] = $aPointDetails['aExtraTags'];
|
||||
$aPlaceDetails['calculated_wikipedia'] = $aPointDetails['wikipedia'];
|
||||
if ($aPointDetails['icon']) {
|
||||
$aPlaceDetails['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aPointDetails['icon'].'.n.32.png';
|
||||
$sIcon = Nominatim\ClassTypes\getIconFile($aPointDetails);
|
||||
if (isset($sIcon)) {
|
||||
$aPlaceDetails['icon'] = $sIcon;
|
||||
}
|
||||
|
||||
$aPlaceDetails['rank_address'] = (int) $aPointDetails['rank_address'];
|
||||
@@ -47,11 +48,13 @@ $funcMapAddressLine = function ($aFull) {
|
||||
'place_id' => isset($aFull['place_id']) ? (int) $aFull['place_id'] : null,
|
||||
'osm_id' => isset($aFull['osm_id']) ? (int) $aFull['osm_id'] : null,
|
||||
'osm_type' => isset($aFull['osm_type']) ? $aFull['osm_type'] : null,
|
||||
'place_type' => isset($aFull['place_type']) ? $aFull['place_type'] : null,
|
||||
'class' => $aFull['class'],
|
||||
'type' => $aFull['type'],
|
||||
'admin_level' => isset($aFull['admin_level']) ? (int) $aFull['admin_level'] : null,
|
||||
'rank_address' => $aFull['rank_address'] ? (int) $aFull['rank_address'] : null,
|
||||
'distance' => (float) $aFull['distance']
|
||||
'distance' => (float) $aFull['distance'],
|
||||
'isaddress' => isset($aFull['isaddress']) ? (bool) $aFull['isaddress'] : null
|
||||
);
|
||||
|
||||
return $aMapped;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<base href="<?php echo CONST_Website_BaseURL;?>" />
|
||||
<link href="nominatim.xml" rel="search" title="Nominatim Search" type="application/opensearchdescription+xml" />
|
||||
<link href="css/leaflet.css" rel="stylesheet" />
|
||||
<link href="css/Control.Minimap.min.css" rel="stylesheet" />
|
||||
<link href="css/bootstrap-theme.min.css" rel="stylesheet" />
|
||||
|
||||
@@ -21,8 +21,10 @@
|
||||
About & Help <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li><a href="https://wiki.openstreetmap.org/wiki/Nominatim" target="_blank">Documentation</a></li>
|
||||
<li><a href="https://wiki.openstreetmap.org/wiki/Nominatim/FAQ" target="_blank">FAQ</a></li>
|
||||
<li><a href="https://nominatim.org/release-docs/develop/api/Overview/" target="_blank">API Reference</a></li>
|
||||
<li><a href="https://nominatim.org/release-docs/develop/api/Faq/" target="_blank">FAQ</a></li>
|
||||
<li><a href="https://help.openstreetmap.org/tags/nominatim/">OpenStreetMap Help</a></li>
|
||||
<li><a href="https://github.com/openstreetmap/Nominatim">Nominatim on Github</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="#" class="" data-toggle="modal" data-target="#report-modal">Report problem with results</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -6,4 +6,4 @@ look up data by its geographic coordinate (reverse search). Each result comes wi
|
||||
link to a details page where you can inspect what data about the object is saved in
|
||||
the database and investigate how the address of the object has been computed.</p>
|
||||
|
||||
For more information visit the <a href="https://wiki.openstreetmap.org/wiki/Nominatim">Nominatim wiki page</a>.
|
||||
For more information visit the <a href="https://nominatim.org">Nominatim home page</a>.
|
||||
|
||||
@@ -1,24 +1,42 @@
|
||||
<p>
|
||||
Before reporting problems please read the <a target="_blank" href="https://wiki.openstreetmap.org/wiki/Nominatim">user documentation</a>
|
||||
and
|
||||
<a target="_blank" href="https://wiki.openstreetmap.org/wiki/Nominatim/FAQ">FAQ</a>.
|
||||
Before reporting problems please read the <a target="_blank" href="https://nominatim.org/release-docs/develop/api/Overview">user documentation</a>.
|
||||
|
||||
<h4>Finding the expected result</h4>
|
||||
|
||||
First of all, please make sure that the result that you expect is
|
||||
available in the OpenStreetMap data.
|
||||
|
||||
To find the OpenStreetMap data, do the following:
|
||||
|
||||
<ul>
|
||||
<li>Go to <a href="https://openstreetmap.org">https://openstreetmap.org</a>.</li>
|
||||
<li>Go to the area of the map where you expect the result
|
||||
and zoom in until you see the object you are looking for.</li>
|
||||
<li>Click on the question mark on the right side of the map,
|
||||
then with the question cursor on the map where your object is located.</li>
|
||||
<li>Find the object of interest in the list that appears on the left side.</li>
|
||||
<li>Click on the object and note down the URL that the browser shows.</li>
|
||||
</ul>
|
||||
|
||||
If you cannot find the data you are looking for, there is a good chance
|
||||
that it has not been entered yet. You should <a href="https://www.openstreetmap.org/fixthemap">report or fix the problem in OpenStreetMap</a> directly.
|
||||
|
||||
<h4>Reporting bad searches</h4>
|
||||
|
||||
Problems may be reported at the <a target="_blank" href="https://github.com/openstreetmap/nominatim/issues">issue tracker on github</a>. Please read through
|
||||
the open tickets first and check if your problem has not already been
|
||||
reported.
|
||||
|
||||
When reporting a problem, include the following:
|
||||
|
||||
<ul>
|
||||
<li>A full description of the problem, including the exact term you
|
||||
were searching for.</li>
|
||||
<li>The result you get.</li>
|
||||
<li>The OpenStreetMap object you expect to find (see above).</li>
|
||||
</ul>
|
||||
|
||||
For general questions about installing and searching in Nominatim, please
|
||||
use <a href="https://help.openstreetmap.org/tags/nominatim/">Help OpenStreetMap</a>.
|
||||
|
||||
If your problem relates to the address of a particular search result please use the 'details' link
|
||||
to check how the address was generated before reporting a problem.
|
||||
</p>
|
||||
<p>
|
||||
Use <a target="_blank" href="https://github.com/openstreetmap/nominatim/issues">Nominatim issues on github</a>
|
||||
to report problems.
|
||||
<!-- You can search for existing bug reports
|
||||
<a href="https://trac.openstreetmap.org/query?status=new&status=assigned&status=reopened&component=nominatim&order=priority">here</a>.</p>
|
||||
-->
|
||||
</p>
|
||||
<p>
|
||||
Please ensure that you include a full description of the problem, including the search
|
||||
query that you used, the problem with the result and, if the problem relates to missing data,
|
||||
the osm type (node, way, relation) and id of the item that is missing.
|
||||
</p>
|
||||
<p>
|
||||
Problems that contain enough detail are likely to get looked at before ones that require
|
||||
significant research.
|
||||
</p>
|
||||
|
||||
71
lib/template/polygons-html.php
Normal file
71
lib/template/polygons-html.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
header("content-type: text/html; charset=UTF-8");
|
||||
include(CONST_BasePath.'/lib/template/includes/html-header.php');
|
||||
?>
|
||||
<title>Nominatim Broken Polygon Data</title>
|
||||
<meta name="description" content="List of broken OSM polygon data by date" lang="en-US" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<h1>Broken polygons</h1>
|
||||
|
||||
<p>
|
||||
Total number of broken polygons: <?php echo $iTotalBroken ?>.
|
||||
Also available in <a href="<?php echo CONST_Website_BaseURL; ?>polygons.php?format=json">JSON format</a>.
|
||||
</p>
|
||||
|
||||
<table class="table table-striped table-hover">
|
||||
|
||||
<?php
|
||||
if (!empty($aPolygons)) {
|
||||
|
||||
echo '<tr>';
|
||||
//var_dump($aPolygons[0]);
|
||||
foreach (array_keys($aPolygons[0]) as $sCol) {
|
||||
echo '<th>'.$sCol.'</th>';
|
||||
}
|
||||
echo '<th> </th>';
|
||||
echo '</tr>';
|
||||
$aSeen = array();
|
||||
foreach ($aPolygons as $aRow) {
|
||||
if (isset($aSeen[$aRow['osm_type'].$aRow['osm_id']])) continue;
|
||||
$aSeen[$aRow['osm_type'].$aRow['osm_id']] = 1;
|
||||
|
||||
echo '<tr>';
|
||||
$sOSMType = formatOSMType($aRow['osm_type']);
|
||||
foreach ($aRow as $sCol => $sVal) {
|
||||
switch ($sCol) {
|
||||
case 'errormessage':
|
||||
if (preg_match('/Self-intersection\\[([0-9.\\-]+) ([0-9.\\-]+)\\]/', $sVal, $aMatch)) {
|
||||
$aRow['lat'] = $aMatch[2];
|
||||
$aRow['lon'] = $aMatch[1];
|
||||
$sUrl = sprintf('https://www.openstreetmap.org/?lat=%f&lon=%f&zoom=18&layers=M&%s=%d',
|
||||
$aRow['lat'],
|
||||
$aRow['lon'],
|
||||
$sOSMType,
|
||||
$aRow['osm_id']);
|
||||
echo '<td><a href="'.$sUrl.'">'.($sVal?$sVal:' ').'</a></td>';
|
||||
} else {
|
||||
echo '<td>'.($sVal?$sVal:' ').'</td>';
|
||||
}
|
||||
break;
|
||||
case 'osm_id':
|
||||
echo '<td>'.osmLink(array('osm_type' => $aRow['osm_type'], 'osm_id' => $aRow['osm_id'])).'</td>';
|
||||
break;
|
||||
default:
|
||||
echo '<td>'.($sVal?$sVal:' ').'</td>';
|
||||
break;
|
||||
}
|
||||
}
|
||||
$sJosmUrl = 'http://localhost:8111/import?url=https://www.openstreetmap.org/api/0.6/'.$sOSMType.'/'.$aRow['osm_id'].'/full';
|
||||
echo '<td><a href="'.$sJosmUrl.'" target="josm">josm</a></td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -25,10 +25,6 @@ foreach ($aBatchResults as $aSearchResults) {
|
||||
$aPointDetails['aBoundingBox'][2],
|
||||
$aPointDetails['aBoundingBox'][3]
|
||||
);
|
||||
|
||||
if (isset($aPointDetails['aPolyPoints']) && $bShowPolygons) {
|
||||
$aPlace['polygonpoints'] = $aPointDetails['aPolyPoints'];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($aPointDetails['zoom'])) {
|
||||
|
||||
@@ -20,27 +20,14 @@ foreach ($aSearchResults as $iResNum => $aPointDetails) {
|
||||
|
||||
$aPlace['properties']['geocoding']['label'] = $aPointDetails['langaddress'];
|
||||
|
||||
$aPlace['properties']['geocoding']['name'] = $aPointDetails['placename'];
|
||||
if ($aPointDetails['placename'] !== null) {
|
||||
$aPlace['properties']['geocoding']['name'] = $aPointDetails['placename'];
|
||||
}
|
||||
|
||||
if (isset($aPointDetails['address'])) {
|
||||
$aFieldMappings = array(
|
||||
'house_number' => 'housenumber',
|
||||
'road' => 'street',
|
||||
'locality' => 'locality',
|
||||
'postcode' => 'postcode',
|
||||
'city' => 'city',
|
||||
'district' => 'district',
|
||||
'county' => 'county',
|
||||
'state' => 'state',
|
||||
'country' => 'country'
|
||||
);
|
||||
|
||||
$aAddrNames = $aPointDetails['address']->getAddressNames();
|
||||
foreach ($aFieldMappings as $sFrom => $sTo) {
|
||||
if (isset($aAddrNames[$sFrom])) {
|
||||
$aPlace['properties']['geocoding'][$sTo] = $aAddrNames[$sFrom];
|
||||
}
|
||||
}
|
||||
$aPointDetails['address']->addGeocodeJsonAddressParts(
|
||||
$aPlace['properties']['geocoding']
|
||||
);
|
||||
|
||||
$aPlace['properties']['geocoding']['admin']
|
||||
= $aPointDetails['address']->getAdminLevels();
|
||||
|
||||
@@ -10,26 +10,46 @@
|
||||
|
||||
<?php include(CONST_BasePath.'/lib/template/includes/html-top-navigation.php'); ?>
|
||||
|
||||
<form class="form-inline" role="search" accept-charset="UTF-8" action="<?php echo CONST_Website_BaseURL; ?>search.php">
|
||||
<div class="form-group">
|
||||
<input id="q" name="q" type="text" class="form-control input-sm" placeholder="Search" value="<?php echo htmlspecialchars($sQuery); ?>" >
|
||||
<div class="top-bar" id="structured-query-selector">
|
||||
<div class="search-type-link">
|
||||
<a id="switch-to-reverse" href="<?php echo CONST_Website_BaseURL; ?>reverse.php?format=html">reverse search</a>
|
||||
</div>
|
||||
|
||||
<div class="radio-inline">
|
||||
<input type="radio" name="query-selector" id="simple" value="simple">
|
||||
<label for="simple">simple</label>
|
||||
</div>
|
||||
<div class="radio-inline">
|
||||
<input type="radio" name="query-selector" id="structured" value="structured">
|
||||
<label for="structured">structured</label>
|
||||
</div>
|
||||
|
||||
<form role="search" accept-charset="UTF-8" action="<?php echo CONST_Website_BaseURL; ?>search.php">
|
||||
<div class="form-group-simple">
|
||||
<input id="q" name="q" type="text" class="form-control input-sm" placeholder="Search" value="<?php echo htmlspecialchars($aMoreParams['q'] ?? ''); ?>" >
|
||||
</div>
|
||||
<div class="form-group-structured">
|
||||
<div class="form-inline">
|
||||
<input id="street" name="street" type="text" class="form-control input-sm" placeholder="House number/Street" value="<?php echo htmlspecialchars($aMoreParams['street'] ?? ''); ?>" >
|
||||
<input id="city" name="city" type="text" class="form-control input-sm" placeholder="City" value="<?php echo htmlspecialchars($aMoreParams['city'] ?? ''); ?>" >
|
||||
<input id="county" name="county" type="text" class="form-control input-sm" placeholder="County" value="<?php echo htmlspecialchars($aMoreParams['county'] ?? ''); ?>" >
|
||||
<input id="state" name="state" type="text" class="form-control input-sm" placeholder="State" value="<?php echo htmlspecialchars($aMoreParams['state'] ?? ''); ?>" >
|
||||
<input id="country" name="country" type="text" class="form-control input-sm" placeholder="Country" value="<?php echo htmlspecialchars($aMoreParams['country'] ?? ''); ?>" >
|
||||
<input id="postalcode" name="postalcode" type="text" class="form-control input-sm" placeholder="Postal Code" value="<?php echo htmlspecialchars($aMoreParams['postalcode'] ?? ''); ?>" >
|
||||
</div></div>
|
||||
<div class="form-group search-button-group">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Search</button>
|
||||
<?php if (CONST_Search_AreaPolygons) { ?>
|
||||
<input type="hidden" value="1" name="polygon_geojson" />
|
||||
<?php } ?>
|
||||
<input type="hidden" name="viewbox" value="<?php if (isset($aMoreParams['viewbox'])) echo ($aMoreParams['viewbox']); ?>" />
|
||||
<input type="hidden" name="viewbox" value="<?php echo htmlspecialchars($aMoreParams['viewbox'] ?? ''); ?>" />
|
||||
<div class="checkbox-inline">
|
||||
<input type="checkbox" id="use_viewbox" <?php if (isset($aMoreParams['viewbox'])) echo "checked='checked'"; ?>>
|
||||
<input type="checkbox" id="use_viewbox" <?php if (!empty($aMoreParams['viewbox'])) echo "checked='checked'"; ?>>
|
||||
<label for="use_viewbox">apply viewbox</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-type-link">
|
||||
<a id="switch-to-reverse" href="<?php echo CONST_Website_BaseURL; ?>reverse.php?format=html">reverse search</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
|
||||
@@ -53,7 +73,7 @@
|
||||
echo ' <span class="type">('.ucwords(str_replace('_',' ',$aResult['class'])).')</span>';
|
||||
else
|
||||
echo ' <span class="type">('.ucwords(str_replace('_',' ',$aResult['type'])).')</span>';
|
||||
echo ' <a class="btn btn-default btn-xs details" href="details.php?place_id='.$aResult['place_id'].'">details</a>';
|
||||
echo detailsPermaLink($aResult, 'details', 'class="btn btn-default btn-xs details"');
|
||||
echo '</div>';
|
||||
$i = $i+1;
|
||||
}
|
||||
@@ -89,10 +109,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
<?php
|
||||
|
||||
@@ -105,7 +121,16 @@
|
||||
);
|
||||
echo 'var nominatim_map_init = ' . json_encode($aNominatimMapInit, JSON_PRETTY_PRINT) . ';';
|
||||
|
||||
echo 'var nominatim_results = ' . json_encode($aSearchResults, JSON_PRETTY_PRINT) . ';';
|
||||
echo 'var nominatim_results = ' . json_encode($aSearchResults, JSON_PRETTY_PRINT) . ';';
|
||||
$sStructuredQuery = (empty($aMoreParams['q'])
|
||||
&& !(empty($aMoreParams['street'])
|
||||
&& empty($aMoreParams['city'])
|
||||
&& empty($aMoreParams['county'])
|
||||
&& empty($aMoreParams['state'])
|
||||
&& empty($aMoreParams['country'])
|
||||
&& empty($aMoreParams['postalcode'])))
|
||||
? 'true' : 'false';
|
||||
echo 'var nominatim_structured_query = '.$sStructuredQuery.';';
|
||||
?>
|
||||
</script>
|
||||
<?php include(CONST_BasePath.'/lib/template/includes/html-footer.php'); ?>
|
||||
|
||||
@@ -15,10 +15,6 @@ foreach ($aSearchResults as $iResNum => $aPointDetails) {
|
||||
|
||||
if (isset($aPointDetails['aBoundingBox'])) {
|
||||
$aPlace['boundingbox'] = $aPointDetails['aBoundingBox'];
|
||||
|
||||
if (isset($aPointDetails['aPolyPoints'])) {
|
||||
$aPlace['polygonpoints'] = $aPointDetails['aPolyPoints'];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($aPointDetails['zoom'])) {
|
||||
|
||||
@@ -11,7 +11,6 @@ echo " timestamp='".date(DATE_RFC822)."'";
|
||||
echo " attribution='Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright'";
|
||||
echo " querystring='".htmlspecialchars($sQuery, ENT_QUOTES)."'";
|
||||
if (isset($aMoreParams['viewbox'])) echo " viewbox='".htmlspecialchars($aMoreParams['viewbox'], ENT_QUOTES)."'";
|
||||
echo " polygon='".(isset($aMoreParams['polygon'])?'true':'false')."'";
|
||||
if (isset($aMoreParams['exclude_place_ids'])) {
|
||||
echo " exclude_place_ids='".htmlspecialchars($aMoreParams['exclude_place_ids'])."'";
|
||||
}
|
||||
@@ -31,12 +30,6 @@ foreach ($aSearchResults as $iResNum => $aResult) {
|
||||
echo ' boundingbox="';
|
||||
echo join(',', $aResult['aBoundingBox']);
|
||||
echo '"';
|
||||
|
||||
if (isset($aResult['aPolyPoints'])) {
|
||||
echo ' polygonpoints=\'';
|
||||
echo json_encode($aResult['aPolyPoints']);
|
||||
echo '\'';
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($aResult['asgeojson'])) {
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
# just use the pgxs makefile
|
||||
find_program(PG_CONFIG pg_config)
|
||||
execute_process(COMMAND ${PG_CONFIG} --pgxs
|
||||
OUTPUT_VARIABLE PGXS
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
if (NOT EXISTS "${PGXS}")
|
||||
message(FATAL_ERROR "Postgresql server package not found.")
|
||||
endif()
|
||||
|
||||
ADD_CUSTOM_COMMAND( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/dummy
|
||||
COMMAND PGXS=${PGXS} PG_CONFIG=${PG_CONFIG} MODSRCDIR=${CMAKE_CURRENT_SOURCE_DIR} $(MAKE) -f ${CMAKE_CURRENT_SOURCE_DIR}/Makefile
|
||||
COMMENT "Running external makefile ${PGXS}"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,12 +0,0 @@
|
||||
add_executable(nominatim export.c geometry.cpp import.c index.c input.c nominatim.c postgresql.c sprompt.c)
|
||||
|
||||
CHECK_SYMBOL_EXISTS(bswap_32 "byteswap.h" HAVE_BYTESWAP)
|
||||
CHECK_SYMBOL_EXISTS(bswap32 "sys/endian.h" HAVE_SYS_ENDIAN)
|
||||
|
||||
target_compile_definitions(nominatim
|
||||
PRIVATE HAVE_BYTESWAP=$<BOOL:${HAVE_BYTESWAP}>
|
||||
PRIVATE HAVE_SYS_ENDIAN=$<BOOL:${HAVE_SYS_ENDIAN}>
|
||||
)
|
||||
|
||||
target_link_libraries(nominatim ${LIBXML2_LIBRARIES} ${ZLIB_LIBRARIES} ${BZIP2_LIBRARIES} ${PostgreSQL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
@@ -1,558 +0,0 @@
|
||||
/*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <libpq-fe.h>
|
||||
|
||||
#include "nominatim.h"
|
||||
#include "export.h"
|
||||
#include "postgresql.h"
|
||||
|
||||
extern int verbose;
|
||||
|
||||
int mode = 0;
|
||||
|
||||
void nominatim_export(int rank_min, int rank_max, const char *conninfo, const char *structuredoutputfile)
|
||||
{
|
||||
xmlTextWriterPtr writer;
|
||||
|
||||
int rankTotalDone;
|
||||
|
||||
PGconn *conn;
|
||||
PGresult * res;
|
||||
PGresult * resSectors;
|
||||
PGresult * resPlaces;
|
||||
|
||||
int rank;
|
||||
int i;
|
||||
int iSector;
|
||||
int tuples;
|
||||
|
||||
const char *paramValues[2];
|
||||
int paramLengths[2];
|
||||
int paramFormats[2];
|
||||
uint32_t paramRank;
|
||||
uint32_t paramSector;
|
||||
uint32_t sector;
|
||||
|
||||
Oid pg_prepare_params[2];
|
||||
|
||||
conn = PQconnectdb(conninfo);
|
||||
if (PQstatus(conn) != CONNECTION_OK)
|
||||
{
|
||||
fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
pg_prepare_params[0] = PG_OID_INT4;
|
||||
res = PQprepare(conn, "index_sectors",
|
||||
"select geometry_sector,count(*) from placex where rank_search = $1 and indexed_status = 0 group by geometry_sector order by geometry_sector",
|
||||
1, pg_prepare_params);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK) exit(EXIT_FAILURE);
|
||||
PQclear(res);
|
||||
|
||||
pg_prepare_params[0] = PG_OID_INT4;
|
||||
pg_prepare_params[1] = PG_OID_INT4;
|
||||
res = PQprepare(conn, "index_sector_places",
|
||||
"select place_id from placex where rank_search = $1 and geometry_sector = $2",
|
||||
2, pg_prepare_params);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK) exit(EXIT_FAILURE);
|
||||
PQclear(res);
|
||||
|
||||
nominatim_exportCreatePreparedQueries(conn);
|
||||
|
||||
// Create the output file
|
||||
writer = nominatim_exportXMLStart(structuredoutputfile);
|
||||
|
||||
for (rank = rank_min; rank <= rank_max; rank++)
|
||||
{
|
||||
printf("Starting rank %d\n", rank);
|
||||
|
||||
paramRank = PGint32(rank);
|
||||
paramValues[0] = (char *)¶mRank;
|
||||
paramLengths[0] = sizeof(paramRank);
|
||||
paramFormats[0] = 1;
|
||||
resSectors = PQexecPrepared(conn, "index_sectors", 1, paramValues, paramLengths, paramFormats, 1);
|
||||
if (PQresultStatus(resSectors) != PGRES_TUPLES_OK)
|
||||
{
|
||||
fprintf(stderr, "index_sectors: SELECT failed: %s", PQerrorMessage(conn));
|
||||
PQclear(resSectors);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (PQftype(resSectors, 0) != PG_OID_INT4)
|
||||
{
|
||||
fprintf(stderr, "Sector value has unexpected type\n");
|
||||
PQclear(resSectors);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (PQftype(resSectors, 1) != PG_OID_INT8)
|
||||
{
|
||||
fprintf(stderr, "Sector value has unexpected type\n");
|
||||
PQclear(resSectors);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
rankTotalDone = 0;
|
||||
for (iSector = 0; iSector < PQntuples(resSectors); iSector++)
|
||||
{
|
||||
sector = PGint32(*((uint32_t *)PQgetvalue(resSectors, iSector, 0)));
|
||||
|
||||
// Get all the place_id's for this sector
|
||||
paramRank = PGint32(rank);
|
||||
paramValues[0] = (char *)¶mRank;
|
||||
paramLengths[0] = sizeof(paramRank);
|
||||
paramFormats[0] = 1;
|
||||
paramSector = PGint32(sector);
|
||||
paramValues[1] = (char *)¶mSector;
|
||||
paramLengths[1] = sizeof(paramSector);
|
||||
paramFormats[1] = 1;
|
||||
resPlaces = PQexecPrepared(conn, "index_sector_places", 2, paramValues, paramLengths, paramFormats, 1);
|
||||
if (PQresultStatus(resPlaces) != PGRES_TUPLES_OK)
|
||||
{
|
||||
fprintf(stderr, "index_sector_places: SELECT failed: %s", PQerrorMessage(conn));
|
||||
PQclear(resPlaces);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (PQftype(resPlaces, 0) != PG_OID_INT8)
|
||||
{
|
||||
fprintf(stderr, "Place_id value has unexpected type\n");
|
||||
PQclear(resPlaces);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
tuples = PQntuples(resPlaces);
|
||||
for (i = 0; i < tuples; i++)
|
||||
{
|
||||
nominatim_exportPlace(PGint64(*((uint64_t *)PQgetvalue(resPlaces, i, 0))), conn, writer, NULL, NULL);
|
||||
rankTotalDone++;
|
||||
if (rankTotalDone%1000 == 0) printf("Done %i (k)\n", rankTotalDone/1000);
|
||||
}
|
||||
PQclear(resPlaces);
|
||||
}
|
||||
PQclear(resSectors);
|
||||
}
|
||||
|
||||
nominatim_exportXMLEnd(writer);
|
||||
|
||||
PQfinish(conn);
|
||||
}
|
||||
|
||||
void nominatim_exportCreatePreparedQueries(PGconn * conn)
|
||||
{
|
||||
Oid pg_prepare_params[2];
|
||||
PGresult * res;
|
||||
|
||||
pg_prepare_params[0] = PG_OID_INT8;
|
||||
res = PQprepare(conn, "placex_details",
|
||||
"select placex.osm_type, placex.osm_id, placex.class, placex.type, placex.name, placex.housenumber, placex.country_code, ST_AsText(placex.geometry), placex.admin_level, placex.rank_address, placex.rank_search, placex.parent_place_id, parent.osm_type, parent.osm_id, placex.indexed_status, placex.linked_place_id from placex left outer join placex as parent on (placex.parent_place_id = parent.place_id) where placex.place_id = $1",
|
||||
1, pg_prepare_params);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Error preparing placex_details: %s", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
pg_prepare_params[0] = PG_OID_INT8;
|
||||
res = PQprepare(conn, "placex_address",
|
||||
"select osm_type,osm_id,class,type,distance,cached_rank_address,isaddress from place_addressline join placex on (address_place_id = placex.place_id) where place_addressline.place_id = $1 and address_place_id != place_addressline.place_id order by cached_rank_address asc,osm_type,osm_id",
|
||||
1, pg_prepare_params);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Error preparing placex_address: %s", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
pg_prepare_params[0] = PG_OID_INT8;
|
||||
res = PQprepare(conn, "placex_names",
|
||||
"select (each(name)).key,(each(name)).value from (select name from placex where place_id = $1) as x order by (each(name)).key",
|
||||
1, pg_prepare_params);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Error preparing placex_names: %s", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
pg_prepare_params[0] = PG_OID_INT8;
|
||||
res = PQprepare(conn, "placex_extratags",
|
||||
"select (each(extratags)).key,(each(extratags)).value from (select extratags from placex where place_id = $1) as x order by (each(extratags)).key",
|
||||
1, pg_prepare_params);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Error preparing placex_extratags: %s", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
}
|
||||
|
||||
xmlTextWriterPtr nominatim_exportXMLStart(const char *structuredoutputfile)
|
||||
{
|
||||
xmlTextWriterPtr writer;
|
||||
|
||||
writer = xmlNewTextWriterFilename(structuredoutputfile, 0);
|
||||
if (writer==NULL)
|
||||
{
|
||||
fprintf(stderr, "Unable to open %s\n", structuredoutputfile);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
xmlTextWriterSetIndent(writer, 1);
|
||||
if (xmlTextWriterStartDocument(writer, NULL, "UTF8", NULL) < 0)
|
||||
{
|
||||
fprintf(stderr, "xmlTextWriterStartDocument failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (xmlTextWriterStartElement(writer, BAD_CAST "osmStructured") < 0)
|
||||
{
|
||||
fprintf(stderr, "xmlTextWriterStartElement failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (xmlTextWriterWriteAttribute(writer, BAD_CAST "version", BAD_CAST "0.1") < 0)
|
||||
{
|
||||
fprintf(stderr, "xmlTextWriterWriteAttribute failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (xmlTextWriterWriteAttribute(writer, BAD_CAST "generator", BAD_CAST "Nominatim") < 0)
|
||||
{
|
||||
fprintf(stderr, "xmlTextWriterWriteAttribute failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
mode = 0;
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
void nominatim_exportXMLEnd(xmlTextWriterPtr writer)
|
||||
{
|
||||
nominatim_exportEndMode(writer);
|
||||
|
||||
// End <osmStructured>
|
||||
if (xmlTextWriterEndElement(writer) < 0)
|
||||
{
|
||||
fprintf(stderr, "xmlTextWriterEndElement failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (xmlTextWriterEndDocument(writer) < 0)
|
||||
{
|
||||
fprintf(stderr, "xmlTextWriterEndDocument failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
xmlFreeTextWriter(writer);
|
||||
}
|
||||
|
||||
void nominatim_exportStartMode(xmlTextWriterPtr writer, int newMode)
|
||||
{
|
||||
if (mode == newMode) return;
|
||||
|
||||
nominatim_exportEndMode(writer);
|
||||
|
||||
switch(newMode)
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (xmlTextWriterStartElement(writer, BAD_CAST "add") < 0)
|
||||
{
|
||||
fprintf(stderr, "xmlTextWriterStartElement failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (xmlTextWriterStartElement(writer, BAD_CAST "update") < 0)
|
||||
{
|
||||
fprintf(stderr, "xmlTextWriterStartElement failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if (xmlTextWriterStartElement(writer, BAD_CAST "delete") < 0)
|
||||
{
|
||||
fprintf(stderr, "xmlTextWriterStartElement failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
mode = newMode;
|
||||
}
|
||||
|
||||
void nominatim_exportEndMode(xmlTextWriterPtr writer)
|
||||
{
|
||||
if (!mode) return;
|
||||
|
||||
if (xmlTextWriterEndElement(writer) < 0)
|
||||
{
|
||||
fprintf(stderr, "xmlTextWriterEndElement failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
void nominatim_exportPlaceQueries(uint64_t place_id, PGconn * conn, struct export_data * querySet)
|
||||
{
|
||||
const char * paramValues[1];
|
||||
int paramLengths[1];
|
||||
int paramFormats[1];
|
||||
uint64_t paramPlaceID;
|
||||
|
||||
paramPlaceID = PGint64(place_id);
|
||||
paramValues[0] = (char *)¶mPlaceID;
|
||||
paramLengths[0] = sizeof(paramPlaceID);
|
||||
paramFormats[0] = 1;
|
||||
|
||||
querySet->res = PQexecPrepared(conn, "placex_details", 1, paramValues, paramLengths, paramFormats, 0);
|
||||
if (PQresultStatus(querySet->res) != PGRES_TUPLES_OK)
|
||||
{
|
||||
fprintf(stderr, "placex_details: SELECT failed: %s", PQerrorMessage(conn));
|
||||
PQclear(querySet->res);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
querySet->resNames = PQexecPrepared(conn, "placex_names", 1, paramValues, paramLengths, paramFormats, 0);
|
||||
if (PQresultStatus(querySet->resNames) != PGRES_TUPLES_OK)
|
||||
{
|
||||
fprintf(stderr, "placex_names: SELECT failed: %s", PQerrorMessage(conn));
|
||||
PQclear(querySet->resNames);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
querySet->resAddress = PQexecPrepared(conn, "placex_address", 1, paramValues, paramLengths, paramFormats, 0);
|
||||
if (PQresultStatus(querySet->resAddress) != PGRES_TUPLES_OK)
|
||||
{
|
||||
fprintf(stderr, "placex_address: SELECT failed: %s", PQerrorMessage(conn));
|
||||
PQclear(querySet->resAddress);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
querySet->resExtraTags = PQexecPrepared(conn, "placex_extratags", 1, paramValues, paramLengths, paramFormats, 0);
|
||||
if (PQresultStatus(querySet->resExtraTags) != PGRES_TUPLES_OK)
|
||||
{
|
||||
fprintf(stderr, "placex_extratags: SELECT failed: %s", PQerrorMessage(conn));
|
||||
PQclear(querySet->resExtraTags);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
void nominatim_exportFreeQueries(struct export_data * querySet)
|
||||
{
|
||||
PQclear(querySet->res);
|
||||
PQclear(querySet->resNames);
|
||||
PQclear(querySet->resAddress);
|
||||
PQclear(querySet->resExtraTags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Requirements: the prepared queries must exist
|
||||
*/
|
||||
void nominatim_exportPlace(uint64_t place_id, PGconn * conn,
|
||||
xmlTextWriterPtr writer, pthread_mutex_t * writer_mutex, struct export_data * prevQuerySet)
|
||||
{
|
||||
struct export_data querySet;
|
||||
|
||||
int i;
|
||||
|
||||
nominatim_exportPlaceQueries(place_id, conn, &querySet);
|
||||
|
||||
// Add, modify or delete?
|
||||
if (prevQuerySet)
|
||||
{
|
||||
if ((PQgetvalue(prevQuerySet->res, 0, 14) && strcmp(PQgetvalue(prevQuerySet->res, 0, 14), "100") == 0) || PQntuples(querySet.res) == 0)
|
||||
{
|
||||
// Delete
|
||||
if (writer_mutex) pthread_mutex_lock( writer_mutex );
|
||||
nominatim_exportStartMode(writer, 3);
|
||||
xmlTextWriterStartElement(writer, BAD_CAST "feature");
|
||||
xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "place_id", "%li", place_id);
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "type", BAD_CAST PQgetvalue(prevQuerySet->res, 0, 0));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "id", BAD_CAST PQgetvalue(prevQuerySet->res, 0, 1));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "key", BAD_CAST PQgetvalue(prevQuerySet->res, 0, 2));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "value", BAD_CAST PQgetvalue(prevQuerySet->res, 0, 3));
|
||||
xmlTextWriterEndElement(writer);
|
||||
if (writer_mutex) pthread_mutex_unlock( writer_mutex );
|
||||
nominatim_exportFreeQueries(&querySet);
|
||||
return;
|
||||
}
|
||||
if (PQgetvalue(prevQuerySet->res, 0, 14) && strcmp(PQgetvalue(prevQuerySet->res, 0, 14), "1") == 0)
|
||||
{
|
||||
// Add
|
||||
if (writer_mutex) pthread_mutex_lock( writer_mutex );
|
||||
nominatim_exportStartMode(writer, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update, but only if something has changed
|
||||
|
||||
// TODO: detect changes
|
||||
|
||||
if (writer_mutex) pthread_mutex_lock( writer_mutex );
|
||||
nominatim_exportStartMode(writer, 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add
|
||||
if (writer_mutex) pthread_mutex_lock( writer_mutex );
|
||||
nominatim_exportStartMode(writer, 1);
|
||||
}
|
||||
|
||||
xmlTextWriterStartElement(writer, BAD_CAST "feature");
|
||||
xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "place_id", "%li", place_id);
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "type", BAD_CAST PQgetvalue(querySet.res, 0, 0));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "id", BAD_CAST PQgetvalue(querySet.res, 0, 1));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "key", BAD_CAST PQgetvalue(querySet.res, 0, 2));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "value", BAD_CAST PQgetvalue(querySet.res, 0, 3));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "rank", BAD_CAST PQgetvalue(querySet.res, 0, 9));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "importance", BAD_CAST PQgetvalue(querySet.res, 0, 10));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "parent_place_id", BAD_CAST PQgetvalue(querySet.res, 0, 11));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "parent_type", BAD_CAST PQgetvalue(querySet.res, 0, 12));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "parent_id", BAD_CAST PQgetvalue(querySet.res, 0, 13));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "linked_place_id", BAD_CAST PQgetvalue(querySet.res, 0, 15));
|
||||
|
||||
if (PQntuples(querySet.resNames))
|
||||
{
|
||||
xmlTextWriterStartElement(writer, BAD_CAST "names");
|
||||
|
||||
for (i = 0; i < PQntuples(querySet.resNames); i++)
|
||||
{
|
||||
xmlTextWriterStartElement(writer, BAD_CAST "name");
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "type", BAD_CAST PQgetvalue(querySet.resNames, i, 0));
|
||||
xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.resNames, i, 1));
|
||||
xmlTextWriterEndElement(writer);
|
||||
}
|
||||
|
||||
xmlTextWriterEndElement(writer);
|
||||
}
|
||||
|
||||
if (PQgetvalue(querySet.res, 0, 5) && strlen(PQgetvalue(querySet.res, 0, 5)))
|
||||
{
|
||||
xmlTextWriterStartElement(writer, BAD_CAST "houseNumber");
|
||||
xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.res, 0, 5));
|
||||
xmlTextWriterEndElement(writer);
|
||||
}
|
||||
|
||||
if (PQgetvalue(querySet.res, 0, 8) && strlen(PQgetvalue(querySet.res, 0, 8)))
|
||||
{
|
||||
xmlTextWriterStartElement(writer, BAD_CAST "adminLevel");
|
||||
xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.res, 0, 8));
|
||||
xmlTextWriterEndElement(writer);
|
||||
}
|
||||
|
||||
if (PQgetvalue(querySet.res, 0, 6) && strlen(PQgetvalue(querySet.res, 0, 6)))
|
||||
{
|
||||
xmlTextWriterStartElement(writer, BAD_CAST "countryCode");
|
||||
xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.res, 0, 6));
|
||||
xmlTextWriterEndElement(writer);
|
||||
}
|
||||
|
||||
if (PQntuples(querySet.resAddress) > 0)
|
||||
{
|
||||
xmlTextWriterStartElement(writer, BAD_CAST "address");
|
||||
for (i = 0; i < PQntuples(querySet.resAddress); i++)
|
||||
{
|
||||
xmlTextWriterStartElement(writer, BAD_CAST getRankLabel(atoi(PQgetvalue(querySet.resAddress, i, 5))));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "rank", BAD_CAST PQgetvalue(querySet.resAddress, i, 5));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "type", BAD_CAST PQgetvalue(querySet.resAddress, i, 0));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "id", BAD_CAST PQgetvalue(querySet.resAddress, i, 1));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "key", BAD_CAST PQgetvalue(querySet.resAddress, i, 2));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "value", BAD_CAST PQgetvalue(querySet.resAddress, i, 3));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "distance", BAD_CAST PQgetvalue(querySet.resAddress, i, 4));
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "isaddress", BAD_CAST PQgetvalue(querySet.resAddress, i, 6));
|
||||
xmlTextWriterEndElement(writer);
|
||||
}
|
||||
xmlTextWriterEndElement(writer);
|
||||
}
|
||||
|
||||
if (PQntuples(querySet.resExtraTags))
|
||||
{
|
||||
xmlTextWriterStartElement(writer, BAD_CAST "tags");
|
||||
|
||||
for (i = 0; i < PQntuples(querySet.resExtraTags); i++)
|
||||
{
|
||||
xmlTextWriterStartElement(writer, BAD_CAST "tag");
|
||||
xmlTextWriterWriteAttribute(writer, BAD_CAST "type", BAD_CAST PQgetvalue(querySet.resExtraTags, i, 0));
|
||||
xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.resExtraTags, i, 1));
|
||||
xmlTextWriterEndElement(writer);
|
||||
}
|
||||
|
||||
xmlTextWriterEndElement(writer);
|
||||
}
|
||||
|
||||
|
||||
xmlTextWriterStartElement(writer, BAD_CAST "osmGeometry");
|
||||
xmlTextWriterWriteString(writer, BAD_CAST PQgetvalue(querySet.res, 0, 7));
|
||||
xmlTextWriterEndElement(writer);
|
||||
|
||||
xmlTextWriterEndElement(writer); // </feature>
|
||||
|
||||
if (writer_mutex) pthread_mutex_unlock( writer_mutex );
|
||||
|
||||
nominatim_exportFreeQueries(&querySet);
|
||||
}
|
||||
|
||||
const char * getRankLabel(int rank)
|
||||
{
|
||||
switch (rank)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
return "continent";
|
||||
case 2:
|
||||
case 3:
|
||||
return "sea";
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
return "country";
|
||||
case 8:
|
||||
case 9:
|
||||
case 10:
|
||||
case 11:
|
||||
return "state";
|
||||
case 12:
|
||||
case 13:
|
||||
case 14:
|
||||
case 15:
|
||||
return "county";
|
||||
case 16:
|
||||
return "city";
|
||||
case 17:
|
||||
return "town";
|
||||
case 18:
|
||||
return "village";
|
||||
case 19:
|
||||
return "unknown";
|
||||
case 20:
|
||||
return "suburb";
|
||||
case 21:
|
||||
return "postcode";
|
||||
case 22:
|
||||
return "neighborhood";
|
||||
case 23:
|
||||
return "postcode";
|
||||
case 24:
|
||||
return "unknown";
|
||||
case 25:
|
||||
return "postcode";
|
||||
case 26:
|
||||
return "street";
|
||||
case 27:
|
||||
return "access";
|
||||
case 28:
|
||||
return "building";
|
||||
case 29:
|
||||
default:
|
||||
return "other";
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#ifndef EXPORT_H
|
||||
#define EXPORT_H
|
||||
|
||||
#include <libxml/encoding.h>
|
||||
#include <libxml/xmlwriter.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct export_data
|
||||
{
|
||||
PGresult * res;
|
||||
PGresult * resNames;
|
||||
PGresult * resAddress;
|
||||
PGresult * resExtraTags;
|
||||
};
|
||||
|
||||
void nominatim_export(int rank_min, int rank_max, const char *conninfo, const char *structuredoutputfile);
|
||||
void nominatim_exportCreatePreparedQueries(PGconn * conn);
|
||||
|
||||
xmlTextWriterPtr nominatim_exportXMLStart(const char *structuredoutputfile);
|
||||
void nominatim_exportXMLEnd(xmlTextWriterPtr writer);
|
||||
|
||||
void nominatim_exportEndMode(xmlTextWriterPtr writer);
|
||||
|
||||
void nominatim_exportPlaceQueries(uint64_t place_id, PGconn * conn, struct export_data * querySet);
|
||||
void nominatim_exportFreeQueries(struct export_data * querySet);
|
||||
|
||||
void nominatim_exportPlace(uint64_t place_id, PGconn * conn,
|
||||
xmlTextWriterPtr writer, pthread_mutex_t * writer_mutex, struct export_data * prevQuerySet);
|
||||
const char * getRankLabel(int rank);
|
||||
|
||||
#endif
|
||||
@@ -1,856 +0,0 @@
|
||||
/*
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <libpq-fe.h>
|
||||
|
||||
#include <libxml/xmlstring.h>
|
||||
#include <libxml/xmlreader.h>
|
||||
#include <libxml/hash.h>
|
||||
|
||||
#include "nominatim.h"
|
||||
#include "import.h"
|
||||
#include "input.h"
|
||||
|
||||
typedef enum { FILETYPE_NONE, FILETYPE_STRUCTUREDV0P1 } filetypes_t;
|
||||
typedef enum { FILEMODE_NONE, FILEMODE_ADD, FILEMODE_UPDATE, FILEMODE_DELETE } filemodes_t;
|
||||
|
||||
#define MAX_FEATUREADDRESS 5000
|
||||
#define MAX_FEATURENAMES 10000
|
||||
#define MAX_FEATUREEXTRATAGS 10000
|
||||
#define MAX_FEATURENAMESTRING 1000000
|
||||
#define MAX_FEATUREEXTRATAGSTRING 500000
|
||||
|
||||
struct feature_address
|
||||
{
|
||||
int place_id;
|
||||
int rankAddress;
|
||||
char isAddress[2];
|
||||
xmlChar * type;
|
||||
xmlChar * id;
|
||||
xmlChar * key;
|
||||
xmlChar * value;
|
||||
xmlChar * distance;
|
||||
};
|
||||
|
||||
struct feature_tag
|
||||
{
|
||||
xmlChar * type;
|
||||
xmlChar * value;
|
||||
};
|
||||
|
||||
struct feature
|
||||
{
|
||||
xmlChar * placeID;
|
||||
xmlChar * type;
|
||||
xmlChar * id;
|
||||
xmlChar * key;
|
||||
xmlChar * value;
|
||||
xmlChar * rankAddress;
|
||||
xmlChar * rankSearch;
|
||||
xmlChar * countryCode;
|
||||
xmlChar * parentPlaceID;
|
||||
xmlChar * parentType;
|
||||
xmlChar * parentID;
|
||||
xmlChar * adminLevel;
|
||||
xmlChar * houseNumber;
|
||||
xmlChar * geometry;
|
||||
} feature;
|
||||
|
||||
int fileType = FILETYPE_NONE;
|
||||
int fileMode = FILEMODE_ADD;
|
||||
PGconn * conn;
|
||||
struct feature_address featureAddress[MAX_FEATUREADDRESS];
|
||||
struct feature_tag featureName[MAX_FEATURENAMES];
|
||||
struct feature_tag featureExtraTag[MAX_FEATUREEXTRATAGS];
|
||||
struct feature feature;
|
||||
int featureAddressLines = 0;
|
||||
int featureNameLines = 0;
|
||||
int featureExtraTagLines = 0;
|
||||
int featureCount = 0;
|
||||
xmlHashTablePtr partionTableTagsHash;
|
||||
xmlHashTablePtr partionTableTagsHashDelete;
|
||||
char featureNameString[MAX_FEATURENAMESTRING];
|
||||
char featureExtraTagString[MAX_FEATUREEXTRATAGSTRING];
|
||||
|
||||
extern int verbose;
|
||||
|
||||
void StartElement(xmlTextReaderPtr reader, const xmlChar *name)
|
||||
{
|
||||
char * value;
|
||||
float version;
|
||||
int isAddressLine;
|
||||
|
||||
if (fileType == FILETYPE_NONE)
|
||||
{
|
||||
// Potential to handle other file types in the future / versions
|
||||
if (xmlStrEqual(name, BAD_CAST "osmStructured"))
|
||||
{
|
||||
value = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "version");
|
||||
version = strtof(value, NULL);
|
||||
xmlFree(value);
|
||||
|
||||
if (version == (float)0.1)
|
||||
{
|
||||
fileType = FILETYPE_STRUCTUREDV0P1;
|
||||
fileMode = FILEMODE_ADD;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf( stderr, "Unknown osmStructured version %f (%s)\n", version, value );
|
||||
exit_nicely();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf( stderr, "Unknown XML document type: %s\n", name );
|
||||
exit_nicely();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (xmlStrEqual(name, BAD_CAST "add"))
|
||||
{
|
||||
fileMode = FILEMODE_ADD;
|
||||
return;
|
||||
}
|
||||
if (xmlStrEqual(name, BAD_CAST "update"))
|
||||
{
|
||||
fileMode = FILEMODE_UPDATE;
|
||||
return;
|
||||
}
|
||||
if (xmlStrEqual(name, BAD_CAST "delete"))
|
||||
{
|
||||
fileMode = FILEMODE_DELETE;
|
||||
return;
|
||||
}
|
||||
if (fileMode == FILEMODE_NONE)
|
||||
{
|
||||
fprintf( stderr, "Unknown import mode in: %s\n", name );
|
||||
exit_nicely();
|
||||
}
|
||||
|
||||
if (xmlStrEqual(name, BAD_CAST "feature"))
|
||||
{
|
||||
feature.placeID = xmlTextReaderGetAttribute(reader, BAD_CAST "place_id");
|
||||
feature.type = xmlTextReaderGetAttribute(reader, BAD_CAST "type");
|
||||
feature.id = xmlTextReaderGetAttribute(reader, BAD_CAST "id");
|
||||
feature.key = xmlTextReaderGetAttribute(reader, BAD_CAST "key");
|
||||
feature.value = xmlTextReaderGetAttribute(reader, BAD_CAST "value");
|
||||
feature.rankAddress = xmlTextReaderGetAttribute(reader, BAD_CAST "rank");
|
||||
feature.rankSearch = xmlTextReaderGetAttribute(reader, BAD_CAST "importance");
|
||||
|
||||
feature.parentPlaceID = xmlTextReaderGetAttribute(reader, BAD_CAST "parent_place_id");
|
||||
/*
|
||||
if (strlen(feature.parentPlaceID) == 0)
|
||||
{
|
||||
xmlFree(feature.parentPlaceID);
|
||||
feature.parentPlaceID = NULL;
|
||||
}
|
||||
*/
|
||||
feature.parentType = xmlTextReaderGetAttribute(reader, BAD_CAST "parent_type");
|
||||
feature.parentID = xmlTextReaderGetAttribute(reader, BAD_CAST "parent_id");
|
||||
|
||||
feature.countryCode = NULL;
|
||||
feature.adminLevel = NULL;
|
||||
feature.houseNumber = NULL;
|
||||
feature.geometry = NULL;
|
||||
featureAddressLines = 0;
|
||||
featureNameLines = 0;
|
||||
featureExtraTagLines = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
if (xmlStrEqual(name, BAD_CAST "names")) return;
|
||||
if (xmlStrEqual(name, BAD_CAST "name"))
|
||||
{
|
||||
if (featureNameLines < MAX_FEATURENAMES)
|
||||
{
|
||||
featureName[featureNameLines].type = xmlTextReaderGetAttribute(reader, BAD_CAST "type");
|
||||
featureName[featureNameLines].value = xmlTextReaderReadString(reader);
|
||||
featureNameLines++;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf( stderr, "Too many name elements (%s%s)\n", feature.type, feature.id);
|
||||
// exit_nicely();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (xmlStrEqual(name, BAD_CAST "tags")) return;
|
||||
if (xmlStrEqual(name, BAD_CAST "tag"))
|
||||
{
|
||||
if (featureExtraTagLines < MAX_FEATUREEXTRATAGS)
|
||||
{
|
||||
featureExtraTag[featureExtraTagLines].type = xmlTextReaderGetAttribute(reader, BAD_CAST "type");
|
||||
featureExtraTag[featureExtraTagLines].value = xmlTextReaderReadString(reader);
|
||||
featureExtraTagLines++;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf( stderr, "Too many extra tag elements (%s%s)\n", feature.type, feature.id);
|
||||
// exit_nicely();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (xmlStrEqual(name, BAD_CAST "osmGeometry"))
|
||||
{
|
||||
feature.geometry = xmlTextReaderReadString(reader);
|
||||
return;
|
||||
}
|
||||
if (xmlStrEqual(name, BAD_CAST "adminLevel"))
|
||||
{
|
||||
feature.adminLevel = xmlTextReaderReadString(reader);
|
||||
return;
|
||||
}
|
||||
if (xmlStrEqual(name, BAD_CAST "countryCode"))
|
||||
{
|
||||
feature.countryCode = xmlTextReaderReadString(reader);
|
||||
return;
|
||||
}
|
||||
if (xmlStrEqual(name, BAD_CAST "houseNumber"))
|
||||
{
|
||||
feature.houseNumber = xmlTextReaderReadString(reader);
|
||||
return;
|
||||
}
|
||||
if (xmlStrEqual(name, BAD_CAST "address"))
|
||||
{
|
||||
featureAddressLines = 0;
|
||||
return;
|
||||
}
|
||||
isAddressLine = 0;
|
||||
if (xmlStrEqual(name, BAD_CAST "continent"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "sea"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "country"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "state"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "county"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "city"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "town"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "village"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "unknown"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "suburb"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "postcode"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "neighborhood"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "street"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "access"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "building"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "other"))
|
||||
{
|
||||
isAddressLine = 1;
|
||||
}
|
||||
if (isAddressLine)
|
||||
{
|
||||
if (featureAddressLines < MAX_FEATUREADDRESS)
|
||||
{
|
||||
value = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "rank");
|
||||
if (!value)
|
||||
{
|
||||
fprintf( stderr, "Address element missing rank\n");
|
||||
exit_nicely();
|
||||
}
|
||||
featureAddress[featureAddressLines].rankAddress = atoi(value);
|
||||
xmlFree(value);
|
||||
|
||||
value = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "isaddress");
|
||||
if (!value)
|
||||
{
|
||||
fprintf( stderr, "Address element missing rank\n");
|
||||
exit_nicely();
|
||||
}
|
||||
if (*value == 't') strcpy(featureAddress[featureAddressLines].isAddress, "t");
|
||||
else strcpy(featureAddress[featureAddressLines].isAddress, "f");
|
||||
xmlFree(value);
|
||||
|
||||
featureAddress[featureAddressLines].type = xmlTextReaderGetAttribute(reader, BAD_CAST "type");
|
||||
featureAddress[featureAddressLines].id = xmlTextReaderGetAttribute(reader, BAD_CAST "id");
|
||||
featureAddress[featureAddressLines].key = xmlTextReaderGetAttribute(reader, BAD_CAST "key");
|
||||
featureAddress[featureAddressLines].value = xmlTextReaderGetAttribute(reader, BAD_CAST "value");
|
||||
featureAddress[featureAddressLines].distance = xmlTextReaderGetAttribute(reader, BAD_CAST "distance");
|
||||
|
||||
featureAddressLines++;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf( stderr, "Too many address elements (%s%s)\n", feature.type, feature.id);
|
||||
// exit_nicely();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
fprintf(stderr, "%s: Unknown element name: %s\n", __FUNCTION__, name);
|
||||
}
|
||||
|
||||
void EndElement(xmlTextReaderPtr reader, const xmlChar *name)
|
||||
{
|
||||
PGresult * res;
|
||||
const char * paramValues[14];
|
||||
char * place_id;
|
||||
char * partionQueryName;
|
||||
int i, namePos, lineTypeLen, lineValueLen;
|
||||
|
||||
if (xmlStrEqual(name, BAD_CAST "feature"))
|
||||
{
|
||||
featureCount++;
|
||||
if (featureCount % 1000 == 0) printf("feature %i(k)\n", featureCount/1000);
|
||||
/*
|
||||
if (fileMode == FILEMODE_ADD)
|
||||
{
|
||||
resPlaceID = PQexecPrepared(conn, "get_new_place_id", 0, NULL, NULL, NULL, 0);
|
||||
if (PQresultStatus(resPlaceID) != PGRES_TUPLES_OK)
|
||||
{
|
||||
fprintf(stderr, "get_place_id: INSERT failed: %s", PQerrorMessage(conn));
|
||||
PQclear(resPlaceID);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
paramValues[0] = (const char *)feature.type;
|
||||
paramValues[1] = (const char *)feature.id;
|
||||
paramValues[2] = (const char *)feature.key;
|
||||
paramValues[3] = (const char *)feature.value;
|
||||
resPlaceID = PQexecPrepared(conn, "get_new_place_id", 4, paramValues, NULL, NULL, 0);
|
||||
if (PQresultStatus(resPlaceID) != PGRES_TUPLES_OK)
|
||||
{
|
||||
fprintf(stderr, "index_placex: INSERT failed: %s", PQerrorMessage(conn));
|
||||
PQclear(resPlaceID);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
*/
|
||||
place_id = (char *)feature.placeID;
|
||||
|
||||
if (fileMode == FILEMODE_UPDATE || fileMode == FILEMODE_DELETE || fileMode == FILEMODE_ADD)
|
||||
{
|
||||
paramValues[0] = (const char *)place_id;
|
||||
if (verbose) fprintf(stderr, "placex_delete: %s\n", paramValues[0]);
|
||||
res = PQexecPrepared(conn, "placex_delete", 1, paramValues, NULL, NULL, 0);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "placex_delete: DELETE failed: %s", PQerrorMessage(conn));
|
||||
PQclear(res);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
if (verbose) fprintf(stderr, "search_name_delete: %s\n", paramValues[0]);
|
||||
res = PQexecPrepared(conn, "search_name_delete", 1, paramValues, NULL, NULL, 0);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "search_name_delete: DELETE failed: %s", PQerrorMessage(conn));
|
||||
PQclear(res);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
if (verbose) fprintf(stderr, "place_addressline_delete: %s\n", paramValues[0]);
|
||||
res = PQexecPrepared(conn, "place_addressline_delete", 1, paramValues, NULL, NULL, 0);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "place_addressline_delete: DELETE failed: %s", PQerrorMessage(conn));
|
||||
PQclear(res);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
partionQueryName = xmlHashLookup2(partionTableTagsHashDelete, feature.key, feature.value);
|
||||
if (partionQueryName)
|
||||
{
|
||||
res = PQexecPrepared(conn, partionQueryName, 1, paramValues, NULL, NULL, 0);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "%s: DELETE failed: %s", partionQueryName, PQerrorMessage(conn));
|
||||
PQclear(res);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
}
|
||||
}
|
||||
|
||||
if (fileMode == FILEMODE_UPDATE || fileMode == FILEMODE_ADD)
|
||||
{
|
||||
// Insert into placex
|
||||
paramValues[0] = (const char *)place_id;
|
||||
paramValues[1] = (const char *)feature.type;
|
||||
paramValues[2] = (const char *)feature.id;
|
||||
paramValues[3] = (const char *)feature.key;
|
||||
paramValues[4] = (const char *)feature.value;
|
||||
|
||||
featureNameString[0] = 0;
|
||||
if (featureNameLines)
|
||||
{
|
||||
namePos = 0;
|
||||
lineTypeLen = 0;
|
||||
lineValueLen = 0;
|
||||
for (i = 0; i < featureNameLines; i++)
|
||||
{
|
||||
lineTypeLen = (int)strlen((char *) featureName[i].type);
|
||||
lineValueLen = (int)strlen((char *) featureName[i].value);
|
||||
if (namePos+lineTypeLen+lineValueLen+7 > MAX_FEATURENAMESTRING)
|
||||
{
|
||||
fprintf(stderr, "feature name too long: %s", (const char *)featureName[i].value);
|
||||
break;
|
||||
}
|
||||
if (namePos) strcpy(featureNameString+(namePos++), ",");
|
||||
strcpy(featureNameString+(namePos++), "\"");
|
||||
strcpy(featureNameString+namePos, (char*) featureName[i].type);
|
||||
namePos += lineTypeLen;
|
||||
strcpy(featureNameString+namePos, "\"=>\"");
|
||||
namePos += 4;
|
||||
strcpy(featureNameString+namePos, (char *) featureName[i].value);
|
||||
namePos += lineValueLen;
|
||||
strcpy(featureNameString+(namePos++), "\"");
|
||||
|
||||
xmlFree(featureName[i].type);
|
||||
xmlFree(featureName[i].value);
|
||||
}
|
||||
}
|
||||
paramValues[5] = (const char *)featureNameString;
|
||||
|
||||
paramValues[6] = (const char *)feature.countryCode;
|
||||
|
||||
featureExtraTagString[0] = 0;
|
||||
if (featureExtraTagLines)
|
||||
{
|
||||
namePos = 0;
|
||||
lineTypeLen = 0;
|
||||
lineValueLen = 0;
|
||||
for (i = 0; i < featureExtraTagLines; i++)
|
||||
{
|
||||
lineTypeLen = strlen((char *) featureExtraTag[i].type);
|
||||
lineValueLen = strlen((char *) featureExtraTag[i].value);
|
||||
if (namePos+lineTypeLen+lineValueLen+7 > MAX_FEATUREEXTRATAGSTRING)
|
||||
{
|
||||
fprintf(stderr, "feature extra tag too long: %s", (const char *)featureExtraTag[i].value);
|
||||
break;
|
||||
}
|
||||
if (namePos) strcpy(featureExtraTagString+(namePos++),",");
|
||||
strcpy(featureExtraTagString+(namePos++), "\"");
|
||||
strcpy(featureExtraTagString+namePos, (char *) featureExtraTag[i].type);
|
||||
namePos += lineTypeLen;
|
||||
strcpy(featureExtraTagString+namePos, "\"=>\"");
|
||||
namePos += 4;
|
||||
strcpy(featureExtraTagString+namePos, (char *) featureExtraTag[i].value);
|
||||
namePos += lineValueLen;
|
||||
strcpy(featureExtraTagString+(namePos++), "\"");
|
||||
|
||||
xmlFree(featureExtraTag[i].type);
|
||||
xmlFree(featureExtraTag[i].value);
|
||||
}
|
||||
}
|
||||
paramValues[7] = (const char *)featureExtraTagString;
|
||||
|
||||
if (xmlStrlen(feature.parentPlaceID) == 0)
|
||||
paramValues[8] = "0";
|
||||
else
|
||||
paramValues[8] = (const char *)feature.parentPlaceID;
|
||||
|
||||
paramValues[9] = (const char *)feature.adminLevel;
|
||||
paramValues[10] = (const char *)feature.houseNumber;
|
||||
paramValues[11] = (const char *)feature.rankAddress;
|
||||
paramValues[12] = (const char *)feature.rankSearch;
|
||||
paramValues[13] = (const char *)feature.geometry;
|
||||
if (strlen(paramValues[3]) && strlen(paramValues[13]))
|
||||
{
|
||||
if (verbose) fprintf(stderr, "placex_insert: %s\n", paramValues[0]);
|
||||
res = PQexecPrepared(conn, "placex_insert", 14, paramValues, NULL, NULL, 0);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "index_placex: INSERT failed: %s", PQerrorMessage(conn));
|
||||
fprintf(stderr, "index_placex: INSERT failed: %s %s %s", paramValues[0], paramValues[1], paramValues[2]);
|
||||
PQclear(res);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
}
|
||||
|
||||
for (i = 0; i < featureAddressLines; i++)
|
||||
{
|
||||
// insert into place_address
|
||||
paramValues[0] = (const char *)place_id;
|
||||
paramValues[1] = (const char *)featureAddress[i].distance;
|
||||
if (paramValues[1] == NULL || strlen(paramValues[1]) == 0) paramValues[1] = "0";
|
||||
paramValues[2] = (const char *)featureAddress[i].type;
|
||||
paramValues[3] = (const char *)featureAddress[i].id;
|
||||
paramValues[4] = (const char *)featureAddress[i].key;
|
||||
paramValues[5] = (const char *)featureAddress[i].value;
|
||||
paramValues[6] = (const char *)featureAddress[i].isAddress;
|
||||
if (verbose) fprintf(stderr, "placex_insert: %s %s\n", paramValues[2], paramValues[3]);
|
||||
res = PQexecPrepared(conn, "place_addressline_insert", 7, paramValues, NULL, NULL, 0);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "place_addressline_insert: INSERT failed: %s", PQerrorMessage(conn));
|
||||
fprintf(stderr, "(%s,%s,%s,%s,%s,%s,%s)",paramValues[0],paramValues[1],paramValues[2],paramValues[3],paramValues[4],paramValues[5],paramValues[6]);
|
||||
PQclear(res);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
xmlFree(featureAddress[i].type);
|
||||
xmlFree(featureAddress[i].id);
|
||||
xmlFree(featureAddress[i].key);
|
||||
xmlFree(featureAddress[i].value);
|
||||
xmlFree(featureAddress[i].distance);
|
||||
}
|
||||
|
||||
if (featureNameLines)
|
||||
{
|
||||
if (xmlStrlen(feature.parentPlaceID) > 0 && featureAddressLines == 0)
|
||||
{
|
||||
paramValues[0] = (const char *)place_id;
|
||||
paramValues[1] = (const char *)feature.parentPlaceID;
|
||||
if (verbose) fprintf(stderr, "search_name_from_parent_insert: INSERT %s %s\n", paramValues[0], paramValues[1]);
|
||||
res = PQexecPrepared(conn, "search_name_from_parent_insert", 2, paramValues, NULL, NULL, 0);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "search_name_from_parent_insert: INSERT failed: %s", PQerrorMessage(conn));
|
||||
PQclear(res);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
}
|
||||
else
|
||||
{
|
||||
paramValues[0] = (const char *)place_id;
|
||||
if (verbose) fprintf(stderr, "search_name_insert: INSERT %s\n", paramValues[0]);
|
||||
res = PQexecPrepared(conn, "search_name_insert", 1, paramValues, NULL, NULL, 0);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "search_name_insert: INSERT failed: %s", PQerrorMessage(conn));
|
||||
PQclear(res);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
}
|
||||
}
|
||||
|
||||
partionQueryName = xmlHashLookup2(partionTableTagsHash, feature.key, feature.value);
|
||||
if (partionQueryName)
|
||||
{
|
||||
// insert into partition table
|
||||
paramValues[0] = (const char *)place_id;
|
||||
paramValues[1] = (const char *)feature.geometry;
|
||||
res = PQexecPrepared(conn, partionQueryName, 2, paramValues, NULL, NULL, 0);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "%s: INSERT failed: %s", partionQueryName, PQerrorMessage(conn));
|
||||
PQclear(res);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < featureAddressLines; i++)
|
||||
{
|
||||
xmlFree(featureAddress[i].type);
|
||||
xmlFree(featureAddress[i].id);
|
||||
xmlFree(featureAddress[i].key);
|
||||
xmlFree(featureAddress[i].value);
|
||||
xmlFree(featureAddress[i].distance);
|
||||
}
|
||||
}
|
||||
|
||||
xmlFree(feature.placeID);
|
||||
xmlFree(feature.type);
|
||||
xmlFree(feature.id);
|
||||
xmlFree(feature.key);
|
||||
xmlFree(feature.value);
|
||||
xmlFree(feature.rankAddress);
|
||||
xmlFree(feature.rankSearch);
|
||||
if (feature.countryCode) xmlFree(feature.countryCode);
|
||||
if (feature.parentPlaceID) xmlFree(feature.parentPlaceID);
|
||||
if (feature.parentType) xmlFree(feature.parentType);
|
||||
if (feature.parentID) xmlFree(feature.parentID);
|
||||
// if (feature.name) xmlFree(feature.name);
|
||||
if (feature.adminLevel) xmlFree(feature.adminLevel);
|
||||
if (feature.houseNumber) xmlFree(feature.houseNumber);
|
||||
if (feature.geometry) xmlFree(feature.geometry);
|
||||
|
||||
// PQclear(resPlaceID);
|
||||
}
|
||||
}
|
||||
|
||||
static void processNode(xmlTextReaderPtr reader)
|
||||
{
|
||||
xmlChar *name;
|
||||
name = xmlTextReaderName(reader);
|
||||
if (name == NULL)
|
||||
{
|
||||
name = xmlStrdup(BAD_CAST "--");
|
||||
}
|
||||
|
||||
switch (xmlTextReaderNodeType(reader))
|
||||
{
|
||||
case XML_READER_TYPE_ELEMENT:
|
||||
StartElement(reader, name);
|
||||
if (xmlTextReaderIsEmptyElement(reader))
|
||||
EndElement(reader, name); /* No end_element for self closing tags! */
|
||||
break;
|
||||
case XML_READER_TYPE_END_ELEMENT:
|
||||
EndElement(reader, name);
|
||||
break;
|
||||
case XML_READER_TYPE_TEXT:
|
||||
case XML_READER_TYPE_CDATA:
|
||||
case XML_READER_TYPE_SIGNIFICANT_WHITESPACE:
|
||||
/* Ignore */
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Unknown node type %d\n", xmlTextReaderNodeType(reader));
|
||||
break;
|
||||
}
|
||||
|
||||
xmlFree(name);
|
||||
}
|
||||
|
||||
int nominatim_import(const char *conninfo, const char *partionTagsFilename, const char *filename)
|
||||
{
|
||||
xmlTextReaderPtr reader;
|
||||
int ret = 0;
|
||||
PGresult * res;
|
||||
FILE * partionTagsFile;
|
||||
char * partionQueryName;
|
||||
char partionQuerySQL[1024];
|
||||
|
||||
conn = PQconnectdb(conninfo);
|
||||
if (PQstatus(conn) != CONNECTION_OK)
|
||||
{
|
||||
fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
partionTableTagsHash = xmlHashCreate(200);
|
||||
partionTableTagsHashDelete = xmlHashCreate(200);
|
||||
|
||||
partionTagsFile = fopen(partionTagsFilename, "rt");
|
||||
if (!partionTagsFile)
|
||||
{
|
||||
fprintf(stderr, "Unable to read partition tags file: %s\n", partionTagsFilename);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
char buffer[1024], osmkey[256], osmvalue[256];
|
||||
int fields;
|
||||
while (fgets(buffer, sizeof(buffer), partionTagsFile) != NULL)
|
||||
{
|
||||
fields = sscanf( buffer, "%23s %63s", osmkey, osmvalue );
|
||||
|
||||
if ( fields <= 0 ) continue;
|
||||
|
||||
if ( fields != 2 )
|
||||
{
|
||||
fprintf( stderr, "Error partition file\n");
|
||||
exit_nicely();
|
||||
}
|
||||
partionQueryName = malloc(strlen("partition_insert_")+strlen(osmkey)+strlen(osmvalue)+2);
|
||||
strcpy(partionQueryName, "partition_insert_");
|
||||
strcat(partionQueryName, osmkey);
|
||||
strcat(partionQueryName, "_");
|
||||
strcat(partionQueryName, osmvalue);
|
||||
|
||||
strcpy(partionQuerySQL, "insert into place_classtype_");
|
||||
strcat(partionQuerySQL, osmkey);
|
||||
strcat(partionQuerySQL, "_");
|
||||
strcat(partionQuerySQL, osmvalue);
|
||||
strcat(partionQuerySQL, " (place_id, centroid) values ($1, ST_Centroid(st_setsrid($2, 4326)))");
|
||||
|
||||
res = PQprepare(conn, partionQueryName, partionQuerySQL, 2, NULL);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed to prepare %s: %s\n", partionQueryName, PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
xmlHashAddEntry2(partionTableTagsHash, BAD_CAST osmkey, BAD_CAST osmvalue, BAD_CAST partionQueryName);
|
||||
|
||||
partionQueryName = malloc(strlen("partition_delete_")+strlen(osmkey)+strlen(osmvalue)+2);
|
||||
strcpy(partionQueryName, "partition_delete_");
|
||||
strcat(partionQueryName, osmkey);
|
||||
strcat(partionQueryName, "_");
|
||||
strcat(partionQueryName, osmvalue);
|
||||
|
||||
strcpy(partionQuerySQL, "delete from place_classtype_");
|
||||
strcat(partionQuerySQL, osmkey);
|
||||
strcat(partionQuerySQL, "_");
|
||||
strcat(partionQuerySQL, osmvalue);
|
||||
strcat(partionQuerySQL, " where place_id = $1::integer");
|
||||
|
||||
res = PQprepare(conn, partionQueryName, partionQuerySQL, 1, NULL);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed to prepare %s: %s\n", partionQueryName, PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
xmlHashAddEntry2(partionTableTagsHashDelete, BAD_CAST osmkey, BAD_CAST osmvalue, BAD_CAST partionQueryName);
|
||||
}
|
||||
|
||||
res = PQprepare(conn, "get_new_place_id",
|
||||
"select nextval('seq_place')",
|
||||
0, NULL);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed to prepare get_new_place_id: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
res = PQprepare(conn, "get_place_id",
|
||||
"select place_id from placex where osm_type = $1 and osm_id = $2 and class = $3 and type = $4",
|
||||
4, NULL);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed to prepare get_place_id: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
res = PQprepare(conn, "placex_insert",
|
||||
"insert into placex (place_id,osm_type,osm_id,class,type,name,country_code,extratags,parent_place_id,admin_level,housenumber,rank_address,rank_search,geometry) "
|
||||
"values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, st_setsrid($14, 4326))",
|
||||
12, NULL);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed to prepare placex_insert: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
res = PQprepare(conn, "search_name_insert",
|
||||
"insert into search_name (place_id, search_rank, address_rank, country_code, name_vector, nameaddress_vector, centroid) "
|
||||
"select place_id, rank_search, rank_address, country_code, make_keywords(name), "
|
||||
"(select uniq(sort(array_agg(parent_search_name.name_vector))) from search_name as parent_search_name where place_id in "
|
||||
"(select distinct address_place_id from place_addressline where place_addressline.place_id = $1 limit 1000)"
|
||||
"), st_centroid(geometry) from placex "
|
||||
"where place_id = $1",
|
||||
1, NULL);
|
||||
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed to prepare search_name_insert: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
res = PQprepare(conn, "search_name_from_parent_insert",
|
||||
"insert into search_name (place_id, search_rank, address_rank, country_code, name_vector, nameaddress_vector, centroid) "
|
||||
"select place_id, rank_search, rank_address, country_code, make_keywords(name), "
|
||||
"(select uniq(sort(name_vector+nameaddress_vector)) from search_name as parent_search_name "
|
||||
"where parent_search_name.place_id = $2 ), st_centroid(geometry) from placex "
|
||||
"where place_id = $1",
|
||||
2, NULL);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed to prepare search_name_insert: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
res = PQprepare(conn, "place_addressline_insert",
|
||||
"insert into place_addressline (place_id, address_place_id, fromarea, isaddress, distance, cached_rank_address) "
|
||||
"select $1, place_id, false, $7, $2, rank_address from placex where osm_type = $3 and osm_id = $4 and class = $5 and type = $6",
|
||||
7, NULL);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed to prepare place_addressline_insert: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
res = PQprepare(conn, "placex_delete",
|
||||
"delete from placex where place_id = $1",
|
||||
1, NULL);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed to prepare placex_delete: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
res = PQprepare(conn, "search_name_delete",
|
||||
"delete from search_name where place_id = $1",
|
||||
1, NULL);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed to prepare search_name_delete: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
res = PQprepare(conn, "place_addressline_delete",
|
||||
"delete from place_addressline where place_id = $1",
|
||||
1, NULL);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed to prepare place_addressline_delete: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
featureCount = 0;
|
||||
|
||||
reader = inputUTF8(filename);
|
||||
|
||||
if (reader == NULL)
|
||||
{
|
||||
fprintf(stderr, "Unable to open %s\n", filename);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ret = xmlTextReaderRead(reader);
|
||||
while (ret == 1)
|
||||
{
|
||||
processNode(reader);
|
||||
ret = xmlTextReaderRead(reader);
|
||||
}
|
||||
if (ret != 0)
|
||||
{
|
||||
fprintf(stderr, "%s : failed to parse\n", filename);
|
||||
return ret;
|
||||
}
|
||||
|
||||
xmlFreeTextReader(reader);
|
||||
xmlHashFree(partionTableTagsHash, NULL);
|
||||
xmlHashFree(partionTableTagsHashDelete, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#ifndef IMPORT_H
|
||||
#define IMPORT_H
|
||||
|
||||
int nominatim_import(const char *conninfo, const char *partionTagsFilename, const char *filename);
|
||||
|
||||
#endif
|
||||
@@ -1,547 +0,0 @@
|
||||
/*
|
||||
* triggers indexing (reparenting etc.) through setting resetting indexed_status: update placex/osmline set indexed_status = 0 where indexed_status > 0
|
||||
* triggers placex_update and osmline_update
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <libpq-fe.h>
|
||||
|
||||
#include "nominatim.h"
|
||||
#include "index.h"
|
||||
#include "export.h"
|
||||
#include "postgresql.h"
|
||||
|
||||
extern int verbose;
|
||||
|
||||
void run_indexing(int rank, int interpolation, PGconn *conn, int num_threads,
|
||||
struct index_thread_data * thread_data, const char *structuredoutputfile)
|
||||
{
|
||||
int tuples, count, sleepcount;
|
||||
pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
time_t rankStartTime;
|
||||
int rankTotalTuples;
|
||||
int rankCountTuples;
|
||||
float rankPerSecond;
|
||||
|
||||
PGresult * resSectors;
|
||||
PGresult * resPlaces;
|
||||
PGresult * resNULL;
|
||||
|
||||
int i;
|
||||
int iSector;
|
||||
int iResult;
|
||||
|
||||
const char *paramValues[2];
|
||||
int paramLengths[2];
|
||||
int paramFormats[2];
|
||||
uint32_t paramRank;
|
||||
uint32_t paramSector;
|
||||
uint32_t sector;
|
||||
|
||||
xmlTextWriterPtr writer;
|
||||
pthread_mutex_t writer_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
// Create the output file
|
||||
writer = NULL;
|
||||
if (structuredoutputfile)
|
||||
{
|
||||
writer = nominatim_exportXMLStart(structuredoutputfile);
|
||||
}
|
||||
|
||||
if (interpolation)
|
||||
{
|
||||
fprintf(stderr, "Starting interpolation lines (location_property_osmline)\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Starting rank %d\n", rank);
|
||||
}
|
||||
|
||||
rankCountTuples = 0;
|
||||
rankPerSecond = 0;
|
||||
|
||||
paramRank = PGint32(rank);
|
||||
paramValues[0] = (char *)¶mRank;
|
||||
paramLengths[0] = sizeof(paramRank);
|
||||
paramFormats[0] = 1;
|
||||
|
||||
if (interpolation)
|
||||
{
|
||||
resSectors = PQexecPrepared(conn, "index_sectors_osmline", 0, NULL, 0, NULL, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
resSectors = PQexecPrepared(conn, "index_sectors", 1, paramValues, paramLengths, paramFormats, 1);
|
||||
}
|
||||
if (PQresultStatus(resSectors) != PGRES_TUPLES_OK)
|
||||
{
|
||||
fprintf(stderr, "index_sectors: SELECT failed: %s", PQerrorMessage(conn));
|
||||
PQclear(resSectors);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (PQftype(resSectors, 0) != PG_OID_INT4)
|
||||
{
|
||||
fprintf(stderr, "Sector value has unexpected type\n");
|
||||
PQclear(resSectors);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (PQftype(resSectors, 1) != PG_OID_INT8)
|
||||
{
|
||||
fprintf(stderr, "Sector value has unexpected type\n");
|
||||
PQclear(resSectors);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
rankTotalTuples = 0;
|
||||
for (iSector = 0; iSector < PQntuples(resSectors); iSector++)
|
||||
{
|
||||
rankTotalTuples += PGint64(*((uint64_t *)PQgetvalue(resSectors, iSector, 1)));
|
||||
}
|
||||
|
||||
rankStartTime = time(0);
|
||||
for (iSector = 0; iSector <= PQntuples(resSectors); iSector++)
|
||||
{
|
||||
if (iSector > 0)
|
||||
{
|
||||
resPlaces = PQgetResult(conn);
|
||||
if (PQresultStatus(resPlaces) != PGRES_TUPLES_OK)
|
||||
{
|
||||
fprintf(stderr, "index_sector_places: SELECT failed: %s", PQerrorMessage(conn));
|
||||
PQclear(resPlaces);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (PQftype(resPlaces, 0) != PG_OID_INT8)
|
||||
{
|
||||
fprintf(stderr, "Place_id value has unexpected type\n");
|
||||
PQclear(resPlaces);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
resNULL = PQgetResult(conn);
|
||||
if (resNULL != NULL)
|
||||
{
|
||||
fprintf(stderr, "Unexpected non-null response\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (iSector < PQntuples(resSectors))
|
||||
{
|
||||
sector = PGint32(*((uint32_t *)PQgetvalue(resSectors, iSector, 0)));
|
||||
// fprintf(stderr, "\n Starting sector %d size %ld\n", sector, PGint64(*((uint64_t *)PQgetvalue(resSectors, iSector, 1))));
|
||||
|
||||
// Get all the place_id's for this sector
|
||||
paramRank = PGint32(rank);
|
||||
paramSector = PGint32(sector);
|
||||
if (rankTotalTuples-rankCountTuples < num_threads*1000)
|
||||
{
|
||||
// no sectors
|
||||
if (interpolation)
|
||||
{
|
||||
iResult = PQsendQueryPrepared(conn, "index_nosector_places_osmline", 0, NULL, 0, NULL, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
paramValues[0] = (char *)¶mRank;
|
||||
paramLengths[0] = sizeof(paramRank);
|
||||
paramFormats[0] = 1;
|
||||
iResult = PQsendQueryPrepared(conn, "index_nosector_places", 1, paramValues, paramLengths, paramFormats, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (interpolation)
|
||||
{
|
||||
iResult = PQsendQueryPrepared(conn, "index_sector_places_osmline", 1, paramValues, paramLengths, paramFormats, 1);
|
||||
paramValues[0] = (char *)¶mSector;
|
||||
paramLengths[0] = sizeof(paramSector);
|
||||
paramFormats[0] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
paramValues[0] = (char *)¶mRank;
|
||||
paramLengths[0] = sizeof(paramRank);
|
||||
paramFormats[0] = 1;
|
||||
paramValues[1] = (char *)¶mSector;
|
||||
paramLengths[1] = sizeof(paramSector);
|
||||
paramFormats[1] = 1;
|
||||
iResult = PQsendQueryPrepared(conn, "index_sector_places", 2, paramValues, paramLengths, paramFormats, 1);
|
||||
}
|
||||
}
|
||||
if (!iResult)
|
||||
{
|
||||
fprintf(stderr, "index_sector_places: SELECT failed: %s", PQerrorMessage(conn));
|
||||
PQclear(resPlaces);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
if (iSector > 0)
|
||||
{
|
||||
count = 0;
|
||||
rankPerSecond = 0;
|
||||
tuples = PQntuples(resPlaces);
|
||||
|
||||
if (tuples > 0)
|
||||
{
|
||||
// Spawn threads
|
||||
for (i = 0; i < num_threads; i++)
|
||||
{
|
||||
thread_data[i].res = resPlaces;
|
||||
thread_data[i].tuples = tuples;
|
||||
thread_data[i].count = &count;
|
||||
thread_data[i].count_mutex = &count_mutex;
|
||||
thread_data[i].writer = writer;
|
||||
thread_data[i].writer_mutex = &writer_mutex;
|
||||
if (interpolation)
|
||||
{
|
||||
thread_data[i].table = 0; // use interpolations table
|
||||
}
|
||||
else
|
||||
{
|
||||
thread_data[i].table = 1; // use placex table
|
||||
}
|
||||
pthread_create(&thread_data[i].thread, NULL, &nominatim_indexThread, (void *)&thread_data[i]);
|
||||
}
|
||||
|
||||
// Monitor threads to give user feedback
|
||||
sleepcount = 0;
|
||||
while (count < tuples)
|
||||
{
|
||||
usleep(1000);
|
||||
|
||||
// Aim for one update per second
|
||||
if (sleepcount++ > 1000)
|
||||
{
|
||||
rankPerSecond = ((float)rankCountTuples + (float)count) / MAX(difftime(time(0), rankStartTime),1);
|
||||
if(interpolation)
|
||||
{
|
||||
fprintf(stderr, " Done %i in %i @ %f per second - Interpolation lines ETA (seconds): %f\n", (rankCountTuples + count), (int)(difftime(time(0), rankStartTime)), rankPerSecond, ((float)(rankTotalTuples - (rankCountTuples + count)))/rankPerSecond);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, " Done %i in %i @ %f per second - Rank %i ETA (seconds): %f\n", (rankCountTuples + count), (int)(difftime(time(0), rankStartTime)), rankPerSecond, rank, ((float)(rankTotalTuples - (rankCountTuples + count)))/rankPerSecond);
|
||||
}
|
||||
|
||||
sleepcount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for everything to finish
|
||||
for (i = 0; i < num_threads; i++)
|
||||
{
|
||||
pthread_join(thread_data[i].thread, NULL);
|
||||
}
|
||||
|
||||
rankCountTuples += tuples;
|
||||
}
|
||||
|
||||
// Finished sector
|
||||
rankPerSecond = (float)rankCountTuples / MAX(difftime(time(0), rankStartTime),1);
|
||||
fprintf(stderr, " Done %i in %i @ %f per second - ETA (seconds): %f\n", rankCountTuples, (int)(difftime(time(0), rankStartTime)), rankPerSecond, ((float)(rankTotalTuples - rankCountTuples))/rankPerSecond);
|
||||
|
||||
PQclear(resPlaces);
|
||||
}
|
||||
if (rankTotalTuples-rankCountTuples < num_threads*20 && iSector < PQntuples(resSectors))
|
||||
{
|
||||
iSector = PQntuples(resSectors) - 1;
|
||||
}
|
||||
}
|
||||
// Finished rank
|
||||
fprintf(stderr, "\r Done %i in %i @ %f per second - FINISHED\n\n", rankCountTuples, (int)(difftime(time(0), rankStartTime)), rankPerSecond);
|
||||
|
||||
PQclear(resSectors);
|
||||
}
|
||||
|
||||
void nominatim_index(int rank_min, int rank_max, int num_threads, const char *conninfo, const char *structuredoutputfile)
|
||||
{
|
||||
struct index_thread_data *thread_data;
|
||||
|
||||
PGconn *conn;
|
||||
PGresult *res;
|
||||
int num_rows = 0, status_code = 0;
|
||||
int db_has_locale = 0;
|
||||
char *result_string = NULL;
|
||||
|
||||
int rank;
|
||||
|
||||
int i;
|
||||
|
||||
xmlTextWriterPtr writer;
|
||||
pthread_mutex_t writer_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
Oid pg_prepare_params[2];
|
||||
|
||||
conn = PQconnectdb(conninfo);
|
||||
if (PQstatus(conn) != CONNECTION_OK)
|
||||
{
|
||||
fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
res = PQexec(conn, "SHOW lc_messages");
|
||||
status_code = PQresultStatus(res);
|
||||
if (status_code != PGRES_TUPLES_OK && status_code != PGRES_SINGLE_TUPLE) {
|
||||
fprintf(stderr, "Failed determining database locale: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
num_rows = PQntuples(res);
|
||||
if (num_rows > 0)
|
||||
{
|
||||
result_string = PQgetvalue(res, 0, 0);
|
||||
if (result_string && (strlen(result_string) > 0) && (strcasecmp(result_string, "C") != 0))
|
||||
{
|
||||
// non-default locale if the result exists, is non-empty, and is not "C"
|
||||
db_has_locale = 1;
|
||||
}
|
||||
}
|
||||
|
||||
pg_prepare_params[0] = PG_OID_INT4;
|
||||
res = PQprepare(conn, "index_sectors",
|
||||
"select geometry_sector,count(*) from placex where rank_search = $1 and indexed_status > 0 group by geometry_sector order by geometry_sector",
|
||||
1, pg_prepare_params);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed preparing index_sectors: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
res = PQprepare(conn, "index_sectors_osmline",
|
||||
"select geometry_sector,count(*) from location_property_osmline where indexed_status > 0 group by geometry_sector order by geometry_sector",
|
||||
0, NULL);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed preparing index_sectors: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
pg_prepare_params[0] = PG_OID_INT4;
|
||||
res = PQprepare(conn, "index_nosectors",
|
||||
"select 0::integer,count(*) from placex where rank_search = $1 and indexed_status > 0",
|
||||
1, pg_prepare_params);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed preparing index_sectors: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
pg_prepare_params[0] = PG_OID_INT4;
|
||||
pg_prepare_params[1] = PG_OID_INT4;
|
||||
res = PQprepare(conn, "index_sector_places",
|
||||
"select place_id from placex where rank_search = $1 and geometry_sector = $2 and indexed_status > 0",
|
||||
2, pg_prepare_params);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed preparing index_sector_places: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
pg_prepare_params[0] = PG_OID_INT4;
|
||||
res = PQprepare(conn, "index_nosector_places",
|
||||
"select place_id from placex where rank_search = $1 and indexed_status > 0 order by geometry_sector",
|
||||
1, pg_prepare_params);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed preparing index_nosector_places: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
pg_prepare_params[0] = PG_OID_INT4;
|
||||
res = PQprepare(conn, "index_sector_places_osmline",
|
||||
"select place_id from location_property_osmline where geometry_sector = $1 and indexed_status > 0",
|
||||
1, pg_prepare_params);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed preparing index_sector_places: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
res = PQprepare(conn, "index_nosector_places_osmline",
|
||||
"select place_id from location_property_osmline where indexed_status > 0 order by geometry_sector",
|
||||
0, NULL);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed preparing index_nosector_places: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
// Build the data for each thread
|
||||
thread_data = (struct index_thread_data *)malloc(sizeof(struct index_thread_data)*num_threads);
|
||||
for (i = 0; i < num_threads; i++)
|
||||
{
|
||||
thread_data[i].conn = PQconnectdb(conninfo);
|
||||
if (PQstatus(thread_data[i].conn) != CONNECTION_OK)
|
||||
{
|
||||
fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(thread_data[i].conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
pg_prepare_params[0] = PG_OID_INT8;
|
||||
res = PQprepare(thread_data[i].conn, "index_placex",
|
||||
"update placex set indexed_status = 0 where place_id = $1",
|
||||
1, pg_prepare_params);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed preparing index_placex: %s\n", PQerrorMessage(thread_data[i].conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
pg_prepare_params[0] = PG_OID_INT8;
|
||||
res = PQprepare(thread_data[i].conn, "index_osmline",
|
||||
"update location_property_osmline set indexed_status = 0 where place_id = $1",
|
||||
1, pg_prepare_params);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed preparing index_osmline: %s\n", PQerrorMessage(thread_data[i].conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
|
||||
if (db_has_locale)
|
||||
{
|
||||
// Make sure the error message is not localized as we parse it later.
|
||||
res = PQexec(thread_data[i].conn, "SET lc_messages TO 'C'");
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed to set langauge: %s\n", PQerrorMessage(thread_data[i].conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQclear(res);
|
||||
}
|
||||
nominatim_exportCreatePreparedQueries(thread_data[i].conn);
|
||||
}
|
||||
|
||||
fprintf(stderr, "Starting indexing rank (%i to %i) using %i threads\n", rank_min, rank_max, num_threads);
|
||||
|
||||
for (rank = rank_min; rank <= rank_max; rank++)
|
||||
{
|
||||
// OSMLINE: do reindexing (=> reparenting) for interpolation lines at rank 30, but before all other objects of rank 30
|
||||
// reason: houses (rank 30) depend on the updated interpolation line, when reparenting (see placex_update in functions.sql)
|
||||
if (rank == 30)
|
||||
{
|
||||
run_indexing(rank, 1, conn, num_threads, thread_data, structuredoutputfile);
|
||||
}
|
||||
run_indexing(rank, 0, conn, num_threads, thread_data, structuredoutputfile);
|
||||
}
|
||||
// Close all connections
|
||||
for (i = 0; i < num_threads; i++)
|
||||
{
|
||||
PQfinish(thread_data[i].conn);
|
||||
}
|
||||
PQfinish(conn);
|
||||
}
|
||||
|
||||
void *nominatim_indexThread(void * thread_data_in)
|
||||
{
|
||||
struct index_thread_data * thread_data = (struct index_thread_data * )thread_data_in;
|
||||
struct export_data querySet;
|
||||
|
||||
PGresult *res;
|
||||
|
||||
const char *paramValues[1];
|
||||
int paramLengths[1];
|
||||
int paramFormats[1];
|
||||
uint64_t paramPlaceID;
|
||||
uint64_t place_id;
|
||||
time_t updateStartTime;
|
||||
unsigned table;
|
||||
|
||||
table = thread_data->table;
|
||||
|
||||
while (1)
|
||||
{
|
||||
pthread_mutex_lock( thread_data->count_mutex );
|
||||
if (*(thread_data->count) >= thread_data->tuples)
|
||||
{
|
||||
pthread_mutex_unlock( thread_data->count_mutex );
|
||||
break;
|
||||
}
|
||||
|
||||
place_id = PGint64(*((uint64_t *)PQgetvalue(thread_data->res, *thread_data->count, 0)));
|
||||
(*thread_data->count)++;
|
||||
|
||||
pthread_mutex_unlock( thread_data->count_mutex );
|
||||
|
||||
if (verbose) fprintf(stderr, " Processing place_id %ld\n", place_id);
|
||||
|
||||
updateStartTime = time(0);
|
||||
int done = 0;
|
||||
|
||||
if (thread_data->writer)
|
||||
{
|
||||
nominatim_exportPlaceQueries(place_id, thread_data->conn, &querySet);
|
||||
}
|
||||
|
||||
while(!done)
|
||||
{
|
||||
paramPlaceID = PGint64(place_id);
|
||||
paramValues[0] = (char *)¶mPlaceID;
|
||||
paramLengths[0] = sizeof(paramPlaceID);
|
||||
paramFormats[0] = 1;
|
||||
if (table == 1) // table=1 for placex
|
||||
{
|
||||
res = PQexecPrepared(thread_data->conn, "index_placex", 1, paramValues, paramLengths, paramFormats, 1);
|
||||
}
|
||||
else // table=0 for osmline
|
||||
{
|
||||
res = PQexecPrepared(thread_data->conn, "index_osmline", 1, paramValues, paramLengths, paramFormats, 1);
|
||||
}
|
||||
if (PQresultStatus(res) == PGRES_COMMAND_OK)
|
||||
done = 1;
|
||||
else
|
||||
{
|
||||
if (!strncmp(PQerrorMessage(thread_data->conn), "ERROR: deadlock detected", 25))
|
||||
{
|
||||
if (table == 1)
|
||||
{
|
||||
fprintf(stderr, "index_placex: UPDATE failed - deadlock, retrying (%ld)\n", place_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "index_osmline: UPDATE failed - deadlock, retrying (%ld)\n", place_id);
|
||||
}
|
||||
PQclear(res);
|
||||
sleep(rand() % 10);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (table == 1)
|
||||
{
|
||||
fprintf(stderr, "index_placex: UPDATE failed: %s", PQerrorMessage(thread_data->conn));
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "index_osmline: UPDATE failed: %s", PQerrorMessage(thread_data->conn));
|
||||
}
|
||||
PQclear(res);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
PQclear(res);
|
||||
if (difftime(time(0), updateStartTime) > 1) fprintf(stderr, " Slow place_id %ld\n", place_id);
|
||||
|
||||
if (thread_data->writer)
|
||||
{
|
||||
nominatim_exportPlace(place_id, thread_data->conn, thread_data->writer, thread_data->writer_mutex, &querySet);
|
||||
nominatim_exportFreeQueries(&querySet);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#ifndef INDEX_H
|
||||
#define INDEX_H
|
||||
|
||||
#include <libxml/encoding.h>
|
||||
#include <libxml/xmlwriter.h>
|
||||
|
||||
struct index_thread_data
|
||||
{
|
||||
pthread_t thread;
|
||||
PGconn * conn;
|
||||
PGresult * res;
|
||||
int tuples;
|
||||
int * count;
|
||||
pthread_mutex_t * count_mutex;
|
||||
xmlTextWriterPtr writer;
|
||||
pthread_mutex_t * writer_mutex;
|
||||
unsigned table;
|
||||
};
|
||||
void nominatim_index(int rank_min, int rank_max, int num_threads, const char *conninfo, const char *structuredoutputfile);
|
||||
void *nominatim_indexThread(void * thread_data_in);
|
||||
|
||||
#endif
|
||||
@@ -1,242 +0,0 @@
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
#define _LARGEFILE64_SOURCE
|
||||
|
||||
#ifdef __MINGW_H
|
||||
# include <windows.h>
|
||||
#else
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
#include <libxml/xmlreader.h>
|
||||
#include <bzlib.h>
|
||||
|
||||
#include "input.h"
|
||||
|
||||
struct Input
|
||||
{
|
||||
char *name;
|
||||
enum { plainFile, gzipFile, bzip2File } type;
|
||||
void *fileHandle;
|
||||
// needed by bzip2 when decompressing from multiple streams. other
|
||||
// decompressors must ignore it.
|
||||
FILE *systemHandle;
|
||||
int eof;
|
||||
char buf[4096];
|
||||
int buf_ptr, buf_fill;
|
||||
};
|
||||
|
||||
// tries to re-open the bz stream at the next stream start.
|
||||
// returns 0 on success, -1 on failure.
|
||||
int bzReOpen(struct Input *ctx, int *error)
|
||||
{
|
||||
// for copying out the last unused part of the block which
|
||||
// has an EOS token in it. needed for re-initialising the
|
||||
// next stream.
|
||||
unsigned char unused[BZ_MAX_UNUSED];
|
||||
void *unused_tmp_ptr = NULL;
|
||||
int nUnused, i;
|
||||
|
||||
BZ2_bzReadGetUnused(error, (BZFILE *)(ctx->fileHandle), &unused_tmp_ptr, &nUnused);
|
||||
if (*error != BZ_OK) return -1;
|
||||
|
||||
// when bzReadClose is called the unused buffer is deallocated,
|
||||
// so it needs to be copied somewhere safe first.
|
||||
for (i = 0; i < nUnused; ++i)
|
||||
unused[i] = ((unsigned char *)unused_tmp_ptr)[i];
|
||||
|
||||
BZ2_bzReadClose(error, (BZFILE *)(ctx->fileHandle));
|
||||
if (*error != BZ_OK) return -1;
|
||||
|
||||
// reassign the file handle
|
||||
ctx->fileHandle = BZ2_bzReadOpen(error, ctx->systemHandle, 0, 0, unused, nUnused);
|
||||
if (ctx->fileHandle == NULL || *error != BZ_OK) return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int readFile(void *context, char * buffer, int len)
|
||||
{
|
||||
struct Input *ctx = context;
|
||||
void *f = ctx->fileHandle;
|
||||
int l = 0, error = 0;
|
||||
|
||||
if (ctx->eof || (len == 0))
|
||||
return 0;
|
||||
|
||||
switch (ctx->type)
|
||||
{
|
||||
case plainFile:
|
||||
l = read(*(int *)f, buffer, len);
|
||||
if (l <= 0) ctx->eof = 1;
|
||||
break;
|
||||
case gzipFile:
|
||||
l = gzread((gzFile)f, buffer, len);
|
||||
if (l <= 0) ctx->eof = 1;
|
||||
break;
|
||||
case bzip2File:
|
||||
l = BZ2_bzRead(&error, (BZFILE *)f, buffer, len);
|
||||
|
||||
// error codes BZ_OK and BZ_STREAM_END are both "OK", but the stream
|
||||
// end means the reader needs to be reset from the original handle.
|
||||
if (error != BZ_OK)
|
||||
{
|
||||
// for stream errors, try re-opening the stream before admitting defeat.
|
||||
if (error != BZ_STREAM_END || bzReOpen(ctx, &error) != 0)
|
||||
{
|
||||
l = 0;
|
||||
ctx->eof = 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Bad file type\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (l < 0)
|
||||
{
|
||||
fprintf(stderr, "File reader received error %d (%d)\n", l, error);
|
||||
l = 0;
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
char inputGetChar(void *context)
|
||||
{
|
||||
struct Input *ctx = context;
|
||||
|
||||
if (ctx->buf_ptr == ctx->buf_fill)
|
||||
{
|
||||
ctx->buf_fill = readFile(context, &ctx->buf[0], sizeof(ctx->buf));
|
||||
ctx->buf_ptr = 0;
|
||||
if (ctx->buf_fill == 0)
|
||||
return 0;
|
||||
if (ctx->buf_fill < 0)
|
||||
{
|
||||
perror("Error while reading file");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
//readFile(context, &c, 1);
|
||||
return ctx->buf[ctx->buf_ptr++];
|
||||
}
|
||||
|
||||
int inputEof(void *context)
|
||||
{
|
||||
return ((struct Input *)context)->eof;
|
||||
}
|
||||
|
||||
void *inputOpen(const char *name)
|
||||
{
|
||||
const char *ext = strrchr(name, '.');
|
||||
struct Input *ctx = malloc (sizeof(*ctx));
|
||||
|
||||
if (!ctx)
|
||||
return NULL;
|
||||
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
|
||||
ctx->name = strdup(name);
|
||||
|
||||
if (ext && !strcmp(ext, ".gz"))
|
||||
{
|
||||
ctx->fileHandle = (void *)gzopen(name, "rb");
|
||||
ctx->type = gzipFile;
|
||||
}
|
||||
else if (ext && !strcmp(ext, ".bz2"))
|
||||
{
|
||||
int error = 0;
|
||||
ctx->systemHandle = fopen(name, "rb");
|
||||
if (!ctx->systemHandle)
|
||||
{
|
||||
fprintf(stderr, "error while opening file %s\n", name);
|
||||
exit(10);
|
||||
}
|
||||
|
||||
ctx->fileHandle = (void *)BZ2_bzReadOpen(&error, ctx->systemHandle, 0, 0, NULL, 0);
|
||||
ctx->type = bzip2File;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
int *pfd = malloc(sizeof(pfd));
|
||||
if (pfd)
|
||||
{
|
||||
if (!strcmp(name, "-"))
|
||||
{
|
||||
*pfd = STDIN_FILENO;
|
||||
}
|
||||
else
|
||||
{
|
||||
int flags = O_RDONLY;
|
||||
#ifdef O_LARGEFILE
|
||||
flags |= O_LARGEFILE;
|
||||
#endif
|
||||
*pfd = open(name, flags);
|
||||
if (*pfd < 0)
|
||||
{
|
||||
free(pfd);
|
||||
pfd = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx->fileHandle = (void *)pfd;
|
||||
ctx->type = plainFile;
|
||||
}
|
||||
if (!ctx->fileHandle)
|
||||
{
|
||||
fprintf(stderr, "error while opening file %s\n", name);
|
||||
exit(10);
|
||||
}
|
||||
ctx->buf_ptr = 0;
|
||||
ctx->buf_fill = 0;
|
||||
return (void *)ctx;
|
||||
}
|
||||
|
||||
int inputClose(void *context)
|
||||
{
|
||||
struct Input *ctx = context;
|
||||
void *f = ctx->fileHandle;
|
||||
|
||||
switch (ctx->type)
|
||||
{
|
||||
case plainFile:
|
||||
close(*(int *)f);
|
||||
free(f);
|
||||
break;
|
||||
case gzipFile:
|
||||
gzclose((gzFile)f);
|
||||
break;
|
||||
case bzip2File:
|
||||
BZ2_bzclose((BZFILE *)f);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Bad file type\n");
|
||||
break;
|
||||
}
|
||||
|
||||
free(ctx->name);
|
||||
free(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
xmlTextReaderPtr inputUTF8(const char *name)
|
||||
{
|
||||
void *ctx = inputOpen(name);
|
||||
|
||||
if (!ctx)
|
||||
{
|
||||
fprintf(stderr, "Input reader create failed for: %s\n", name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return xmlReaderForIO(readFile, inputClose, (void *)ctx, NULL, NULL, 0);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#ifndef INPUT_H
|
||||
#define INPUT_H
|
||||
|
||||
int readFile(void *context, char * buffer, int len);
|
||||
int inputClose(void *context);
|
||||
void *inputOpen(const char *name);
|
||||
char inputGetChar(void *context);
|
||||
int inputEof(void *context);
|
||||
xmlTextReaderPtr inputUTF8(const char *name);
|
||||
|
||||
#endif
|
||||
@@ -1,255 +0,0 @@
|
||||
/*
|
||||
#-----------------------------------------------------------------------------
|
||||
# nominatim - [description]
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright 2010, Brian Quinion
|
||||
# Based on osm2pgsql
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#-----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <getopt.h>
|
||||
#include <libgen.h>
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <libpq-fe.h>
|
||||
|
||||
#include "nominatim.h"
|
||||
#include "postgresql.h"
|
||||
#include "sprompt.h"
|
||||
#include "index.h"
|
||||
#include "export.h"
|
||||
#include "import.h"
|
||||
|
||||
int verbose;
|
||||
|
||||
void exit_nicely(void)
|
||||
{
|
||||
fprintf(stderr, "Error occurred, cleaning up\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void short_usage(char *arg0)
|
||||
{
|
||||
const char *name = basename(arg0);
|
||||
|
||||
fprintf(stderr, "Usage error. For further information see:\n");
|
||||
fprintf(stderr, "\t%s -h|--help\n", name);
|
||||
}
|
||||
|
||||
static void long_usage(char *arg0)
|
||||
{
|
||||
const char *name = basename(arg0);
|
||||
|
||||
fprintf(stderr, "Usage:\n");
|
||||
fprintf(stderr, "\t%s [options] planet.osms\n", name);
|
||||
fprintf(stderr, "\nThis will import the structured osm data into a PostgreSQL database\n");
|
||||
fprintf(stderr, "suitable for nominatim search engine\n");
|
||||
fprintf(stderr, "\nOptions:\n");
|
||||
fprintf(stderr, " -d|--database\tThe name of the PostgreSQL database to connect\n");
|
||||
fprintf(stderr, " \tto (default: nominatim).\n");
|
||||
fprintf(stderr, " -U|--username\tPostgresql user name.\n");
|
||||
fprintf(stderr, " -W|--password\tForce password prompt.\n");
|
||||
fprintf(stderr, " -H|--host\t\tDatabase server hostname or socket location.\n");
|
||||
fprintf(stderr, " -P|--port\t\tDatabase server port.\n");
|
||||
fprintf(stderr, " -i|--index\t\tIndex the database.\n");
|
||||
fprintf(stderr, " -e|--export\t\tGenerate a structured file.\n");
|
||||
fprintf(stderr, " -I|--import\t\tImport a structured file.\n");
|
||||
fprintf(stderr, " -r|--minrank\t\tMinimum / starting rank. (default: 0))\n");
|
||||
fprintf(stderr, " -R|--maxrank\t\tMaximum / finishing rank. (default: 30)\n");
|
||||
fprintf(stderr, " -t|--threads\t\tNumber of threads to create for indexing.\n");
|
||||
fprintf(stderr, " -F|--file\t\tfile to use (either to import or export).\n");
|
||||
fprintf(stderr, " -T|--tagfile\t\tfile containing 'special' tag pairs\n");
|
||||
fprintf(stderr, " \t(default: partitionedtags.def).\n");
|
||||
fprintf(stderr, " -h|--help\t\tHelp information.\n");
|
||||
fprintf(stderr, " -v|--verbose\t\tVerbose output.\n");
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
if (sizeof(int*) == 4)
|
||||
{
|
||||
fprintf(stderr, "\n\nYou are running this on 32bit system - this will not work\n");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int long_usage_bool=0;
|
||||
int pass_prompt=0;
|
||||
const char *db = "nominatim";
|
||||
const char *username=NULL;
|
||||
const char *host=NULL;
|
||||
const char *password=NULL;
|
||||
const char *port = "5432";
|
||||
const char *conninfo = NULL;
|
||||
int index = 0;
|
||||
int export = 0;
|
||||
int import = 0;
|
||||
int minrank = 0;
|
||||
int maxrank = 30;
|
||||
int threads = 1;
|
||||
const char *file = NULL;
|
||||
const char *tagsfile = "partitionedtags.def";
|
||||
|
||||
//import = 1;
|
||||
//structuredinputfile = "out.osms";
|
||||
|
||||
PGconn *conn;
|
||||
|
||||
fprintf(stderr, "nominatim version %s\n\n", NOMINATIM_VERSION);
|
||||
|
||||
while (1)
|
||||
{
|
||||
int c, option_index = 0;
|
||||
static struct option long_options[] =
|
||||
{
|
||||
{"help", 0, 0, 'h'},
|
||||
|
||||
{"verbose", 0, 0, 'v'},
|
||||
|
||||
{"database", 1, 0, 'd'},
|
||||
{"username", 1, 0, 'U'},
|
||||
{"password", 0, 0, 'W'},
|
||||
{"host", 1, 0, 'H'},
|
||||
{"port", 1, 0, 'P'},
|
||||
|
||||
{"index", 0, 0, 'i'},
|
||||
{"export", 0, 0, 'e'},
|
||||
{"import", 1, 0, 'I'},
|
||||
{"threads", 1, 0, 't'},
|
||||
{"file", 1, 0, 'F'},
|
||||
{"tagsfile", 1, 0, 'T'},
|
||||
|
||||
{"minrank", 1, 0, 'r'},
|
||||
{"maxrank", 1, 0, 'R'},
|
||||
|
||||
|
||||
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
c = getopt_long(argc, argv, "vhd:U:WH:P:ieIt:F:T:r:R:", long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case 'v':
|
||||
verbose=1;
|
||||
break;
|
||||
case 'd':
|
||||
db=optarg;
|
||||
break;
|
||||
case 'U':
|
||||
username=optarg;
|
||||
break;
|
||||
case 'W':
|
||||
pass_prompt=1;
|
||||
break;
|
||||
case 'H':
|
||||
host=optarg;
|
||||
break;
|
||||
case 'P':
|
||||
port=optarg;
|
||||
break;
|
||||
case 'h':
|
||||
long_usage_bool=1;
|
||||
break;
|
||||
case 'i':
|
||||
index=1;
|
||||
break;
|
||||
case 'e':
|
||||
export=1;
|
||||
break;
|
||||
case 'I':
|
||||
import=1;
|
||||
break;
|
||||
case 't':
|
||||
threads=atoi(optarg);
|
||||
break;
|
||||
case 'r':
|
||||
minrank=atoi(optarg);
|
||||
break;
|
||||
case 'R':
|
||||
maxrank=atoi(optarg);
|
||||
break;
|
||||
case 'F':
|
||||
file=optarg;
|
||||
break;
|
||||
case 'T':
|
||||
tagsfile=optarg;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
short_usage(argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (long_usage_bool)
|
||||
{
|
||||
long_usage(argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (threads < 1) threads = 1;
|
||||
|
||||
/*
|
||||
if (argc == optind) { // No non-switch arguments
|
||||
short_usage(argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
*/
|
||||
if (index && import)
|
||||
{
|
||||
fprintf(stderr, "Error: --index and --import options can not be used on the same database!\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (pass_prompt)
|
||||
password = simple_prompt("Password:", 100, 0);
|
||||
else
|
||||
{
|
||||
password = getenv("PGPASS");
|
||||
}
|
||||
|
||||
// Test the database connection
|
||||
conninfo = build_conninfo(db, username, password, host, port);
|
||||
conn = PQconnectdb(conninfo);
|
||||
if (PQstatus(conn) != CONNECTION_OK)
|
||||
{
|
||||
fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
PQfinish(conn);
|
||||
|
||||
if (!index && !export && !import)
|
||||
{
|
||||
fprintf(stderr, "Please select index, export or import.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (index) nominatim_index(minrank, maxrank, threads, conninfo, file);
|
||||
if (export) nominatim_export(minrank, maxrank, conninfo, file);
|
||||
if (import) nominatim_import(conninfo, tagsfile, file);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#ifndef NOMINATIM_H
|
||||
#define NOMINATIM_H
|
||||
|
||||
#define MAX(x,y) (x > y?x:y)
|
||||
#define MIN(x,y) (x < y?x:y)
|
||||
|
||||
struct output_options
|
||||
{
|
||||
const char *conninfo; /* Connection info string */
|
||||
const char *prefix; /* prefix for table names */
|
||||
int scale; /* scale for converting coordinates to fixed point */
|
||||
int projection; /* SRS of projection */
|
||||
int append; /* Append to existing data */
|
||||
int slim; /* In slim mode */
|
||||
int cache; /* Memory usable for cache in MB */
|
||||
struct middle_t *mid; /* Mid storage to use */
|
||||
const char *tblsindex; /* Pg Tablespace to store indexes */
|
||||
const char *style; /* style file to use */
|
||||
int expire_tiles_zoom; /* Zoom level for tile expiry list */
|
||||
int expire_tiles_zoom_min; /* Minimum zoom level for tile expiry list */
|
||||
const char *expire_tiles_filename; /* File name to output expired tiles list to */
|
||||
int enable_hstore; /* add an additional hstore column with objects key/value pairs */
|
||||
int enable_multi; /* Output multi-geometries instead of several simple geometries */
|
||||
char** hstore_columns; /* list of columns that should be written into their own hstore column */
|
||||
int n_hstore_columns; /* number of hstore columns */
|
||||
};
|
||||
|
||||
void exit_nicely(void);
|
||||
void short_usage(char *arg0);
|
||||
|
||||
#endif
|
||||
370
nominatim/nominatim.py
Executable file
370
nominatim/nominatim.py
Executable file
@@ -0,0 +1,370 @@
|
||||
#! /usr/bin/env python3
|
||||
#-----------------------------------------------------------------------------
|
||||
# nominatim - [description]
|
||||
#-----------------------------------------------------------------------------
|
||||
#
|
||||
# Indexing tool for the Nominatim database.
|
||||
#
|
||||
# Based on C version by Brian Quinion
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
from argparse import ArgumentParser, RawDescriptionHelpFormatter, ArgumentTypeError
|
||||
import logging
|
||||
import sys
|
||||
import re
|
||||
import getpass
|
||||
from datetime import datetime
|
||||
import psycopg2
|
||||
from psycopg2.extras import wait_select
|
||||
import select
|
||||
|
||||
log = logging.getLogger()
|
||||
|
||||
def make_connection(options, asynchronous=False):
|
||||
params = {'dbname' : options.dbname,
|
||||
'user' : options.user,
|
||||
'password' : options.password,
|
||||
'host' : options.host,
|
||||
'port' : options.port,
|
||||
'async' : asynchronous}
|
||||
|
||||
return psycopg2.connect(**params)
|
||||
|
||||
|
||||
class RankRunner(object):
|
||||
""" Returns SQL commands for indexing one rank within the placex table.
|
||||
"""
|
||||
|
||||
def __init__(self, rank):
|
||||
self.rank = rank
|
||||
|
||||
def name(self):
|
||||
return "rank {}".format(self.rank)
|
||||
|
||||
def sql_index_sectors(self):
|
||||
return """SELECT geometry_sector, count(*) FROM placex
|
||||
WHERE rank_search = {} and indexed_status > 0
|
||||
GROUP BY geometry_sector
|
||||
ORDER BY geometry_sector""".format(self.rank)
|
||||
|
||||
def sql_nosector_places(self):
|
||||
return """SELECT place_id FROM placex
|
||||
WHERE indexed_status > 0 and rank_search = {}
|
||||
ORDER BY geometry_sector""".format(self.rank)
|
||||
|
||||
def sql_sector_places(self):
|
||||
return """SELECT place_id FROM placex
|
||||
WHERE indexed_status > 0 and rank_search = {}
|
||||
and geometry_sector = %s""".format(self.rank)
|
||||
|
||||
def sql_index_place(self):
|
||||
return "UPDATE placex SET indexed_status = 0 WHERE place_id = %s"
|
||||
|
||||
|
||||
class InterpolationRunner(object):
|
||||
""" Returns SQL commands for indexing the address interpolation table
|
||||
location_property_osmline.
|
||||
"""
|
||||
|
||||
def name(self):
|
||||
return "interpolation lines (location_property_osmline)"
|
||||
|
||||
def sql_index_sectors(self):
|
||||
return """SELECT geometry_sector, count(*) FROM location_property_osmline
|
||||
WHERE indexed_status > 0
|
||||
GROUP BY geometry_sector
|
||||
ORDER BY geometry_sector"""
|
||||
|
||||
def sql_nosector_places(self):
|
||||
return """SELECT place_id FROM location_property_osmline
|
||||
WHERE indexed_status > 0
|
||||
ORDER BY geometry_sector"""
|
||||
|
||||
def sql_sector_places(self):
|
||||
return """SELECT place_id FROM location_property_osmline
|
||||
WHERE indexed_status > 0 and geometry_sector = %s
|
||||
ORDER BY geometry_sector"""
|
||||
|
||||
def sql_index_place(self):
|
||||
return """UPDATE location_property_osmline
|
||||
SET indexed_status = 0 WHERE place_id = %s"""
|
||||
|
||||
|
||||
class DBConnection(object):
|
||||
""" A single non-blocking database connection.
|
||||
"""
|
||||
|
||||
def __init__(self, options):
|
||||
self.current_query = None
|
||||
self.current_params = None
|
||||
|
||||
self.conn = None
|
||||
self.connect()
|
||||
|
||||
def connect(self):
|
||||
if self.conn is not None:
|
||||
self.cursor.close()
|
||||
self.conn.close()
|
||||
|
||||
self.conn = make_connection(options, asynchronous=True)
|
||||
self.wait()
|
||||
|
||||
self.cursor = self.conn.cursor()
|
||||
# Disable JIT and parallel workers as they are known to cause problems.
|
||||
# Update pg_settings instead of using SET because it does not yield
|
||||
# errors on older versions of Postgres where the settings are not
|
||||
# implemented.
|
||||
self.perform(
|
||||
""" UPDATE pg_settings SET setting = -1 WHERE name = 'jit_above_cost';
|
||||
UPDATE pg_settings SET setting = 0
|
||||
WHERE name = 'max_parallel_workers_per_gather';""")
|
||||
self.wait()
|
||||
|
||||
def wait(self):
|
||||
""" Block until any pending operation is done.
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
wait_select(self.conn)
|
||||
self.current_query = None
|
||||
return
|
||||
except psycopg2.extensions.TransactionRollbackError as e:
|
||||
if e.pgcode == '40P01':
|
||||
log.info("Deadlock detected (params = {}), retry."
|
||||
.format(self.current_params))
|
||||
self.cursor.execute(self.current_query, self.current_params)
|
||||
else:
|
||||
raise
|
||||
except psycopg2.errors.DeadlockDetected:
|
||||
self.cursor.execute(self.current_query, self.current_params)
|
||||
|
||||
def perform(self, sql, args=None):
|
||||
""" Send SQL query to the server. Returns immediately without
|
||||
blocking.
|
||||
"""
|
||||
self.current_query = sql
|
||||
self.current_params = args
|
||||
self.cursor.execute(sql, args)
|
||||
|
||||
def fileno(self):
|
||||
""" File descriptor to wait for. (Makes this class select()able.)
|
||||
"""
|
||||
return self.conn.fileno()
|
||||
|
||||
def is_done(self):
|
||||
""" Check if the connection is available for a new query.
|
||||
|
||||
Also checks if the previous query has run into a deadlock.
|
||||
If so, then the previous query is repeated.
|
||||
"""
|
||||
if self.current_query is None:
|
||||
return True
|
||||
|
||||
try:
|
||||
if self.conn.poll() == psycopg2.extensions.POLL_OK:
|
||||
self.current_query = None
|
||||
return True
|
||||
except psycopg2.extensions.TransactionRollbackError as e:
|
||||
if e.pgcode == '40P01':
|
||||
log.info("Deadlock detected (params = {}), retry.".format(self.current_params))
|
||||
self.cursor.execute(self.current_query, self.current_params)
|
||||
else:
|
||||
raise
|
||||
except psycopg2.errors.DeadlockDetected:
|
||||
self.cursor.execute(self.current_query, self.current_params)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class Indexer(object):
|
||||
""" Main indexing routine.
|
||||
"""
|
||||
|
||||
def __init__(self, options):
|
||||
self.minrank = max(0, options.minrank)
|
||||
self.maxrank = min(30, options.maxrank)
|
||||
self.conn = make_connection(options)
|
||||
self.threads = [DBConnection(options) for i in range(options.threads)]
|
||||
|
||||
def run(self):
|
||||
""" Run indexing over the entire database.
|
||||
"""
|
||||
log.warning("Starting indexing rank ({} to {}) using {} threads".format(
|
||||
self.minrank, self.maxrank, len(self.threads)))
|
||||
|
||||
for rank in range(self.minrank, self.maxrank):
|
||||
self.index(RankRunner(rank))
|
||||
|
||||
if self.maxrank == 30:
|
||||
self.index(InterpolationRunner())
|
||||
|
||||
self.index(RankRunner(self.maxrank))
|
||||
|
||||
def index(self, obj):
|
||||
""" Index a single rank or table. `obj` describes the SQL to use
|
||||
for indexing.
|
||||
"""
|
||||
log.warning("Starting {}".format(obj.name()))
|
||||
|
||||
cur = self.conn.cursor(name='main')
|
||||
cur.execute(obj.sql_index_sectors())
|
||||
|
||||
total_tuples = 0
|
||||
for r in cur:
|
||||
total_tuples += r[1]
|
||||
log.debug("Total number of rows; {}".format(total_tuples))
|
||||
|
||||
cur.scroll(0, mode='absolute')
|
||||
|
||||
next_thread = self.find_free_thread()
|
||||
done_tuples = 0
|
||||
rank_start_time = datetime.now()
|
||||
|
||||
sector_sql = obj.sql_sector_places()
|
||||
index_sql = obj.sql_index_place()
|
||||
min_grouped_tuples = total_tuples - len(self.threads) * 1000
|
||||
|
||||
next_info = 100 if log.isEnabledFor(logging.INFO) else total_tuples + 1
|
||||
|
||||
for r in cur:
|
||||
sector = r[0]
|
||||
|
||||
# Should we do the remaining ones together?
|
||||
do_all = done_tuples > min_grouped_tuples
|
||||
|
||||
pcur = self.conn.cursor(name='places')
|
||||
|
||||
if do_all:
|
||||
pcur.execute(obj.sql_nosector_places())
|
||||
else:
|
||||
pcur.execute(sector_sql, (sector, ))
|
||||
|
||||
for place in pcur:
|
||||
place_id = place[0]
|
||||
log.debug("Processing place {}".format(place_id))
|
||||
thread = next(next_thread)
|
||||
|
||||
thread.perform(index_sql, (place_id,))
|
||||
done_tuples += 1
|
||||
|
||||
if done_tuples >= next_info:
|
||||
now = datetime.now()
|
||||
done_time = (now - rank_start_time).total_seconds()
|
||||
tuples_per_sec = done_tuples / done_time
|
||||
log.info("Done {} in {} @ {:.3f} per second - {} ETA (seconds): {:.2f}"
|
||||
.format(done_tuples, int(done_time),
|
||||
tuples_per_sec, obj.name(),
|
||||
(total_tuples - done_tuples)/tuples_per_sec))
|
||||
next_info += int(tuples_per_sec)
|
||||
|
||||
pcur.close()
|
||||
|
||||
if do_all:
|
||||
break
|
||||
|
||||
cur.close()
|
||||
|
||||
for t in self.threads:
|
||||
t.wait()
|
||||
|
||||
rank_end_time = datetime.now()
|
||||
diff_seconds = (rank_end_time-rank_start_time).total_seconds()
|
||||
|
||||
log.warning("Done {}/{} in {} @ {:.3f} per second - FINISHED {}\n".format(
|
||||
done_tuples, total_tuples, int(diff_seconds),
|
||||
done_tuples/diff_seconds, obj.name()))
|
||||
|
||||
def find_free_thread(self):
|
||||
""" Generator that returns the next connection that is free for
|
||||
sending a query.
|
||||
"""
|
||||
ready = self.threads
|
||||
command_stat = 0
|
||||
|
||||
while True:
|
||||
for thread in ready:
|
||||
if thread.is_done():
|
||||
command_stat += 1
|
||||
yield thread
|
||||
|
||||
# refresh the connections occasionaly to avoid potential
|
||||
# memory leaks in Postgresql.
|
||||
if command_stat > 100000:
|
||||
for t in self.threads:
|
||||
while not t.is_done():
|
||||
t.wait()
|
||||
t.connect()
|
||||
command_stat = 0
|
||||
ready = self.threads
|
||||
else:
|
||||
ready, _, _ = select.select(self.threads, [], [])
|
||||
|
||||
assert False, "Unreachable code"
|
||||
|
||||
|
||||
def nominatim_arg_parser():
|
||||
""" Setup the command-line parser for the tool.
|
||||
"""
|
||||
def h(s):
|
||||
return re.sub("\s\s+" , " ", s)
|
||||
|
||||
p = ArgumentParser(description="Indexing tool for Nominatim.",
|
||||
formatter_class=RawDescriptionHelpFormatter)
|
||||
|
||||
p.add_argument('-d', '--database',
|
||||
dest='dbname', action='store', default='nominatim',
|
||||
help='Name of the PostgreSQL database to connect to.')
|
||||
p.add_argument('-U', '--username',
|
||||
dest='user', action='store',
|
||||
help='PostgreSQL user name.')
|
||||
p.add_argument('-W', '--password',
|
||||
dest='password_prompt', action='store_true',
|
||||
help='Force password prompt.')
|
||||
p.add_argument('-H', '--host',
|
||||
dest='host', action='store',
|
||||
help='PostgreSQL server hostname or socket location.')
|
||||
p.add_argument('-P', '--port',
|
||||
dest='port', action='store',
|
||||
help='PostgreSQL server port')
|
||||
p.add_argument('-r', '--minrank',
|
||||
dest='minrank', type=int, metavar='RANK', default=0,
|
||||
help='Minimum/starting rank.')
|
||||
p.add_argument('-R', '--maxrank',
|
||||
dest='maxrank', type=int, metavar='RANK', default=30,
|
||||
help='Maximum/finishing rank.')
|
||||
p.add_argument('-t', '--threads',
|
||||
dest='threads', type=int, metavar='NUM', default=1,
|
||||
help='Number of threads to create for indexing.')
|
||||
p.add_argument('-v', '--verbose',
|
||||
dest='loglevel', action='count', default=0,
|
||||
help='Increase verbosity')
|
||||
|
||||
return p
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(stream=sys.stderr, format='%(levelname)s: %(message)s')
|
||||
|
||||
options = nominatim_arg_parser().parse_args(sys.argv[1:])
|
||||
|
||||
log.setLevel(max(3 - options.loglevel, 0) * 10)
|
||||
|
||||
options.password = None
|
||||
if options.password_prompt:
|
||||
password = getpass.getpass("Database password: ")
|
||||
options.password = password
|
||||
|
||||
Indexer(options).run()
|
||||
@@ -1,55 +0,0 @@
|
||||
|
||||
%define svn @SVN@
|
||||
|
||||
Summary: Nominatim OpenStreetMap geocoding database
|
||||
Name: @PACKAGE@
|
||||
Group: Applications/Text
|
||||
Version: @VERSION@
|
||||
Release: 1.%{svn}%{?dist}
|
||||
|
||||
License: GPL
|
||||
URL: http://svn.openstreetmap.org/applications/utils/nominatim
|
||||
Source0: %{name}-%{version}-%{svn}.tar.bz2
|
||||
Source1: nominatim-svn.sh
|
||||
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
|
||||
|
||||
BuildRequires: geos-devel
|
||||
BuildRequires: libxml2-devel
|
||||
BuildRequires: postgresql-devel
|
||||
BuildRequires: bzip2-devel
|
||||
BuildRequires: proj-devel
|
||||
|
||||
%description
|
||||
Processes data imported using osm2pgsql from the communtiy mapping project
|
||||
at http://www.openstreetmap.org.
|
||||
|
||||
%prep
|
||||
%setup -q -n %{name}
|
||||
|
||||
|
||||
%build
|
||||
|
||||
export CFLAGS="$RPM_OPT_FLAGS"
|
||||
export CXXFLAGS="$RPM_OPT_FLAGS"
|
||||
|
||||
make all
|
||||
|
||||
|
||||
%install
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
install -D -p nominatim $RPM_BUILD_ROOT/usr/bin/nominatim
|
||||
|
||||
|
||||
%clean
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
|
||||
|
||||
%files
|
||||
%defattr(-,root,root)
|
||||
%doc README.txt
|
||||
%{_bindir}/nominatim
|
||||
|
||||
|
||||
%changelog
|
||||
* Fri Sep 09 2010 Brian Quinion <nominatim@brian.quinion.co.uk> 0.1-1.20070316svn
|
||||
- Initial build
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "postgresql.h"
|
||||
|
||||
const char *build_conninfo(const char *db, const char *username, const char *password, const char *host, const char *port)
|
||||
{
|
||||
static char conninfo[1024];
|
||||
|
||||
conninfo[0]='\0';
|
||||
strcat(conninfo, "dbname='");
|
||||
strcat(conninfo, db);
|
||||
strcat(conninfo, "'");
|
||||
|
||||
if (username)
|
||||
{
|
||||
strcat(conninfo, " user='");
|
||||
strcat(conninfo, username);
|
||||
strcat(conninfo, "'");
|
||||
}
|
||||
if (password)
|
||||
{
|
||||
strcat(conninfo, " password='");
|
||||
strcat(conninfo, password);
|
||||
strcat(conninfo, "'");
|
||||
}
|
||||
if (host)
|
||||
{
|
||||
strcat(conninfo, " host='");
|
||||
strcat(conninfo, host);
|
||||
strcat(conninfo, "'");
|
||||
}
|
||||
if (port)
|
||||
{
|
||||
strcat(conninfo, " port='");
|
||||
strcat(conninfo, port);
|
||||
strcat(conninfo, "'");
|
||||
}
|
||||
|
||||
return conninfo;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
*/
|
||||
|
||||
#ifndef POSTGRESQL_H
|
||||
#define POSTGRESQL_H
|
||||
|
||||
#define PG_OID_INT8 20
|
||||
#define PG_OID_INT4 23
|
||||
|
||||
#if HAVE_BYTESWAP
|
||||
#include <byteswap.h>
|
||||
#define PG_BSWAP32(x) bswap_32(x)
|
||||
#define PG_BSWAP64(x) bswap_64(x)
|
||||
#elif HAVE_SYS_ENDIAN
|
||||
#include <sys/endian.h>
|
||||
#define PG_BSWAP32(x) bswap32(x)
|
||||
#define PG_BSWAP64(x) bswap64(x)
|
||||
#else
|
||||
#error "No appropriate byteswap found for your system."
|
||||
#endif
|
||||
|
||||
#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
|
||||
#define PGint32(x) (x)
|
||||
#define PGint64(x) (x)
|
||||
#elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||
#define PGint32(x) PG_BSWAP32(x)
|
||||
#define PGint64(x) PG_BSWAP64(x)
|
||||
#elif defined(_BYTE_ORDER) && (_BYTE_ORDER == _BIG_ENDIAN)
|
||||
#define PGint32(x) (x)
|
||||
#define PGint64(x) (x)
|
||||
#elif defined(_BYTE_ORDER) && (_BYTE_ORDER == _LITTLE_ENDIAN)
|
||||
#define PGint32(x) PG_BSWAP32(x)
|
||||
#define PGint64(x) PG_BSWAP64(x)
|
||||
#else
|
||||
#error "Cannot determine byte order."
|
||||
#endif
|
||||
|
||||
const char *build_conninfo(const char *db, const char *username, const char *password, const char *host, const char *port);
|
||||
|
||||
#endif
|
||||
@@ -1,200 +0,0 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* sprompt.c
|
||||
* simple_prompt() routine
|
||||
*
|
||||
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/port/sprompt.c,v 1.18 2006/10/04 00:30:14 momjian Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*
|
||||
* PostgreSQL Database Management System
|
||||
* (formerly known as Postgres, then as Postgres95)
|
||||
*
|
||||
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
|
||||
*
|
||||
* Portions Copyright (c) 1994, The Regents of the University of California
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software and its
|
||||
* documentation for any purpose, without fee, and without a written agreement
|
||||
* is hereby granted, provided that the above copyright notice and this
|
||||
* paragraph and the following two paragraphs appear in all copies.
|
||||
*
|
||||
* IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
|
||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
|
||||
* LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
|
||||
* DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
* ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO
|
||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* simple_prompt
|
||||
*
|
||||
* Generalized function especially intended for reading in usernames and
|
||||
* password interactively. Reads from /dev/tty or stdin/stderr.
|
||||
*
|
||||
* prompt: The prompt to print
|
||||
* maxlen: How many characters to accept
|
||||
* echo: Set to false if you want to hide what is entered (for passwords)
|
||||
*
|
||||
* Returns a malloc()'ed string with the input (w/o trailing newline).
|
||||
*/
|
||||
|
||||
#define DEVTTY "/dev/tty"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libpq-fe.h>
|
||||
|
||||
#ifdef __MINGW_H
|
||||
# include <windows.h>
|
||||
#else
|
||||
# define HAVE_TERMIOS_H
|
||||
# include <termios.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
extern char *simple_prompt(const char *prompt, int maxlen, int echo);
|
||||
*/
|
||||
|
||||
char *
|
||||
simple_prompt(const char *prompt, int maxlen, int echo)
|
||||
{
|
||||
int length;
|
||||
char *destination;
|
||||
FILE *termin,
|
||||
*termout;
|
||||
|
||||
#ifdef HAVE_TERMIOS_H
|
||||
struct termios t_orig,
|
||||
t;
|
||||
#else
|
||||
#ifdef WIN32
|
||||
HANDLE t = NULL;
|
||||
LPDWORD t_orig = NULL;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
destination = (char *) malloc(maxlen + 1);
|
||||
if (!destination)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Do not try to collapse these into one "w+" mode file. Doesn't work on
|
||||
* some platforms (eg, HPUX 10.20).
|
||||
*/
|
||||
termin = fopen(DEVTTY, "r");
|
||||
termout = fopen(DEVTTY, "w");
|
||||
if (!termin || !termout
|
||||
#ifdef WIN32
|
||||
/* See DEVTTY comment for msys */
|
||||
|| (getenv("OSTYPE") && strcmp(getenv("OSTYPE"), "msys") == 0)
|
||||
#endif
|
||||
)
|
||||
{
|
||||
if (termin)
|
||||
fclose(termin);
|
||||
if (termout)
|
||||
fclose(termout);
|
||||
termin = stdin;
|
||||
termout = stderr;
|
||||
}
|
||||
|
||||
#ifdef HAVE_TERMIOS_H
|
||||
if (!echo)
|
||||
{
|
||||
tcgetattr(fileno(termin), &t);
|
||||
t_orig = t;
|
||||
t.c_lflag &= ~ECHO;
|
||||
tcsetattr(fileno(termin), TCSAFLUSH, &t);
|
||||
}
|
||||
#else
|
||||
#ifdef WIN32
|
||||
if (!echo)
|
||||
{
|
||||
/* get a new handle to turn echo off */
|
||||
t_orig = (LPDWORD) malloc(sizeof(DWORD));
|
||||
t = GetStdHandle(STD_INPUT_HANDLE);
|
||||
|
||||
/* save the old configuration first */
|
||||
GetConsoleMode(t, t_orig);
|
||||
|
||||
/* set to the new mode */
|
||||
SetConsoleMode(t, ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (prompt)
|
||||
{
|
||||
fputs(prompt, termout);
|
||||
fflush(termout);
|
||||
}
|
||||
|
||||
if (fgets(destination, maxlen + 1, termin) == NULL)
|
||||
destination[0] = '\0';
|
||||
|
||||
length = strlen(destination);
|
||||
if (length > 0 && destination[length - 1] != '\n')
|
||||
{
|
||||
/* eat rest of the line */
|
||||
char buf[128];
|
||||
int buflen;
|
||||
|
||||
do
|
||||
{
|
||||
if (fgets(buf, sizeof(buf), termin) == NULL)
|
||||
break;
|
||||
buflen = strlen(buf);
|
||||
}
|
||||
while (buflen > 0 && buf[buflen - 1] != '\n');
|
||||
}
|
||||
|
||||
if (length > 0 && destination[length - 1] == '\n')
|
||||
/* remove trailing newline */
|
||||
destination[length - 1] = '\0';
|
||||
|
||||
#ifdef HAVE_TERMIOS_H
|
||||
if (!echo)
|
||||
{
|
||||
tcsetattr(fileno(termin), TCSAFLUSH, &t_orig);
|
||||
fputs("\n", termout);
|
||||
fflush(termout);
|
||||
}
|
||||
#else
|
||||
#ifdef WIN32
|
||||
if (!echo)
|
||||
{
|
||||
/* reset to the original console mode */
|
||||
SetConsoleMode(t, *t_orig);
|
||||
fputs("\n", termout);
|
||||
fflush(termout);
|
||||
free(t_orig);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (termin != stdin)
|
||||
{
|
||||
fclose(termin);
|
||||
fclose(termout);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
#ifndef SPROMPT_H
|
||||
#define SPROMPT_H
|
||||
char *simple_prompt(const char *prompt, int maxlen, int echo);
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,281 +0,0 @@
|
||||
/*
|
||||
* The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T
|
||||
* Bell Laboratories.
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code was originally written by Stephan Fortune in C code. I, Shane O'Sullivan,
|
||||
* have since modified it, encapsulating it in a C++ class and, fixing memory leaks and
|
||||
* adding accessors to the Voronoi Edges.
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
#ifndef VORONOI_DIAGRAM_GENERATOR
|
||||
#define VORONOI_DIAGRAM_GENERATOR
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
#ifndef NULL
|
||||
#define NULL 0
|
||||
#endif
|
||||
#define DELETED -2
|
||||
|
||||
#define le 0
|
||||
#define re 1
|
||||
|
||||
struct SourcePoint
|
||||
{
|
||||
int id;
|
||||
double weight;
|
||||
double x;
|
||||
double y;
|
||||
};
|
||||
|
||||
struct Freenode
|
||||
{
|
||||
struct Freenode *nextfree;
|
||||
};
|
||||
|
||||
struct FreeNodeArrayList
|
||||
{
|
||||
struct Freenode* memory;
|
||||
struct FreeNodeArrayList* next;
|
||||
|
||||
};
|
||||
|
||||
struct Freelist
|
||||
{
|
||||
struct Freenode *head;
|
||||
int nodesize;
|
||||
};
|
||||
|
||||
struct Point
|
||||
{
|
||||
float x,y;
|
||||
};
|
||||
|
||||
struct PolygonPoint
|
||||
{
|
||||
struct Point coord;
|
||||
double angle;
|
||||
int boundary;
|
||||
};
|
||||
|
||||
struct Polygon
|
||||
{
|
||||
int sitenbr;
|
||||
struct Point coord;
|
||||
int numpoints;
|
||||
struct PolygonPoint * pointlist;
|
||||
int boundary;
|
||||
};
|
||||
|
||||
|
||||
// structure used both for sites and for vertices
|
||||
struct Site
|
||||
{
|
||||
struct Point coord;
|
||||
struct Point coordout;
|
||||
double weight;
|
||||
int sitenbr;
|
||||
int refcnt;
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct Edge
|
||||
{
|
||||
float a,b,c;
|
||||
struct Site *ep[2];
|
||||
struct Site *reg[2];
|
||||
int edgenbr;
|
||||
|
||||
};
|
||||
|
||||
struct GraphEdge
|
||||
{
|
||||
float x1,y1,x2,y2;
|
||||
struct GraphEdge* next;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
struct Halfedge
|
||||
{
|
||||
struct Halfedge *ELleft, *ELright;
|
||||
struct Edge *ELedge;
|
||||
int ELrefcnt;
|
||||
char ELpm;
|
||||
struct Site *vertex;
|
||||
float ystar;
|
||||
struct Halfedge *PQnext;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
class VoronoiDiagramGenerator
|
||||
{
|
||||
public:
|
||||
VoronoiDiagramGenerator();
|
||||
~VoronoiDiagramGenerator();
|
||||
|
||||
bool generateVoronoi(struct SourcePoint* srcPoints, int numPoints, float minX, float maxX, float minY, float maxY, float minDist=0);
|
||||
void getSitePoints(int sitenbr, int* numpoints, PolygonPoint** pS);
|
||||
|
||||
void resetIterator()
|
||||
{
|
||||
iteratorEdges = allEdges;
|
||||
}
|
||||
|
||||
bool getNext(float& x1, float& y1, float& x2, float& y2)
|
||||
{
|
||||
if(iteratorEdges == 0)
|
||||
return false;
|
||||
|
||||
x1 = iteratorEdges->x1;
|
||||
x2 = iteratorEdges->x2;
|
||||
y1 = iteratorEdges->y1;
|
||||
y2 = iteratorEdges->y2;
|
||||
|
||||
iteratorEdges = iteratorEdges->next;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
void cleanup();
|
||||
void cleanupEdges();
|
||||
char *getfree(struct Freelist *fl);
|
||||
struct Halfedge *PQfind();
|
||||
int PQempty();
|
||||
|
||||
|
||||
|
||||
struct Halfedge **ELhash;
|
||||
struct Halfedge *HEcreate(), *ELleft(), *ELright(), *ELleftbnd();
|
||||
struct Halfedge *HEcreate(struct Edge *e,int pm);
|
||||
|
||||
|
||||
struct Point PQ_min();
|
||||
struct Halfedge *PQextractmin();
|
||||
void freeinit(struct Freelist *fl,int size);
|
||||
void makefree(struct Freenode *curr,struct Freelist *fl);
|
||||
void geominit();
|
||||
void plotinit();
|
||||
bool voronoi(int triangulate);
|
||||
void ref(struct Site *v);
|
||||
void deref(struct Site *v);
|
||||
void endpoint(struct Edge *e,int lr,struct Site * s);
|
||||
void endpoint(struct Edge *e1,int lr,struct Site * s, struct Edge *e2, struct Edge *e3);
|
||||
|
||||
void ELdelete(struct Halfedge *he);
|
||||
struct Halfedge *ELleftbnd(struct Point *p);
|
||||
struct Halfedge *ELright(struct Halfedge *he);
|
||||
void makevertex(struct Site *v);
|
||||
void out_triple(struct Site *s1, struct Site *s2,struct Site * s3);
|
||||
|
||||
void PQinsert(struct Halfedge *he,struct Site * v, float offset);
|
||||
void PQdelete(struct Halfedge *he);
|
||||
bool ELinitialize();
|
||||
void ELinsert(struct Halfedge *lb, struct Halfedge *newHe);
|
||||
struct Halfedge * ELgethash(int b);
|
||||
struct Halfedge *ELleft(struct Halfedge *he);
|
||||
struct Site *leftreg(struct Halfedge *he);
|
||||
void out_site(struct Site *s);
|
||||
bool PQinitialize();
|
||||
int PQbucket(struct Halfedge *he);
|
||||
void pushpoint(int sitenbr, double x, double y, int boundary);
|
||||
int ccw( Point p0, Point p1, Point p2 );
|
||||
void clip_line(struct Edge *e);
|
||||
char *myalloc(unsigned n);
|
||||
int right_of(struct Halfedge *el,struct Point *p);
|
||||
|
||||
struct Site *rightreg(struct Halfedge *he);
|
||||
struct Edge *bisect(struct Site *s1,struct Site *s2);
|
||||
float dist(struct Site *s,struct Site *t);
|
||||
struct Site *intersect(struct Halfedge *el1, struct Halfedge *el2, struct Point *p=0);
|
||||
|
||||
void out_bisector(struct Edge *e);
|
||||
void out_ep(struct Edge *e);
|
||||
void out_vertex(struct Site *v);
|
||||
struct Site *nextone();
|
||||
|
||||
void pushGraphEdge(float x1, float y1, float x2, float y2);
|
||||
|
||||
void openpl();
|
||||
void line(float x1, float y1, float x2, float y2);
|
||||
void circle(float x, float y, float radius);
|
||||
void range(float minX, float minY, float maxX, float maxY);
|
||||
|
||||
|
||||
struct Freelist hfl;
|
||||
struct Halfedge *ELleftend, *ELrightend;
|
||||
int ELhashsize;
|
||||
|
||||
int triangulate, sorted, plot, debug;
|
||||
float xmin, xmax, ymin, ymax, deltax, deltay;
|
||||
|
||||
struct Site *sites;
|
||||
struct Polygon *polygons;
|
||||
struct Point corners[4];
|
||||
int nsites;
|
||||
int siteidx;
|
||||
int sqrt_nsites;
|
||||
int nvertices;
|
||||
struct Freelist sfl;
|
||||
struct Site *bottomsite;
|
||||
|
||||
int nedges;
|
||||
struct Freelist efl;
|
||||
int PQhashsize;
|
||||
struct Halfedge *PQhash;
|
||||
int PQcount;
|
||||
int PQmin;
|
||||
|
||||
int ntry, totalsearch;
|
||||
float pxmin, pxmax, pymin, pymax, cradius;
|
||||
int total_alloc;
|
||||
|
||||
float borderMinX, borderMaxX, borderMinY, borderMaxY;
|
||||
|
||||
FreeNodeArrayList* allMemoryList;
|
||||
FreeNodeArrayList* currentMemoryBlock;
|
||||
|
||||
GraphEdge* allEdges;
|
||||
GraphEdge* iteratorEdges;
|
||||
|
||||
float minDistanceBetweenSites;
|
||||
|
||||
};
|
||||
|
||||
int scomp(const void *p1,const void *p2);
|
||||
int spcomp(const void *p1,const void *p2);
|
||||
int anglecomp(const void * p1, const void * p2);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* The author of this software is Shane O'Sullivan.
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
|
||||
#
|
||||
#include <stdio.h>
|
||||
#include <search.h>
|
||||
#include <malloc.h>
|
||||
#include "VoronoiDiagramGenerator.h"
|
||||
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
double xmin, xmax, ymin, ymax;
|
||||
scanf("%lf %lf %lf %lf", &xmin, &xmax, &ymin, &ymax) ;
|
||||
|
||||
SourcePoint * sites;
|
||||
long nsites;
|
||||
|
||||
nsites = 0;
|
||||
sites = (SourcePoint *) malloc(4000 * sizeof(SourcePoint));
|
||||
while (scanf("%d %lf %lf %lf", &sites[nsites].id, &sites[nsites].weight, &sites[nsites].x, &sites[nsites].y) != EOF)
|
||||
{
|
||||
nsites++;
|
||||
if (nsites % 4000 == 0) {
|
||||
sites = (SourcePoint *)realloc(sites,(nsites+4000)*sizeof(SourcePoint));
|
||||
}
|
||||
}
|
||||
|
||||
VoronoiDiagramGenerator * pvdg;
|
||||
pvdg = new VoronoiDiagramGenerator();
|
||||
pvdg->generateVoronoi(sites, nsites, xmin, xmax, ymin, ymax, 0);
|
||||
|
||||
// printf("sites %ld\n-------------------------------\n", nsites);
|
||||
PolygonPoint* pSitePoints;
|
||||
int numpoints, i, j;
|
||||
for(i = 0; i < nsites; i++)
|
||||
{
|
||||
pvdg->getSitePoints(i, &numpoints, &pSitePoints);
|
||||
if (numpoints == 0)
|
||||
{
|
||||
printf("-- no points for %d\n", i);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
|
||||
printf("update temp_child_4076440_0 set resultgeom = st_setsrid('POLYGON((");
|
||||
for(j = 0; j < numpoints; j++)
|
||||
{
|
||||
printf("%.15lf %.15lf,", pSitePoints[j].coord.x, pSitePoints[j].coord.y, (pSitePoints[j].angle/M_PI)*180);
|
||||
}
|
||||
printf("%.15lf %.15lf", pSitePoints[0].coord.x, pSitePoints[0].coord.y, (pSitePoints[j].angle/M_PI)*180);
|
||||
printf("))'::geometry,4326) where id = %d;\n", sites[i].id);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
float x1,y1,x2,y2;
|
||||
// printf("sites %ld\n-------------------------------\n", nsites);
|
||||
pvdg->resetIterator();
|
||||
while(pvdg->getNext(x1,y1,x2,y2))
|
||||
{
|
||||
printf("(%f %f,%f %f)\n",x1,y1,x2, y2);
|
||||
|
||||
}
|
||||
|
||||
delete pvdg;
|
||||
free(sites);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Submodule osm2pgsql updated: 5f3f736348...2909ff20ef
@@ -5,21 +5,22 @@
|
||||
"continent" : [2, 0],
|
||||
"country" : [4, 0],
|
||||
"state" : [8, 0],
|
||||
"province" : [8, 0],
|
||||
"region" : [18, 0],
|
||||
"county" : 12,
|
||||
"municipality" : [17, 14],
|
||||
"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],
|
||||
"borough" : [19, 18],
|
||||
"hamlet" : 20,
|
||||
"suburb" : 20,
|
||||
"croft" : 20,
|
||||
"subdivision" : 20,
|
||||
"isolated_dwelling" : 20,
|
||||
"allotments" : 20,
|
||||
"farm" : [20, 0],
|
||||
"locality" : [20, 0],
|
||||
"islet" : [20, 0],
|
||||
@@ -62,7 +63,11 @@
|
||||
"sea" : [4, 0]
|
||||
},
|
||||
"waterway" : {
|
||||
"" : [17, 0]
|
||||
"river" : [19, 0],
|
||||
"stream" : [22, 0],
|
||||
"ditch" : [22, 0],
|
||||
"drain" : [22, 0],
|
||||
"" : [20, 0]
|
||||
},
|
||||
"highway" : {
|
||||
"" : 26,
|
||||
@@ -89,6 +94,7 @@
|
||||
{ "countries" : [ "de" ],
|
||||
"tags" : {
|
||||
"place" : {
|
||||
"region" : [10, 0],
|
||||
"county" : [12, 0]
|
||||
},
|
||||
"boundary" : {
|
||||
@@ -102,6 +108,16 @@
|
||||
"administrative7" : [14, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "countries" : ["se", "no"],
|
||||
"tags" : {
|
||||
"place" : {
|
||||
},
|
||||
"boundary" : {
|
||||
"administrative3" : 8,
|
||||
"administrative4" : 12
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
},
|
||||
{
|
||||
"keys" : ["country_code", "ISO3166-1", "is_in:country_code", "is_in:country",
|
||||
"addr:country", "addr:country", "addr:country_code"],
|
||||
"addr:country", "addr:country_code"],
|
||||
"values" : {
|
||||
"" : "country"
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
},
|
||||
{
|
||||
"keys" : ["country_code", "ISO3166-1", "is_in:country_code", "is_in:country",
|
||||
"addr:country", "addr:country", "addr:country_code"],
|
||||
"addr:country", "addr:country_code"],
|
||||
"values" : {
|
||||
"" : "country"
|
||||
}
|
||||
|
||||
238
settings/import-extratags.style
Normal file
238
settings/import-extratags.style
Normal file
@@ -0,0 +1,238 @@
|
||||
[
|
||||
{
|
||||
"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",
|
||||
"rail" : "extra",
|
||||
"" : "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_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" : ["note", "note:*", "source", "source*", "attribution",
|
||||
"comment", "fixme", "FIXME", "created_by", "tiger:*", "NHD:*",
|
||||
"nhd:*", "gnis:*", "geobase:*", "KSJ2:*", "yh:*",
|
||||
"osak:*", "naptan:*", "CLC:*", "import", "it:fvg:*",
|
||||
"type", "lacounty:*", "ref:ruian:*", "building:ruian:type",
|
||||
"ref:linz:*"],
|
||||
"values" : {
|
||||
"" : "skip"
|
||||
}
|
||||
},
|
||||
{
|
||||
"keys" : [""],
|
||||
"values" : {
|
||||
"" : "extra"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -98,6 +98,7 @@
|
||||
"values" : {
|
||||
"level_crossing" : "skip",
|
||||
"no" : "skip",
|
||||
"rail" : "skip",
|
||||
"" : "main,with_name"
|
||||
}
|
||||
},
|
||||
@@ -187,7 +188,7 @@
|
||||
},
|
||||
{
|
||||
"keys" : ["country_code", "ISO3166-1", "is_in:country_code", "is_in:country",
|
||||
"addr:country", "addr:country", "addr:country_code"],
|
||||
"addr:country", "addr:country_code"],
|
||||
"values" : {
|
||||
"" : "country"
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
},
|
||||
{
|
||||
"keys" : ["country_code", "ISO3166-1", "is_in:country_code", "is_in:country",
|
||||
"addr:country", "addr:country", "addr:country_code"],
|
||||
"addr:country", "addr:country_code"],
|
||||
"values" : {
|
||||
"" : "country"
|
||||
}
|
||||
|
||||
2860
sql/functions.sql
2860
sql/functions.sql
File diff suppressed because it is too large
Load Diff
289
sql/functions/address_lookup.sql
Normal file
289
sql/functions/address_lookup.sql
Normal file
@@ -0,0 +1,289 @@
|
||||
-- Functions for returning address information for a place.
|
||||
|
||||
DROP TYPE IF EXISTS addressline CASCADE;
|
||||
CREATE TYPE addressline as (
|
||||
place_id BIGINT,
|
||||
osm_type CHAR(1),
|
||||
osm_id BIGINT,
|
||||
name HSTORE,
|
||||
class TEXT,
|
||||
type TEXT,
|
||||
place_type TEXT,
|
||||
admin_level INTEGER,
|
||||
fromarea BOOLEAN,
|
||||
isaddress BOOLEAN,
|
||||
rank_address INTEGER,
|
||||
distance FLOAT
|
||||
);
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION get_name_by_language(name hstore, languagepref TEXT[])
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
result TEXT;
|
||||
BEGIN
|
||||
IF name is null THEN
|
||||
RETURN null;
|
||||
END IF;
|
||||
|
||||
FOR j IN 1..array_upper(languagepref,1) LOOP
|
||||
IF name ? languagepref[j] THEN
|
||||
result := trim(name->languagepref[j]);
|
||||
IF result != '' THEN
|
||||
return result;
|
||||
END IF;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- anything will do as a fallback - just take the first name type thing there is
|
||||
RETURN trim((avals(name))[1]);
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
|
||||
--housenumber only needed for tiger data
|
||||
CREATE OR REPLACE FUNCTION get_address_by_language(for_place_id BIGINT,
|
||||
housenumber INTEGER,
|
||||
languagepref TEXT[])
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
result TEXT[];
|
||||
currresult TEXT;
|
||||
prevresult TEXT;
|
||||
location RECORD;
|
||||
BEGIN
|
||||
|
||||
result := '{}';
|
||||
prevresult := '';
|
||||
|
||||
FOR location IN
|
||||
SELECT name,
|
||||
CASE WHEN place_id = for_place_id THEN 99 ELSE rank_address END as rank_address
|
||||
FROM get_addressdata(for_place_id, housenumber)
|
||||
WHERE isaddress order by rank_address desc
|
||||
LOOP
|
||||
currresult := trim(get_name_by_language(location.name, languagepref));
|
||||
IF currresult != prevresult AND currresult IS NOT NULL
|
||||
AND result[(100 - location.rank_address)] IS NULL
|
||||
THEN
|
||||
result[(100 - location.rank_address)] := currresult;
|
||||
prevresult := currresult;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
RETURN array_to_string(result,', ');
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql STABLE;
|
||||
|
||||
|
||||
-- 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
|
||||
AS $$
|
||||
DECLARE
|
||||
for_place_id BIGINT;
|
||||
result TEXT[];
|
||||
search TEXT[];
|
||||
found INTEGER;
|
||||
location RECORD;
|
||||
countrylocation RECORD;
|
||||
searchcountrycode varchar(2);
|
||||
searchhousenumber TEXT;
|
||||
searchhousename HSTORE;
|
||||
searchrankaddress INTEGER;
|
||||
searchpostcode TEXT;
|
||||
postcode_isexact BOOL;
|
||||
searchclass TEXT;
|
||||
searchtype TEXT;
|
||||
countryname HSTORE;
|
||||
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_isexact := false;
|
||||
|
||||
-- first query osmline (interpolation lines)
|
||||
IF in_housenumber >= 0 THEN
|
||||
SELECT parent_place_id, country_code, in_housenumber::text, 30, postcode,
|
||||
null, 'place', 'house'
|
||||
FROM location_property_osmline
|
||||
WHERE place_id = in_place_id AND in_housenumber>=startnumber
|
||||
AND in_housenumber <= endnumber
|
||||
INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress,
|
||||
searchpostcode, searchhousename, searchclass, searchtype;
|
||||
END IF;
|
||||
|
||||
--then query tiger data
|
||||
-- %NOTIGERDATA% IF 0 THEN
|
||||
IF for_place_id IS NULL AND in_housenumber >= 0 THEN
|
||||
SELECT parent_place_id, 'us', in_housenumber::text, 30, postcode, null,
|
||||
'place', 'house'
|
||||
FROM location_property_tiger
|
||||
WHERE place_id = in_place_id AND in_housenumber >= startnumber
|
||||
AND in_housenumber <= endnumber
|
||||
INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress,
|
||||
searchpostcode, searchhousename, searchclass, searchtype;
|
||||
END IF;
|
||||
-- %NOTIGERDATA% END IF;
|
||||
|
||||
-- %NOAUXDATA% IF 0 THEN
|
||||
IF for_place_id IS NULL THEN
|
||||
SELECT parent_place_id, 'us', housenumber, 30, postcode, null, 'place', 'house'
|
||||
FROM location_property_aux
|
||||
WHERE place_id = in_place_id
|
||||
INTO for_place_id,searchcountrycode, searchhousenumber, searchrankaddress,
|
||||
searchpostcode, searchhousename, searchclass, searchtype;
|
||||
END IF;
|
||||
-- %NOAUXDATA% END IF;
|
||||
|
||||
-- postcode table
|
||||
IF for_place_id IS NULL THEN
|
||||
SELECT parent_place_id, country_code, rank_search, postcode, 'place', 'postcode'
|
||||
FROM location_postcode
|
||||
WHERE place_id = in_place_id
|
||||
INTO for_place_id, searchcountrycode, searchrankaddress, searchpostcode,
|
||||
searchclass, searchtype;
|
||||
END IF;
|
||||
|
||||
-- POI objects in the placex table
|
||||
IF for_place_id IS NULL THEN
|
||||
SELECT parent_place_id, country_code, housenumber, rank_search,
|
||||
postcode, address is not null and address ? 'postcode',
|
||||
name, class, type
|
||||
FROM placex
|
||||
WHERE place_id = in_place_id and rank_search > 27
|
||||
INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress,
|
||||
searchpostcode, postcode_isexact, searchhousename, searchclass, searchtype;
|
||||
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
|
||||
select coalesce(linked_place_id, place_id), country_code,
|
||||
housenumber, rank_search, postcode,
|
||||
address is not null and address ? 'postcode', null
|
||||
from placex where place_id = in_place_id
|
||||
INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress, searchpostcode, postcode_isexact, searchhousename;
|
||||
END IF;
|
||||
|
||||
--RAISE WARNING '% % % %',searchcountrycode, searchhousenumber, searchrankaddress, searchpostcode;
|
||||
|
||||
found := 1000; -- the lowest rank_address included
|
||||
|
||||
-- Return the record for the base entry.
|
||||
FOR location IN
|
||||
SELECT placex.place_id, osm_type, osm_id, name,
|
||||
class, type, admin_level,
|
||||
type not in ('postcode', 'postal_code') as isaddress,
|
||||
CASE WHEN rank_address = 0 THEN 100
|
||||
WHEN rank_address = 11 THEN 5
|
||||
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, NULL,
|
||||
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, class, type,
|
||||
coalesce(extratags->'linked_place', extratags->'place') as place_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
|
||||
--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')
|
||||
AND searchpostcode is not null
|
||||
THEN
|
||||
-- If the place had a postcode assigned, take this one only
|
||||
-- into consideration when it is an area and the place does not have
|
||||
-- a postcode itself.
|
||||
IF location.fromarea AND not postcode_isexact AND location.isaddress THEN
|
||||
searchpostcode := null; -- remove the less exact postcode
|
||||
ELSE
|
||||
location.isaddress := false;
|
||||
END IF;
|
||||
END IF;
|
||||
countrylocation := ROW(location.place_id, location.osm_type, location.osm_id,
|
||||
location.name, location.class, location.type,
|
||||
location.place_type,
|
||||
location.admin_level, location.fromarea,
|
||||
location.isaddress, location.rank_address,
|
||||
location.distance)::addressline;
|
||||
RETURN NEXT countrylocation;
|
||||
found := location.rank_address;
|
||||
END LOOP;
|
||||
|
||||
-- If no country was included yet, add the name information from country_name.
|
||||
IF found > 4 THEN
|
||||
SELECT name FROM country_name
|
||||
WHERE country_code = searchcountrycode LIMIT 1 INTO countryname;
|
||||
--RAISE WARNING '% % %',found,searchcountrycode,countryname;
|
||||
IF countryname IS NOT NULL THEN
|
||||
location := ROW(null, null, null, countryname, 'place', 'country', NULL,
|
||||
null, true, true, 4, 0)::addressline;
|
||||
RETURN NEXT location;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Finally add some artificial rows.
|
||||
IF searchcountrycode IS NOT NULL THEN
|
||||
location := ROW(null, null, null, hstore('ref', searchcountrycode),
|
||||
'place', 'country_code', null, null, true, false, 4, 0)::addressline;
|
||||
RETURN NEXT location;
|
||||
END IF;
|
||||
|
||||
IF searchhousename IS NOT NULL THEN
|
||||
location := ROW(in_place_id, null, null, searchhousename, searchclass,
|
||||
searchtype, null, null, true, true, 29, 0)::addressline;
|
||||
RETURN NEXT location;
|
||||
END IF;
|
||||
|
||||
IF searchhousenumber IS NOT NULL THEN
|
||||
location := ROW(in_place_id, null, null, hstore('ref', searchhousenumber),
|
||||
'place', 'house_number', null, null, true, true, 28, 0)::addressline;
|
||||
RETURN NEXT location;
|
||||
END IF;
|
||||
|
||||
IF searchpostcode IS NOT NULL THEN
|
||||
location := ROW(null, null, null, hstore('ref', searchpostcode), 'place',
|
||||
'postcode', null, null, false, true, 5, 0)::addressline;
|
||||
RETURN NEXT location;
|
||||
END IF;
|
||||
|
||||
RETURN;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql STABLE;
|
||||
53
sql/functions/aux_property.sql
Normal file
53
sql/functions/aux_property.sql
Normal file
@@ -0,0 +1,53 @@
|
||||
-- Functions for adding external data (currently unused).
|
||||
|
||||
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
|
||||
AS $$
|
||||
DECLARE
|
||||
|
||||
newpoints INTEGER;
|
||||
place_centroid GEOMETRY;
|
||||
out_partition INTEGER;
|
||||
out_parent_place_id BIGINT;
|
||||
location RECORD;
|
||||
address_street_word_ids INTEGER[];
|
||||
out_postcode TEXT;
|
||||
|
||||
BEGIN
|
||||
|
||||
place_centroid := ST_Centroid(pointgeo);
|
||||
out_partition := get_partition(in_countrycode);
|
||||
out_parent_place_id := null;
|
||||
|
||||
address_street_word_ids := word_ids_from_name(in_street);
|
||||
IF address_street_word_ids IS NOT NULL THEN
|
||||
out_parent_place_id := getNearestNamedRoadPlaceId(out_partition, place_centroid,
|
||||
address_street_word_ids);
|
||||
END IF;
|
||||
|
||||
IF out_parent_place_id IS NULL THEN
|
||||
SELECT getNearestRoadPlaceId(out_partition, place_centroid)
|
||||
INTO out_parent_place_id;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
out_postcode := in_postcode;
|
||||
IF out_postcode IS NULL THEN
|
||||
SELECT postcode from placex where place_id = out_parent_place_id INTO out_postcode;
|
||||
END IF;
|
||||
-- XXX look into postcode table
|
||||
|
||||
newpoints := 0;
|
||||
insert into location_property_aux (place_id, partition, parent_place_id,
|
||||
housenumber, postcode, centroid)
|
||||
values (nextval('seq_place'), out_partition, out_parent_place_id,
|
||||
in_housenumber, out_postcode, place_centroid);
|
||||
newpoints := newpoints + 1;
|
||||
|
||||
RETURN newpoints;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
125
sql/functions/importance.sql
Normal file
125
sql/functions/importance.sql
Normal file
@@ -0,0 +1,125 @@
|
||||
-- Functions for interpreting wkipedia/wikidata tags and computing importance.
|
||||
|
||||
DROP TYPE IF EXISTS wikipedia_article_match CASCADE;
|
||||
CREATE TYPE wikipedia_article_match as (
|
||||
language TEXT,
|
||||
title TEXT,
|
||||
importance FLOAT
|
||||
);
|
||||
|
||||
DROP TYPE IF EXISTS place_importance CASCADE;
|
||||
CREATE TYPE place_importance as (
|
||||
importance FLOAT,
|
||||
wikipedia TEXT
|
||||
);
|
||||
|
||||
|
||||
-- See: http://stackoverflow.com/questions/6410088/how-can-i-mimic-the-php-urldecode-function-in-postgresql
|
||||
CREATE OR REPLACE FUNCTION decode_url_part(p varchar)
|
||||
RETURNS varchar
|
||||
AS $$
|
||||
SELECT convert_from(CAST(E'\\x' || array_to_string(ARRAY(
|
||||
SELECT CASE WHEN length(r.m[1]) = 1 THEN encode(convert_to(r.m[1], 'SQL_ASCII'), 'hex') ELSE substring(r.m[1] from 2 for 2) END
|
||||
FROM regexp_matches($1, '%[0-9a-f][0-9a-f]|.', 'gi') AS r(m)
|
||||
), '') AS bytea), 'UTF8');
|
||||
$$
|
||||
LANGUAGE SQL IMMUTABLE STRICT;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION catch_decode_url_part(p varchar)
|
||||
RETURNS varchar
|
||||
AS $$
|
||||
DECLARE
|
||||
BEGIN
|
||||
RETURN decode_url_part(p);
|
||||
EXCEPTION
|
||||
WHEN others THEN return null;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql IMMUTABLE STRICT;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION get_wikipedia_match(extratags HSTORE, country_code varchar(2))
|
||||
RETURNS wikipedia_article_match
|
||||
AS $$
|
||||
DECLARE
|
||||
langs TEXT[];
|
||||
i INT;
|
||||
wiki_article TEXT;
|
||||
wiki_article_title TEXT;
|
||||
wiki_article_language TEXT;
|
||||
result wikipedia_article_match;
|
||||
BEGIN
|
||||
langs := ARRAY['english','country','ar','bg','ca','cs','da','de','en','es','eo','eu','fa','fr','ko','hi','hr','id','it','he','lt','hu','ms','nl','ja','no','pl','pt','kk','ro','ru','sk','sl','sr','fi','sv','tr','uk','vi','vo','war','zh'];
|
||||
i := 1;
|
||||
WHILE langs[i] IS NOT NULL LOOP
|
||||
wiki_article := extratags->(case when langs[i] in ('english','country') THEN 'wikipedia' ELSE 'wikipedia:'||langs[i] END);
|
||||
IF wiki_article is not null THEN
|
||||
wiki_article := regexp_replace(wiki_article,E'^(.*?)([a-z]{2,3}).wikipedia.org/wiki/',E'\\2:');
|
||||
wiki_article := regexp_replace(wiki_article,E'^(.*?)([a-z]{2,3}).wikipedia.org/w/index.php\\?title=',E'\\2:');
|
||||
wiki_article := regexp_replace(wiki_article,E'^(.*?)/([a-z]{2,3})/wiki/',E'\\2:');
|
||||
--wiki_article := regexp_replace(wiki_article,E'^(.*?)([a-z]{2,3})[=:]',E'\\2:');
|
||||
wiki_article := replace(wiki_article,' ','_');
|
||||
IF strpos(wiki_article, ':') IN (3,4) THEN
|
||||
wiki_article_language := lower(trim(split_part(wiki_article, ':', 1)));
|
||||
wiki_article_title := trim(substr(wiki_article, strpos(wiki_article, ':')+1));
|
||||
ELSE
|
||||
wiki_article_title := trim(wiki_article);
|
||||
wiki_article_language := CASE WHEN langs[i] = 'english' THEN 'en' WHEN langs[i] = 'country' THEN get_country_language_code(country_code) ELSE langs[i] END;
|
||||
END IF;
|
||||
|
||||
select wikipedia_article.language,wikipedia_article.title,wikipedia_article.importance
|
||||
from wikipedia_article
|
||||
where language = wiki_article_language and
|
||||
(title = wiki_article_title OR title = catch_decode_url_part(wiki_article_title) OR title = replace(catch_decode_url_part(wiki_article_title),E'\\',''))
|
||||
UNION ALL
|
||||
select wikipedia_article.language,wikipedia_article.title,wikipedia_article.importance
|
||||
from wikipedia_redirect join wikipedia_article on (wikipedia_redirect.language = wikipedia_article.language and wikipedia_redirect.to_title = wikipedia_article.title)
|
||||
where wikipedia_redirect.language = wiki_article_language and
|
||||
(from_title = wiki_article_title OR from_title = catch_decode_url_part(wiki_article_title) OR from_title = replace(catch_decode_url_part(wiki_article_title),E'\\',''))
|
||||
order by importance desc limit 1 INTO result;
|
||||
|
||||
IF result.language is not null THEN
|
||||
return result;
|
||||
END IF;
|
||||
END IF;
|
||||
i := i + 1;
|
||||
END LOOP;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql STABLE;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION compute_importance(extratags HSTORE,
|
||||
country_code varchar(2),
|
||||
osm_type varchar(1), osm_id BIGINT)
|
||||
RETURNS place_importance
|
||||
AS $$
|
||||
DECLARE
|
||||
match RECORD;
|
||||
result place_importance;
|
||||
BEGIN
|
||||
FOR match IN SELECT * FROM get_wikipedia_match(extratags, country_code)
|
||||
WHERE language is not NULL
|
||||
LOOP
|
||||
result.importance := match.importance;
|
||||
result.wikipedia := match.language || ':' || match.title;
|
||||
RETURN result;
|
||||
END LOOP;
|
||||
|
||||
IF extratags ? 'wikidata' THEN
|
||||
FOR match IN SELECT * FROM wikipedia_article
|
||||
WHERE wd_page_title = extratags->'wikidata'
|
||||
ORDER BY language = 'en' DESC, langcount DESC LIMIT 1 LOOP
|
||||
result.importance := match.importance;
|
||||
result.wikipedia := match.language || ':' || match.title;
|
||||
RETURN result;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
RETURN null;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
251
sql/functions/interpolation.sql
Normal file
251
sql/functions/interpolation.sql
Normal file
@@ -0,0 +1,251 @@
|
||||
-- Functions for address interpolation objects in location_property_osmline.
|
||||
|
||||
-- Splits the line at the given point and returns the two parts
|
||||
-- in a multilinestring.
|
||||
CREATE OR REPLACE FUNCTION split_line_on_node(line GEOMETRY, point GEOMETRY)
|
||||
RETURNS GEOMETRY
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN ST_Split(ST_Snap(line, point, 0.0005), point);
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
|
||||
-- find the parent road of the cut road parts
|
||||
CREATE OR REPLACE FUNCTION get_interpolation_parent(wayid BIGINT, street TEXT,
|
||||
place TEXT, partition SMALLINT,
|
||||
centroid GEOMETRY, geom GEOMETRY)
|
||||
RETURNS BIGINT
|
||||
AS $$
|
||||
DECLARE
|
||||
addr_street TEXT;
|
||||
addr_place TEXT;
|
||||
parent_place_id BIGINT;
|
||||
|
||||
waynodes BIGINT[];
|
||||
|
||||
location RECORD;
|
||||
BEGIN
|
||||
addr_street = street;
|
||||
addr_place = place;
|
||||
|
||||
IF addr_street is null and addr_place is null THEN
|
||||
select nodes from planet_osm_ways where id = wayid INTO waynodes;
|
||||
FOR location IN SELECT placex.address from placex
|
||||
where osm_type = 'N' and osm_id = ANY(waynodes)
|
||||
and placex.address is not null
|
||||
and (placex.address ? 'street' or placex.address ? 'place')
|
||||
and indexed_status < 100
|
||||
limit 1 LOOP
|
||||
addr_street = location.address->'street';
|
||||
addr_place = location.address->'place';
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
parent_place_id := find_parent_for_address(addr_street, addr_place,
|
||||
partition, centroid);
|
||||
|
||||
IF parent_place_id is null THEN
|
||||
FOR location IN SELECT place_id FROM placex
|
||||
WHERE ST_DWithin(geom, placex.geometry, 0.001) and placex.rank_search = 26
|
||||
ORDER BY (ST_distance(placex.geometry, ST_LineInterpolatePoint(geom,0))+
|
||||
ST_distance(placex.geometry, ST_LineInterpolatePoint(geom,0.5))+
|
||||
ST_distance(placex.geometry, ST_LineInterpolatePoint(geom,1))) ASC limit 1
|
||||
LOOP
|
||||
parent_place_id := location.place_id;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
IF parent_place_id is null THEN
|
||||
RETURN 0;
|
||||
END IF;
|
||||
|
||||
RETURN parent_place_id;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql STABLE;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION osmline_reinsert(node_id BIGINT, geom GEOMETRY)
|
||||
RETURNS BOOLEAN
|
||||
AS $$
|
||||
DECLARE
|
||||
existingline RECORD;
|
||||
BEGIN
|
||||
SELECT w.id FROM planet_osm_ways w, location_property_osmline p
|
||||
WHERE p.linegeo && geom and p.osm_id = w.id and p.indexed_status = 0
|
||||
and node_id = any(w.nodes) INTO existingline;
|
||||
|
||||
IF existingline.id is not NULL THEN
|
||||
DELETE FROM location_property_osmline WHERE osm_id = existingline.id;
|
||||
INSERT INTO location_property_osmline (osm_id, address, linegeo)
|
||||
SELECT osm_id, address, geometry FROM place
|
||||
WHERE osm_type = 'W' and osm_id = existingline.id;
|
||||
END IF;
|
||||
|
||||
RETURN true;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION osmline_insert()
|
||||
RETURNS TRIGGER
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW.place_id := nextval('seq_place');
|
||||
NEW.indexed_date := now();
|
||||
|
||||
IF NEW.indexed_status IS NULL THEN
|
||||
IF NEW.address is NULL OR NOT NEW.address ? 'interpolation'
|
||||
OR NEW.address->'interpolation' NOT IN ('odd', 'even', 'all') THEN
|
||||
-- other interpolation types than odd/even/all (e.g. numeric ones) are not supported
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
NEW.indexed_status := 1; --STATUS_NEW
|
||||
NEW.country_code := lower(get_country_code(NEW.linegeo));
|
||||
|
||||
NEW.partition := get_partition(NEW.country_code);
|
||||
NEW.geometry_sector := geometry_sector(NEW.partition, NEW.linegeo);
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION osmline_update()
|
||||
RETURNS TRIGGER
|
||||
AS $$
|
||||
DECLARE
|
||||
place_centroid GEOMETRY;
|
||||
waynodes BIGINT[];
|
||||
prevnode RECORD;
|
||||
nextnode RECORD;
|
||||
startnumber INTEGER;
|
||||
endnumber INTEGER;
|
||||
housenum INTEGER;
|
||||
linegeo GEOMETRY;
|
||||
splitline GEOMETRY;
|
||||
sectiongeo GEOMETRY;
|
||||
interpol_postcode TEXT;
|
||||
postcode TEXT;
|
||||
BEGIN
|
||||
-- deferred delete
|
||||
IF OLD.indexed_status = 100 THEN
|
||||
delete from location_property_osmline where place_id = OLD.place_id;
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
NEW.interpolationtype = NEW.address->'interpolation';
|
||||
|
||||
place_centroid := ST_PointOnSurface(NEW.linegeo);
|
||||
NEW.parent_place_id = get_interpolation_parent(NEW.osm_id, NEW.address->'street',
|
||||
NEW.address->'place',
|
||||
NEW.partition, place_centroid, NEW.linegeo);
|
||||
|
||||
IF NEW.address is not NULL AND NEW.address ? 'postcode' AND NEW.address->'postcode' not similar to '%(,|;)%' THEN
|
||||
interpol_postcode := NEW.address->'postcode';
|
||||
housenum := getorcreate_postcode_id(NEW.address->'postcode');
|
||||
ELSE
|
||||
interpol_postcode := NULL;
|
||||
END IF;
|
||||
|
||||
-- if the line was newly inserted, split the line as necessary
|
||||
IF OLD.indexed_status = 1 THEN
|
||||
select nodes from planet_osm_ways where id = NEW.osm_id INTO waynodes;
|
||||
|
||||
IF array_upper(waynodes, 1) IS NULL THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
linegeo := NEW.linegeo;
|
||||
startnumber := NULL;
|
||||
|
||||
FOR nodeidpos in 1..array_upper(waynodes, 1) LOOP
|
||||
|
||||
select osm_id, address, geometry
|
||||
from place where osm_type = 'N' and osm_id = waynodes[nodeidpos]::BIGINT
|
||||
and address is not NULL and address ? 'housenumber' limit 1 INTO nextnode;
|
||||
--RAISE NOTICE 'Nextnode.place_id: %s', nextnode.place_id;
|
||||
IF nextnode.osm_id IS NOT NULL THEN
|
||||
--RAISE NOTICE 'place_id is not null';
|
||||
IF nodeidpos > 1 and nodeidpos < array_upper(waynodes, 1) THEN
|
||||
-- Make sure that the point is actually on the line. That might
|
||||
-- be a bit paranoid but ensures that the algorithm still works
|
||||
-- should osm2pgsql attempt to repair geometries.
|
||||
splitline := split_line_on_node(linegeo, nextnode.geometry);
|
||||
sectiongeo := ST_GeometryN(splitline, 1);
|
||||
linegeo := ST_GeometryN(splitline, 2);
|
||||
ELSE
|
||||
sectiongeo = linegeo;
|
||||
END IF;
|
||||
endnumber := substring(nextnode.address->'housenumber','[0-9]+')::integer;
|
||||
|
||||
IF startnumber IS NOT NULL AND endnumber IS NOT NULL
|
||||
AND startnumber != endnumber
|
||||
AND ST_GeometryType(sectiongeo) = 'ST_LineString' THEN
|
||||
|
||||
IF (startnumber > endnumber) THEN
|
||||
housenum := endnumber;
|
||||
endnumber := startnumber;
|
||||
startnumber := housenum;
|
||||
sectiongeo := ST_Reverse(sectiongeo);
|
||||
END IF;
|
||||
|
||||
-- determine postcode
|
||||
postcode := coalesce(interpol_postcode,
|
||||
prevnode.address->'postcode',
|
||||
nextnode.address->'postcode',
|
||||
postcode);
|
||||
|
||||
IF postcode is NULL THEN
|
||||
SELECT placex.postcode FROM placex WHERE place_id = NEW.parent_place_id INTO postcode;
|
||||
END IF;
|
||||
IF postcode is NULL THEN
|
||||
postcode := get_nearest_postcode(NEW.country_code, nextnode.geometry);
|
||||
END IF;
|
||||
|
||||
IF NEW.startnumber IS NULL THEN
|
||||
NEW.startnumber := startnumber;
|
||||
NEW.endnumber := endnumber;
|
||||
NEW.linegeo := sectiongeo;
|
||||
NEW.postcode := upper(trim(postcode));
|
||||
ELSE
|
||||
insert into location_property_osmline
|
||||
(linegeo, partition, osm_id, parent_place_id,
|
||||
startnumber, endnumber, interpolationtype,
|
||||
address, postcode, country_code,
|
||||
geometry_sector, indexed_status)
|
||||
values (sectiongeo, NEW.partition, NEW.osm_id, NEW.parent_place_id,
|
||||
startnumber, endnumber, NEW.interpolationtype,
|
||||
NEW.address, postcode,
|
||||
NEW.country_code, NEW.geometry_sector, 0);
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- early break if we are out of line string,
|
||||
-- might happen when a line string loops back on itself
|
||||
IF ST_GeometryType(linegeo) != 'ST_LineString' THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
startnumber := substring(nextnode.address->'housenumber','[0-9]+')::integer;
|
||||
prevnode := nextnode;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
-- marking descendants for reparenting is not needed, because there are
|
||||
-- actually no descendants for interpolation lines
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
407
sql/functions/normalization.sql
Normal file
407
sql/functions/normalization.sql
Normal file
@@ -0,0 +1,407 @@
|
||||
-- Functions for term normalisation and access to the 'word' table.
|
||||
|
||||
CREATE OR REPLACE FUNCTION transliteration(text) RETURNS text
|
||||
AS '{modulepath}/nominatim.so', 'transliteration'
|
||||
LANGUAGE c IMMUTABLE STRICT;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION gettokenstring(text) RETURNS text
|
||||
AS '{modulepath}/nominatim.so', 'gettokenstring'
|
||||
LANGUAGE c IMMUTABLE STRICT;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION make_standard_name(name TEXT) RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
o TEXT;
|
||||
BEGIN
|
||||
o := public.gettokenstring(public.transliteration(name));
|
||||
RETURN trim(substr(o,1,length(o)));
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
-- returns NULL if the word is too common
|
||||
CREATE OR REPLACE FUNCTION getorcreate_word_id(lookup_word TEXT)
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
DECLARE
|
||||
lookup_token TEXT;
|
||||
return_word_id INTEGER;
|
||||
count INTEGER;
|
||||
BEGIN
|
||||
lookup_token := trim(lookup_word);
|
||||
SELECT min(word_id), max(search_name_count) FROM word
|
||||
WHERE word_token = lookup_token and class is null and type is null
|
||||
INTO return_word_id, count;
|
||||
IF return_word_id IS NULL THEN
|
||||
return_word_id := nextval('seq_word');
|
||||
INSERT INTO word VALUES (return_word_id, lookup_token, null, null, null, null, 0);
|
||||
ELSE
|
||||
IF count > get_maxwordfreq() THEN
|
||||
return_word_id := NULL;
|
||||
END IF;
|
||||
END IF;
|
||||
RETURN return_word_id;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION getorcreate_housenumber_id(lookup_word TEXT)
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
DECLARE
|
||||
lookup_token TEXT;
|
||||
return_word_id INTEGER;
|
||||
BEGIN
|
||||
lookup_token := ' ' || trim(lookup_word);
|
||||
SELECT min(word_id) FROM word
|
||||
WHERE word_token = lookup_token and class='place' and type='house'
|
||||
INTO return_word_id;
|
||||
IF return_word_id IS NULL THEN
|
||||
return_word_id := nextval('seq_word');
|
||||
INSERT INTO word VALUES (return_word_id, lookup_token, null,
|
||||
'place', 'house', null, 0);
|
||||
END IF;
|
||||
RETURN return_word_id;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION getorcreate_postcode_id(postcode TEXT)
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
DECLARE
|
||||
lookup_token TEXT;
|
||||
lookup_word TEXT;
|
||||
return_word_id INTEGER;
|
||||
BEGIN
|
||||
lookup_word := upper(trim(postcode));
|
||||
lookup_token := ' ' || make_standard_name(lookup_word);
|
||||
SELECT min(word_id) FROM word
|
||||
WHERE word_token = lookup_token and class='place' and type='postcode'
|
||||
INTO return_word_id;
|
||||
IF return_word_id IS NULL THEN
|
||||
return_word_id := nextval('seq_word');
|
||||
INSERT INTO word VALUES (return_word_id, lookup_token, lookup_word,
|
||||
'place', 'postcode', null, 0);
|
||||
END IF;
|
||||
RETURN return_word_id;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION getorcreate_country(lookup_word TEXT,
|
||||
lookup_country_code varchar(2))
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
DECLARE
|
||||
lookup_token TEXT;
|
||||
return_word_id INTEGER;
|
||||
BEGIN
|
||||
lookup_token := ' '||trim(lookup_word);
|
||||
SELECT min(word_id) FROM word
|
||||
WHERE word_token = lookup_token and country_code=lookup_country_code
|
||||
INTO return_word_id;
|
||||
IF return_word_id IS NULL THEN
|
||||
return_word_id := nextval('seq_word');
|
||||
INSERT INTO word VALUES (return_word_id, lookup_token, null,
|
||||
null, null, lookup_country_code, 0);
|
||||
END IF;
|
||||
RETURN return_word_id;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION getorcreate_amenity(lookup_word TEXT, normalized_word TEXT,
|
||||
lookup_class text, lookup_type text)
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
DECLARE
|
||||
lookup_token TEXT;
|
||||
return_word_id INTEGER;
|
||||
BEGIN
|
||||
lookup_token := ' '||trim(lookup_word);
|
||||
SELECT min(word_id) FROM word
|
||||
WHERE word_token = lookup_token and word = normalized_word
|
||||
and class = lookup_class and type = lookup_type
|
||||
INTO return_word_id;
|
||||
IF return_word_id IS NULL THEN
|
||||
return_word_id := nextval('seq_word');
|
||||
INSERT INTO word VALUES (return_word_id, lookup_token, normalized_word,
|
||||
lookup_class, lookup_type, null, 0);
|
||||
END IF;
|
||||
RETURN return_word_id;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION getorcreate_amenityoperator(lookup_word TEXT,
|
||||
normalized_word TEXT,
|
||||
lookup_class text,
|
||||
lookup_type text,
|
||||
op text)
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
DECLARE
|
||||
lookup_token TEXT;
|
||||
return_word_id INTEGER;
|
||||
BEGIN
|
||||
lookup_token := ' '||trim(lookup_word);
|
||||
SELECT min(word_id) FROM word
|
||||
WHERE word_token = lookup_token and word = normalized_word
|
||||
and class = lookup_class and type = lookup_type and operator = op
|
||||
INTO return_word_id;
|
||||
IF return_word_id IS NULL THEN
|
||||
return_word_id := nextval('seq_word');
|
||||
INSERT INTO word VALUES (return_word_id, lookup_token, normalized_word,
|
||||
lookup_class, lookup_type, null, 0, op);
|
||||
END IF;
|
||||
RETURN return_word_id;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION getorcreate_name_id(lookup_word TEXT, src_word TEXT)
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
DECLARE
|
||||
lookup_token TEXT;
|
||||
nospace_lookup_token TEXT;
|
||||
return_word_id INTEGER;
|
||||
BEGIN
|
||||
lookup_token := ' '||trim(lookup_word);
|
||||
SELECT min(word_id) FROM word
|
||||
WHERE word_token = lookup_token and class is null and type is null
|
||||
INTO return_word_id;
|
||||
IF return_word_id IS NULL THEN
|
||||
return_word_id := nextval('seq_word');
|
||||
INSERT INTO word VALUES (return_word_id, lookup_token, src_word,
|
||||
null, null, null, 0);
|
||||
END IF;
|
||||
RETURN return_word_id;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION getorcreate_name_id(lookup_word TEXT)
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
DECLARE
|
||||
BEGIN
|
||||
RETURN getorcreate_name_id(lookup_word, '');
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
-- Normalize a string and lookup its word ids (partial words).
|
||||
CREATE OR REPLACE FUNCTION addr_ids_from_name(lookup_word TEXT)
|
||||
RETURNS INTEGER[]
|
||||
AS $$
|
||||
DECLARE
|
||||
lookup_token TEXT;
|
||||
return_word_id INTEGER[];
|
||||
BEGIN
|
||||
lookup_token := make_standard_name(lookup_word);
|
||||
SELECT array_agg(word_id) FROM word
|
||||
WHERE word_token = lookup_token and class is null and type is null
|
||||
INTO return_word_id;
|
||||
RETURN return_word_id;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql STABLE;
|
||||
|
||||
|
||||
-- Normalize a string and look up its name ids (full words).
|
||||
CREATE OR REPLACE FUNCTION word_ids_from_name(lookup_word TEXT)
|
||||
RETURNS INTEGER[]
|
||||
AS $$
|
||||
DECLARE
|
||||
lookup_token TEXT;
|
||||
return_word_ids INTEGER[];
|
||||
BEGIN
|
||||
lookup_token := ' '|| make_standard_name(lookup_word);
|
||||
SELECT array_agg(word_id) FROM word
|
||||
WHERE word_token = lookup_token and class is null and type is null
|
||||
INTO return_word_ids;
|
||||
RETURN return_word_ids;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql STABLE STRICT;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION create_country(src HSTORE, country_code varchar(2))
|
||||
RETURNS VOID
|
||||
AS $$
|
||||
DECLARE
|
||||
s TEXT;
|
||||
w INTEGER;
|
||||
words TEXT[];
|
||||
item RECORD;
|
||||
j INTEGER;
|
||||
BEGIN
|
||||
FOR item IN SELECT (each(src)).* LOOP
|
||||
|
||||
s := make_standard_name(item.value);
|
||||
w := getorcreate_country(s, country_code);
|
||||
|
||||
words := regexp_split_to_array(item.value, E'[,;()]');
|
||||
IF array_upper(words, 1) != 1 THEN
|
||||
FOR j IN 1..array_upper(words, 1) LOOP
|
||||
s := make_standard_name(words[j]);
|
||||
IF s != '' THEN
|
||||
w := getorcreate_country(s, country_code);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION make_keywords(src HSTORE)
|
||||
RETURNS INTEGER[]
|
||||
AS $$
|
||||
DECLARE
|
||||
result INTEGER[];
|
||||
s TEXT;
|
||||
w INTEGER;
|
||||
words TEXT[];
|
||||
item RECORD;
|
||||
j INTEGER;
|
||||
BEGIN
|
||||
result := '{}'::INTEGER[];
|
||||
|
||||
FOR item IN SELECT (each(src)).* LOOP
|
||||
|
||||
s := make_standard_name(item.value);
|
||||
w := getorcreate_name_id(s, item.value);
|
||||
|
||||
IF not(ARRAY[w] <@ result) THEN
|
||||
result := result || w;
|
||||
END IF;
|
||||
|
||||
w := getorcreate_word_id(s);
|
||||
|
||||
IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
|
||||
result := result || w;
|
||||
END IF;
|
||||
|
||||
words := string_to_array(s, ' ');
|
||||
IF array_upper(words, 1) IS NOT NULL THEN
|
||||
FOR j IN 1..array_upper(words, 1) LOOP
|
||||
IF (words[j] != '') THEN
|
||||
w = getorcreate_word_id(words[j]);
|
||||
IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
|
||||
result := result || w;
|
||||
END IF;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
words := regexp_split_to_array(item.value, E'[,;()]');
|
||||
IF array_upper(words, 1) != 1 THEN
|
||||
FOR j IN 1..array_upper(words, 1) LOOP
|
||||
s := make_standard_name(words[j]);
|
||||
IF s != '' THEN
|
||||
w := getorcreate_word_id(s);
|
||||
IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
|
||||
result := result || w;
|
||||
END IF;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
s := regexp_replace(item.value, '市$', '');
|
||||
IF s != item.value THEN
|
||||
s := make_standard_name(s);
|
||||
IF s != '' THEN
|
||||
w := getorcreate_name_id(s, item.value);
|
||||
IF NOT (ARRAY[w] <@ result) THEN
|
||||
result := result || w;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
END LOOP;
|
||||
|
||||
RETURN result;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION make_keywords(src TEXT)
|
||||
RETURNS INTEGER[]
|
||||
AS $$
|
||||
DECLARE
|
||||
result INTEGER[];
|
||||
s TEXT;
|
||||
w INTEGER;
|
||||
words TEXT[];
|
||||
i INTEGER;
|
||||
j INTEGER;
|
||||
BEGIN
|
||||
result := '{}'::INTEGER[];
|
||||
|
||||
s := make_standard_name(src);
|
||||
w := getorcreate_name_id(s, src);
|
||||
|
||||
IF NOT (ARRAY[w] <@ result) THEN
|
||||
result := result || w;
|
||||
END IF;
|
||||
|
||||
w := getorcreate_word_id(s);
|
||||
|
||||
IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
|
||||
result := result || w;
|
||||
END IF;
|
||||
|
||||
words := string_to_array(s, ' ');
|
||||
IF array_upper(words, 1) IS NOT NULL THEN
|
||||
FOR j IN 1..array_upper(words, 1) LOOP
|
||||
IF (words[j] != '') THEN
|
||||
w = getorcreate_word_id(words[j]);
|
||||
IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
|
||||
result := result || w;
|
||||
END IF;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
words := regexp_split_to_array(src, E'[,;()]');
|
||||
IF array_upper(words, 1) != 1 THEN
|
||||
FOR j IN 1..array_upper(words, 1) LOOP
|
||||
s := make_standard_name(words[j]);
|
||||
IF s != '' THEN
|
||||
w := getorcreate_word_id(s);
|
||||
IF w IS NOT NULL AND NOT (ARRAY[w] <@ result) THEN
|
||||
result := result || w;
|
||||
END IF;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
s := regexp_replace(src, '市$', '');
|
||||
IF s != src THEN
|
||||
s := make_standard_name(s);
|
||||
IF s != '' THEN
|
||||
w := getorcreate_name_id(s, src);
|
||||
IF NOT (ARRAY[w] <@ result) THEN
|
||||
result := result || w;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
RETURN result;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
284
sql/functions/place_triggers.sql
Normal file
284
sql/functions/place_triggers.sql
Normal file
@@ -0,0 +1,284 @@
|
||||
CREATE OR REPLACE FUNCTION place_insert()
|
||||
RETURNS TRIGGER
|
||||
AS $$
|
||||
DECLARE
|
||||
i INTEGER;
|
||||
existing RECORD;
|
||||
existingplacex RECORD;
|
||||
existingline RECORD;
|
||||
existinggeometry GEOMETRY;
|
||||
existingplace_id BIGINT;
|
||||
result BOOLEAN;
|
||||
partition INTEGER;
|
||||
BEGIN
|
||||
|
||||
--DEBUG: RAISE WARNING '-----------------------------------------------------------------------------------';
|
||||
--DEBUG: RAISE WARNING 'place_insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,st_area(NEW.geometry);
|
||||
-- filter wrong tupels
|
||||
IF ST_IsEmpty(NEW.geometry) OR NOT ST_IsValid(NEW.geometry) OR ST_X(ST_Centroid(NEW.geometry))::text in ('NaN','Infinity','-Infinity') OR ST_Y(ST_Centroid(NEW.geometry))::text in ('NaN','Infinity','-Infinity') THEN
|
||||
INSERT INTO import_polygon_error (osm_type, osm_id, class, type, name, country_code, updated, errormessage, prevgeometry, newgeometry)
|
||||
VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name, NEW.address->'country', now(), ST_IsValidReason(NEW.geometry), null, NEW.geometry);
|
||||
-- RAISE WARNING 'Invalid Geometry: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
|
||||
RETURN null;
|
||||
END IF;
|
||||
|
||||
-- decide, whether it is an osm interpolation line => insert intoosmline, or else just placex
|
||||
IF NEW.class='place' and NEW.type='houses' and NEW.osm_type='W' and ST_GeometryType(NEW.geometry) = 'ST_LineString' THEN
|
||||
-- Have we already done this place?
|
||||
select * from place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type INTO existing;
|
||||
|
||||
-- Get the existing place_id
|
||||
select * from location_property_osmline where osm_id = NEW.osm_id INTO existingline;
|
||||
|
||||
-- Handle a place changing type by removing the old data (this trigger is executed BEFORE INSERT of the NEW tupel)
|
||||
IF existing.osm_type IS NULL THEN
|
||||
DELETE FROM place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class;
|
||||
END IF;
|
||||
|
||||
DELETE from import_polygon_error where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
|
||||
DELETE from import_polygon_delete where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
|
||||
|
||||
-- update method for interpolation lines: delete all old interpolation lines with same osm_id (update on place) and insert the new one(s) (they can be split up, if they have > 2 nodes)
|
||||
IF existingline.osm_id IS NOT NULL THEN
|
||||
delete from location_property_osmline where osm_id = NEW.osm_id;
|
||||
END IF;
|
||||
|
||||
-- for interpolations invalidate all nodes on the line
|
||||
update placex p set indexed_status = 2
|
||||
from planet_osm_ways w
|
||||
where w.id = NEW.osm_id and p.osm_type = 'N' and p.osm_id = any(w.nodes);
|
||||
|
||||
|
||||
INSERT INTO location_property_osmline (osm_id, address, linegeo)
|
||||
VALUES (NEW.osm_id, NEW.address, NEW.geometry);
|
||||
|
||||
|
||||
IF existing.osm_type IS NULL THEN
|
||||
return NEW;
|
||||
END IF;
|
||||
|
||||
IF coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
|
||||
OR (coalesce(existing.extratags, ''::hstore) != coalesce(NEW.extratags, ''::hstore))
|
||||
OR existing.geometry::text != NEW.geometry::text
|
||||
THEN
|
||||
|
||||
update place set
|
||||
name = NEW.name,
|
||||
address = NEW.address,
|
||||
extratags = NEW.extratags,
|
||||
admin_level = NEW.admin_level,
|
||||
geometry = NEW.geometry
|
||||
where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type;
|
||||
END IF;
|
||||
|
||||
RETURN NULL;
|
||||
|
||||
ELSE -- insert to placex
|
||||
|
||||
-- Patch in additional country names
|
||||
IF NEW.admin_level = 2 AND NEW.type = 'administrative'
|
||||
AND NEW.address is not NULL AND NEW.address ? 'country' THEN
|
||||
SELECT name FROM country_name WHERE country_code = lower(NEW.address->'country') INTO existing;
|
||||
IF existing.name IS NOT NULL THEN
|
||||
NEW.name = existing.name || NEW.name;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Have we already done this place?
|
||||
select * from place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type INTO existing;
|
||||
|
||||
-- Get the existing place_id
|
||||
select * from placex where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type INTO existingplacex;
|
||||
|
||||
-- Handle a place changing type by removing the old data
|
||||
-- My generated 'place' types are causing havok because they overlap with real keys
|
||||
-- TODO: move them to their own special purpose key/class to avoid collisions
|
||||
IF existing.osm_type IS NULL THEN
|
||||
DELETE FROM place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class;
|
||||
END IF;
|
||||
|
||||
--DEBUG: RAISE WARNING 'Existing: %',existing.osm_id;
|
||||
--DEBUG: RAISE WARNING 'Existing PlaceX: %',existingplacex.place_id;
|
||||
|
||||
-- Log and discard
|
||||
IF existing.geometry is not null AND st_isvalid(existing.geometry)
|
||||
AND st_area(existing.geometry) > 0.02
|
||||
AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon')
|
||||
AND st_area(NEW.geometry) < st_area(existing.geometry)*0.5
|
||||
THEN
|
||||
INSERT INTO import_polygon_error (osm_type, osm_id, class, type, name, country_code, updated, errormessage, prevgeometry, newgeometry)
|
||||
VALUES (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name, NEW.address->'country', now(),
|
||||
'Area reduced from '||st_area(existing.geometry)||' to '||st_area(NEW.geometry), existing.geometry, NEW.geometry);
|
||||
RETURN null;
|
||||
END IF;
|
||||
|
||||
DELETE from import_polygon_error where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
|
||||
DELETE from import_polygon_delete where osm_type = NEW.osm_type and osm_id = NEW.osm_id;
|
||||
|
||||
-- To paraphrase, if there isn't an existing item, OR if the admin level has changed
|
||||
IF existingplacex.osm_type IS NULL OR
|
||||
(existingplacex.class = 'boundary' AND
|
||||
((coalesce(existingplacex.admin_level, 15) != coalesce(NEW.admin_level, 15) AND existingplacex.type = 'administrative') OR
|
||||
(existingplacex.type != NEW.type)))
|
||||
THEN
|
||||
|
||||
IF existingplacex.osm_type IS NOT NULL THEN
|
||||
-- sanity check: ignore admin_level changes on places with too many active children
|
||||
-- or we end up reindexing entire countries because somebody accidentally deleted admin_level
|
||||
--LIMIT INDEXING: SELECT count(*) FROM (SELECT 'a' FROM placex , place_addressline where address_place_id = existingplacex.place_id and placex.place_id = place_addressline.place_id and indexed_status = 0 and place_addressline.isaddress LIMIT 100001) sub INTO i;
|
||||
--LIMIT INDEXING: IF i > 100000 THEN
|
||||
--LIMIT INDEXING: RETURN null;
|
||||
--LIMIT INDEXING: END IF;
|
||||
END IF;
|
||||
|
||||
IF existing.osm_type IS NOT NULL THEN
|
||||
-- pathological case caused by the triggerless copy into place during initial import
|
||||
-- force delete even for large areas, it will be reinserted later
|
||||
UPDATE place set geometry = ST_SetSRID(ST_Point(0,0), 4326) where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type;
|
||||
DELETE from place where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type;
|
||||
END IF;
|
||||
|
||||
-- No - process it as a new insertion (hopefully of low rank or it will be slow)
|
||||
insert into placex (osm_type, osm_id, class, type, name,
|
||||
admin_level, address, extratags, geometry)
|
||||
values (NEW.osm_type, NEW.osm_id, NEW.class, NEW.type, NEW.name,
|
||||
NEW.admin_level, NEW.address, NEW.extratags, NEW.geometry);
|
||||
|
||||
--DEBUG: RAISE WARNING 'insert done % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,NEW.name;
|
||||
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
-- Special case for polygon shape changes because they tend to be large and we can be a bit clever about how we handle them
|
||||
IF existing.geometry::text != NEW.geometry::text
|
||||
AND ST_GeometryType(existing.geometry) in ('ST_Polygon','ST_MultiPolygon')
|
||||
AND ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon')
|
||||
THEN
|
||||
|
||||
-- Get the version of the geometry actually used (in placex table)
|
||||
select geometry from placex where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type into existinggeometry;
|
||||
|
||||
-- Performance limit
|
||||
IF st_area(NEW.geometry) < 0.000000001 AND st_area(existinggeometry) < 1 THEN
|
||||
|
||||
-- re-index points that have moved in / out of the polygon, could be done as a single query but postgres gets the index usage wrong
|
||||
update placex set indexed_status = 2 where indexed_status = 0 and
|
||||
(st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry))
|
||||
AND NOT (st_covers(existinggeometry, placex.geometry) OR ST_Intersects(existinggeometry, placex.geometry))
|
||||
AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null);
|
||||
|
||||
update placex set indexed_status = 2 where indexed_status = 0 and
|
||||
(st_covers(existinggeometry, placex.geometry) OR ST_Intersects(existinggeometry, placex.geometry))
|
||||
AND NOT (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry))
|
||||
AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null);
|
||||
|
||||
END IF;
|
||||
|
||||
END IF;
|
||||
|
||||
|
||||
IF coalesce(existing.name::text, '') != coalesce(NEW.name::text, '')
|
||||
OR coalesce(existing.extratags::text, '') != coalesce(NEW.extratags::text, '')
|
||||
OR coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
|
||||
OR coalesce(existing.admin_level, 15) != coalesce(NEW.admin_level, 15)
|
||||
OR existing.geometry::text != NEW.geometry::text
|
||||
THEN
|
||||
|
||||
update place set
|
||||
name = NEW.name,
|
||||
address = NEW.address,
|
||||
extratags = NEW.extratags,
|
||||
admin_level = NEW.admin_level,
|
||||
geometry = NEW.geometry
|
||||
where osm_type = NEW.osm_type and osm_id = NEW.osm_id and class = NEW.class and type = NEW.type;
|
||||
|
||||
|
||||
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
|
||||
-- postcode was deleted, no longer retain in placex
|
||||
DELETE FROM placex where place_id = existingplacex.place_id;
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
NEW.name := hstore('ref', NEW.address->'postcode');
|
||||
END IF;
|
||||
|
||||
IF NEW.class in ('boundary')
|
||||
AND ST_GeometryType(NEW.geometry) not in ('ST_Polygon','ST_MultiPolygon') THEN
|
||||
DELETE FROM placex where place_id = existingplacex.place_id;
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
update placex set
|
||||
name = NEW.name,
|
||||
address = NEW.address,
|
||||
parent_place_id = null,
|
||||
extratags = NEW.extratags,
|
||||
admin_level = NEW.admin_level,
|
||||
indexed_status = 2,
|
||||
geometry = NEW.geometry
|
||||
where place_id = existingplacex.place_id;
|
||||
-- if a node(=>house), which is part of a interpolation line, changes (e.g. the street attribute) => mark this line for reparenting
|
||||
-- (already here, because interpolation lines are reindexed before nodes, so in the second call it would be too late)
|
||||
IF NEW.osm_type='N'
|
||||
and (coalesce(existing.address, ''::hstore) != coalesce(NEW.address, ''::hstore)
|
||||
or existing.geometry::text != NEW.geometry::text)
|
||||
THEN
|
||||
result:= osmline_reinsert(NEW.osm_id, NEW.geometry);
|
||||
END IF;
|
||||
|
||||
-- linked places should get potential new naming and addresses
|
||||
IF existingplacex.linked_place_id is not NULL THEN
|
||||
update placex x set
|
||||
name = p.name,
|
||||
extratags = p.extratags,
|
||||
indexed_status = 2
|
||||
from place p
|
||||
where x.place_id = existingplacex.linked_place_id
|
||||
and x.indexed_status = 0
|
||||
and x.osm_type = p.osm_type
|
||||
and x.osm_id = p.osm_id
|
||||
and x.class = p.class;
|
||||
END IF;
|
||||
|
||||
END IF;
|
||||
|
||||
-- Abort the add (we modified the existing place instead)
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION place_delete()
|
||||
RETURNS TRIGGER
|
||||
AS $$
|
||||
DECLARE
|
||||
has_rank BOOLEAN;
|
||||
BEGIN
|
||||
|
||||
--DEBUG: RAISE WARNING 'delete: % % % %',OLD.osm_type,OLD.osm_id,OLD.class,OLD.type;
|
||||
|
||||
-- deleting large polygons can have a massive effect on the system - require manual intervention to let them through
|
||||
IF st_area(OLD.geometry) > 2 and st_isvalid(OLD.geometry) THEN
|
||||
SELECT bool_or(not (rank_address = 0 or rank_address > 26)) as ranked FROM placex WHERE osm_type = OLD.osm_type and osm_id = OLD.osm_id and class = OLD.class and type = OLD.type INTO has_rank;
|
||||
IF has_rank THEN
|
||||
insert into import_polygon_delete (osm_type, osm_id, class, type) values (OLD.osm_type,OLD.osm_id,OLD.class,OLD.type);
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- mark for delete
|
||||
UPDATE placex set indexed_status = 100 where osm_type = OLD.osm_type and osm_id = OLD.osm_id and class = OLD.class and type = OLD.type;
|
||||
|
||||
-- interpolations are special
|
||||
IF OLD.osm_type='W' and OLD.class = 'place' and OLD.type = 'houses' THEN
|
||||
UPDATE location_property_osmline set indexed_status = 100 where osm_id = OLD.osm_id; -- osm_id = wayid (=old.osm_id)
|
||||
END IF;
|
||||
|
||||
RETURN OLD;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
965
sql/functions/placex_triggers.sql
Normal file
965
sql/functions/placex_triggers.sql
Normal file
@@ -0,0 +1,965 @@
|
||||
-- Trigger functions for the placex table.
|
||||
|
||||
-- Find the parent road of a POI.
|
||||
--
|
||||
-- \returns Place ID of parent object or NULL if none
|
||||
--
|
||||
-- Copy data from linked items (POIs on ways, addr:street links, relations).
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION find_parent_for_poi(poi_osm_type CHAR(1),
|
||||
poi_osm_id BIGINT,
|
||||
poi_partition SMALLINT,
|
||||
bbox GEOMETRY,
|
||||
addr_street TEXT,
|
||||
addr_place TEXT,
|
||||
fallback BOOL = true)
|
||||
RETURNS BIGINT
|
||||
AS $$
|
||||
DECLARE
|
||||
parent_place_id BIGINT DEFAULT NULL;
|
||||
location RECORD;
|
||||
parent RECORD;
|
||||
BEGIN
|
||||
--DEBUG: RAISE WARNING 'finding street for % %', poi_osm_type, poi_osm_id;
|
||||
|
||||
-- Is this object part of an associatedStreet relation?
|
||||
FOR location IN
|
||||
SELECT members FROM planet_osm_rels
|
||||
WHERE parts @> ARRAY[poi_osm_id]
|
||||
and members @> ARRAY[lower(poi_osm_type) || poi_osm_id]
|
||||
and tags @> ARRAY['associatedStreet']
|
||||
LOOP
|
||||
FOR i IN 1..array_upper(location.members, 1) BY 2 LOOP
|
||||
IF location.members[i+1] = 'street' THEN
|
||||
--DEBUG: RAISE WARNING 'node in relation %',relation;
|
||||
FOR parent IN
|
||||
SELECT place_id from placex
|
||||
WHERE osm_type = 'W' and osm_id = substring(location.members[i],2)::bigint
|
||||
and name is not null
|
||||
and rank_search between 26 and 27
|
||||
LOOP
|
||||
RETURN parent.place_id;
|
||||
END LOOP;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END LOOP;
|
||||
|
||||
parent_place_id := find_parent_for_address(addr_street, addr_place,
|
||||
poi_partition, bbox);
|
||||
IF parent_place_id is not null THEN
|
||||
RETURN parent_place_id;
|
||||
END IF;
|
||||
|
||||
IF poi_osm_type = 'N' THEN
|
||||
-- Is this node part of an interpolation?
|
||||
FOR parent IN
|
||||
SELECT q.parent_place_id
|
||||
FROM location_property_osmline q, planet_osm_ways x
|
||||
WHERE q.linegeo && bbox and x.id = q.osm_id
|
||||
and poi_osm_id = any(x.nodes)
|
||||
LIMIT 1
|
||||
LOOP
|
||||
--DEBUG: RAISE WARNING 'Get parent from interpolation: %', parent.parent_place_id;
|
||||
RETURN parent.parent_place_id;
|
||||
END LOOP;
|
||||
|
||||
-- Is this node part of any other way?
|
||||
FOR location IN
|
||||
SELECT p.place_id, p.osm_id, p.rank_search, p.address,
|
||||
coalesce(p.centroid, ST_Centroid(p.geometry)) as centroid
|
||||
FROM placex p, planet_osm_ways w
|
||||
WHERE p.osm_type = 'W' and p.rank_search >= 26
|
||||
and p.geometry && bbox
|
||||
and w.id = p.osm_id and poi_osm_id = any(w.nodes)
|
||||
LOOP
|
||||
--DEBUG: RAISE WARNING 'Node is part of way % ', location.osm_id;
|
||||
|
||||
-- Way IS a road then we are on it - that must be our road
|
||||
IF location.rank_search < 28 THEN
|
||||
--DEBUG: RAISE WARNING 'node in way that is a street %',location;
|
||||
return location.place_id;
|
||||
END IF;
|
||||
|
||||
SELECT find_parent_for_poi('W', location.osm_id, poi_partition,
|
||||
location.centroid,
|
||||
location.address->'street',
|
||||
location.address->'place',
|
||||
false)
|
||||
INTO parent_place_id;
|
||||
IF parent_place_id is not null THEN
|
||||
RETURN parent_place_id;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
IF fallback THEN
|
||||
IF ST_Area(bbox) < 0.01 THEN
|
||||
-- for smaller features get the nearest road
|
||||
SELECT getNearestRoadPlaceId(poi_partition, bbox) INTO parent_place_id;
|
||||
--DEBUG: RAISE WARNING 'Checked for nearest way (%)', parent_place_id;
|
||||
ELSE
|
||||
-- for larger features simply find the area with the largest rank that
|
||||
-- contains the bbox, only use addressable features
|
||||
FOR location IN
|
||||
SELECT place_id FROM placex
|
||||
WHERE bbox @ geometry AND _ST_Covers(geometry, ST_Centroid(bbox))
|
||||
AND rank_address between 5 and 25
|
||||
ORDER BY rank_address desc
|
||||
LOOP
|
||||
RETURN location.place_id;
|
||||
END LOOP;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
RETURN parent_place_id;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql STABLE;
|
||||
|
||||
-- Try to find a linked place for the given object.
|
||||
CREATE OR REPLACE FUNCTION find_linked_place(bnd placex)
|
||||
RETURNS placex
|
||||
AS $$
|
||||
DECLARE
|
||||
relation_members TEXT[];
|
||||
rel_member RECORD;
|
||||
linked_placex placex%ROWTYPE;
|
||||
bnd_name TEXT;
|
||||
BEGIN
|
||||
IF bnd.rank_search >= 26 or bnd.rank_address = 0
|
||||
or ST_GeometryType(bnd.geometry) NOT IN ('ST_Polygon','ST_MultiPolygon')
|
||||
THEN
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
IF bnd.osm_type = 'R' THEN
|
||||
-- see if we have any special relation members
|
||||
SELECT members FROM planet_osm_rels WHERE id = bnd.osm_id INTO relation_members;
|
||||
--DEBUG: RAISE WARNING 'Got relation members';
|
||||
|
||||
-- Search for relation members with role 'lable'.
|
||||
IF relation_members IS NOT NULL THEN
|
||||
FOR rel_member IN
|
||||
SELECT get_rel_node_members(relation_members, ARRAY['label']) as member
|
||||
LOOP
|
||||
--DEBUG: RAISE WARNING 'Found label member %', rel_member.member;
|
||||
|
||||
FOR linked_placex IN
|
||||
SELECT * from placex
|
||||
WHERE osm_type = 'N' and osm_id = rel_member.member
|
||||
and class = 'place'
|
||||
LOOP
|
||||
--DEBUG: RAISE WARNING 'Linked label member';
|
||||
RETURN linked_placex;
|
||||
END LOOP;
|
||||
|
||||
END LOOP;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
IF bnd.name ? 'name' THEN
|
||||
bnd_name := make_standard_name(bnd.name->'name');
|
||||
IF bnd_name = '' THEN
|
||||
bnd_name := NULL;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- If extratags has a place tag, look for linked nodes by their place type.
|
||||
-- Area and node still have to have the same name.
|
||||
IF bnd.extratags ? 'place' and bnd_name is not null THEN
|
||||
FOR linked_placex IN
|
||||
SELECT * FROM placex
|
||||
WHERE make_standard_name(name->'name') = bnd_name
|
||||
AND placex.class = 'place' AND placex.type = bnd.extratags->'place'
|
||||
AND placex.osm_type = 'N'
|
||||
AND placex.rank_search < 26 -- needed to select the right index
|
||||
AND _st_covers(bnd.geometry, placex.geometry)
|
||||
LOOP
|
||||
--DEBUG: RAISE WARNING 'Found type-matching place node %', linked_placex.osm_id;
|
||||
RETURN linked_placex;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
IF bnd.extratags ? 'wikidata' THEN
|
||||
FOR linked_placex IN
|
||||
SELECT * FROM placex
|
||||
WHERE placex.class = 'place' AND placex.osm_type = 'N'
|
||||
AND placex.extratags ? 'wikidata' -- needed to select right index
|
||||
AND placex.extratags->'wikidata' = bnd.extratags->'wikidata'
|
||||
AND placex.rank_search < 26
|
||||
AND _st_covers(bnd.geometry, placex.geometry)
|
||||
ORDER BY make_standard_name(name->'name') = bnd_name desc
|
||||
LOOP
|
||||
--DEBUG: RAISE WARNING 'Found wikidata-matching place node %', linked_placex.osm_id;
|
||||
RETURN linked_placex;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
-- Name searches can be done for ways as well as relations
|
||||
IF bnd_name is not null THEN
|
||||
--DEBUG: RAISE WARNING 'Looking for nodes with matching names';
|
||||
FOR linked_placex IN
|
||||
SELECT placex.* from placex
|
||||
WHERE make_standard_name(name->'name') = bnd_name
|
||||
AND ((bnd.rank_address > 0 and placex.rank_address = bnd.rank_address)
|
||||
OR (bnd.rank_address = 0 and placex.rank_search = bnd.rank_search))
|
||||
AND placex.osm_type = 'N'
|
||||
AND placex.rank_search < 26 -- needed to select the right index
|
||||
AND _st_covers(bnd.geometry, placex.geometry)
|
||||
LOOP
|
||||
--DEBUG: RAISE WARNING 'Found matching place node %', linked_placex.osm_id;
|
||||
RETURN linked_placex;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql STABLE;
|
||||
|
||||
|
||||
-- Insert address of a place into the place_addressline table.
|
||||
--
|
||||
-- \param obj_place_id Place_id of the place to compute the address for.
|
||||
-- \param partition Partition number where the place is in.
|
||||
-- \param maxrank Rank of the place. All address features must have
|
||||
-- a search rank lower than the given rank.
|
||||
-- \param address Address terms for the place.
|
||||
-- \param geoemtry Geometry to which the address objects should be close.
|
||||
--
|
||||
-- \retval parent_place_id Place_id of the address object that is the direct
|
||||
-- ancestor.
|
||||
-- \retval postcode Postcode computed from the address. This is the
|
||||
-- addr:postcode of one of the address objects. If
|
||||
-- more than one of has a postcode, the highest ranking
|
||||
-- one is used. May be NULL.
|
||||
-- \retval nameaddress_vector Search terms for the address. This is the sum
|
||||
-- of name terms of all address objects.
|
||||
CREATE OR REPLACE FUNCTION insert_addresslines(obj_place_id BIGINT,
|
||||
partition SMALLINT,
|
||||
maxrank SMALLINT,
|
||||
address HSTORE,
|
||||
geometry GEOMETRY,
|
||||
OUT parent_place_id BIGINT,
|
||||
OUT postcode TEXT,
|
||||
OUT nameaddress_vector INT[])
|
||||
AS $$
|
||||
DECLARE
|
||||
current_rank_address INTEGER := 0;
|
||||
location_distance FLOAT := 0;
|
||||
location_parent GEOMETRY := NULL;
|
||||
parent_place_id_rank SMALLINT := 0;
|
||||
|
||||
location_isaddress BOOLEAN;
|
||||
|
||||
address_havelevel BOOLEAN[];
|
||||
location_keywords INT[];
|
||||
|
||||
location RECORD;
|
||||
addr_item RECORD;
|
||||
|
||||
isin_tokens INT[];
|
||||
isin TEXT[];
|
||||
BEGIN
|
||||
parent_place_id := 0;
|
||||
nameaddress_vector := '{}'::int[];
|
||||
isin_tokens := '{}'::int[];
|
||||
|
||||
---- convert address store to array of tokenids
|
||||
IF address IS NOT NULL THEN
|
||||
FOR addr_item IN SELECT * FROM each(address)
|
||||
LOOP
|
||||
IF addr_item.key IN ('city', 'tiger:county', 'state', 'suburb', 'province',
|
||||
'district', 'region', 'county', 'municipality',
|
||||
'hamlet', 'village', 'subdistrict', 'town',
|
||||
'neighbourhood', 'quarter', 'parish')
|
||||
THEN
|
||||
isin_tokens := array_merge(isin_tokens,
|
||||
word_ids_from_name(addr_item.value));
|
||||
IF NOT %REVERSE-ONLY% THEN
|
||||
nameaddress_vector := array_merge(nameaddress_vector,
|
||||
addr_ids_from_name(addr_item.value));
|
||||
END IF;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
IF address ? 'is_in' THEN
|
||||
-- is_in items need splitting
|
||||
isin := regexp_split_to_array(address->'is_in', E'[;,]');
|
||||
IF array_upper(isin, 1) IS NOT NULL THEN
|
||||
FOR i IN 1..array_upper(isin, 1) LOOP
|
||||
isin_tokens := array_merge(isin_tokens,
|
||||
word_ids_from_name(isin[i]));
|
||||
|
||||
-- merge word into address vector
|
||||
IF NOT %REVERSE-ONLY% THEN
|
||||
nameaddress_vector := array_merge(nameaddress_vector,
|
||||
addr_ids_from_name(isin[i]));
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
IF NOT %REVERSE-ONLY% THEN
|
||||
nameaddress_vector := array_merge(nameaddress_vector, isin_tokens);
|
||||
END IF;
|
||||
|
||||
---- now compute the address terms
|
||||
FOR i IN 1..28 LOOP
|
||||
address_havelevel[i] := false;
|
||||
END LOOP;
|
||||
|
||||
FOR location IN
|
||||
SELECT * FROM getNearFeatures(partition, geometry, maxrank, isin_tokens)
|
||||
LOOP
|
||||
IF location.rank_address != current_rank_address THEN
|
||||
current_rank_address := location.rank_address;
|
||||
IF location.isguess THEN
|
||||
location_distance := location.distance * 1.5;
|
||||
ELSE
|
||||
IF location.rank_address <= 12 THEN
|
||||
-- for county and above, if we have an area consider that exact
|
||||
-- (It would be nice to relax the constraint for places close to
|
||||
-- the boundary but we'd need the exact geometry for that. Too
|
||||
-- expensive.)
|
||||
location_distance = 0;
|
||||
ELSE
|
||||
-- Below county level remain slightly fuzzy.
|
||||
location_distance := location.distance * 0.5;
|
||||
END IF;
|
||||
END IF;
|
||||
ELSE
|
||||
CONTINUE WHEN location.keywords <@ location_keywords;
|
||||
END IF;
|
||||
|
||||
IF location.distance < location_distance OR NOT location.isguess THEN
|
||||
location_keywords := location.keywords;
|
||||
|
||||
location_isaddress := NOT address_havelevel[location.rank_address];
|
||||
--DEBUG: RAISE WARNING 'should be address: %, is guess: %, rank: %', location_isaddress, location.isguess, location.rank_address;
|
||||
IF location_isaddress AND location.isguess AND location_parent IS NOT NULL THEN
|
||||
location_isaddress := ST_Contains(location_parent, location.centroid);
|
||||
END IF;
|
||||
|
||||
--DEBUG: RAISE WARNING '% isaddress: %', location.place_id, location_isaddress;
|
||||
-- Add it to the list of search terms
|
||||
IF NOT %REVERSE-ONLY% THEN
|
||||
nameaddress_vector := array_merge(nameaddress_vector,
|
||||
location.keywords::integer[]);
|
||||
END IF;
|
||||
|
||||
INSERT INTO place_addressline (place_id, address_place_id, fromarea,
|
||||
isaddress, distance, cached_rank_address)
|
||||
VALUES (obj_place_id, location.place_id, true,
|
||||
location_isaddress, location.distance, location.rank_address);
|
||||
|
||||
IF location_isaddress THEN
|
||||
-- add postcode if we have one
|
||||
-- (If multiple postcodes are available, we end up with the highest ranking one.)
|
||||
IF location.postcode is not null THEN
|
||||
postcode = location.postcode;
|
||||
END IF;
|
||||
|
||||
address_havelevel[location.rank_address] := true;
|
||||
-- add a hack against postcode ranks
|
||||
IF NOT location.isguess
|
||||
AND location.rank_address != 11 AND location.rank_address != 5
|
||||
THEN
|
||||
SELECT p.geometry FROM placex p
|
||||
WHERE p.place_id = location.place_id INTO location_parent;
|
||||
END IF;
|
||||
|
||||
IF location.rank_address > parent_place_id_rank THEN
|
||||
parent_place_id = location.place_id;
|
||||
parent_place_id_rank = location.rank_address;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
END LOOP;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION placex_insert()
|
||||
RETURNS TRIGGER
|
||||
AS $$
|
||||
DECLARE
|
||||
postcode TEXT;
|
||||
result BOOLEAN;
|
||||
is_area BOOLEAN;
|
||||
country_code VARCHAR(2);
|
||||
diameter FLOAT;
|
||||
classtable TEXT;
|
||||
BEGIN
|
||||
--DEBUG: RAISE WARNING '% % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
|
||||
|
||||
NEW.place_id := nextval('seq_place');
|
||||
NEW.indexed_status := 1; --STATUS_NEW
|
||||
|
||||
NEW.country_code := lower(get_country_code(NEW.geometry));
|
||||
|
||||
NEW.partition := get_partition(NEW.country_code);
|
||||
NEW.geometry_sector := geometry_sector(NEW.partition, NEW.geometry);
|
||||
|
||||
IF NEW.osm_type = 'X' THEN
|
||||
-- E'X'ternal records should already be in the right format so do nothing
|
||||
ELSE
|
||||
is_area := ST_GeometryType(NEW.geometry) IN ('ST_Polygon','ST_MultiPolygon');
|
||||
|
||||
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
|
||||
-- most likely just a part of a multipolygon postcode boundary, throw it away
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
NEW.name := hstore('ref', NEW.address->'postcode');
|
||||
|
||||
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;
|
||||
END IF;
|
||||
|
||||
SELECT * INTO NEW.rank_search, NEW.rank_address
|
||||
FROM compute_place_rank(NEW.country_code,
|
||||
CASE WHEN is_area THEN 'A' ELSE NEW.osm_type END,
|
||||
NEW.class, NEW.type, NEW.admin_level,
|
||||
(NEW.extratags->'capital') = 'yes',
|
||||
NEW.address->'postcode');
|
||||
|
||||
-- a country code make no sense below rank 4 (country)
|
||||
IF NEW.rank_search < 4 THEN
|
||||
NEW.country_code := NULL;
|
||||
END IF;
|
||||
|
||||
END IF;
|
||||
|
||||
--DEBUG: RAISE WARNING 'placex_insert:END: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
|
||||
|
||||
RETURN NEW; -- %DIFFUPDATES% The following is not needed until doing diff updates, and slows the main index process down
|
||||
|
||||
IF NEW.osm_type = 'N' and NEW.rank_search > 28 THEN
|
||||
-- might be part of an interpolation
|
||||
result := osmline_reinsert(NEW.osm_id, NEW.geometry);
|
||||
ELSEIF NEW.rank_address > 0 THEN
|
||||
IF (ST_GeometryType(NEW.geometry) in ('ST_Polygon','ST_MultiPolygon') AND ST_IsValid(NEW.geometry)) THEN
|
||||
-- Performance: We just can't handle re-indexing for country level changes
|
||||
IF st_area(NEW.geometry) < 1 THEN
|
||||
-- mark items within the geometry for re-indexing
|
||||
-- RAISE WARNING 'placex poly insert: % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type;
|
||||
|
||||
-- work around bug in postgis, this may have been fixed in 2.0.0 (see http://trac.osgeo.org/postgis/ticket/547)
|
||||
update placex set indexed_status = 2 where (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry))
|
||||
AND rank_search > NEW.rank_search and indexed_status = 0 and ST_geometrytype(placex.geometry) = 'ST_Point' and (rank_search < 28 or name is not null or (NEW.rank_search >= 16 and address ? 'place'));
|
||||
update placex set indexed_status = 2 where (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry))
|
||||
AND rank_search > NEW.rank_search and indexed_status = 0 and ST_geometrytype(placex.geometry) != 'ST_Point' and (rank_search < 28 or name is not null or (NEW.rank_search >= 16 and address ? 'place'));
|
||||
END IF;
|
||||
ELSE
|
||||
-- mark nearby items for re-indexing, where 'nearby' depends on the features rank_search and is a complete guess :(
|
||||
diameter := update_place_diameter(NEW.rank_search);
|
||||
IF diameter > 0 THEN
|
||||
-- RAISE WARNING 'placex point insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,diameter;
|
||||
IF NEW.rank_search >= 26 THEN
|
||||
-- roads may cause reparenting for >27 rank places
|
||||
update placex set indexed_status = 2 where indexed_status = 0 and rank_search > NEW.rank_search and ST_DWithin(placex.geometry, NEW.geometry, diameter);
|
||||
-- reparenting also for OSM Interpolation Lines (and for Tiger?)
|
||||
update location_property_osmline set indexed_status = 2 where indexed_status = 0 and ST_DWithin(location_property_osmline.linegeo, NEW.geometry, diameter);
|
||||
ELSEIF NEW.rank_search >= 16 THEN
|
||||
-- up to rank 16, street-less addresses may need reparenting
|
||||
update placex set indexed_status = 2 where indexed_status = 0 and rank_search > NEW.rank_search and ST_DWithin(placex.geometry, NEW.geometry, diameter) and (rank_search < 28 or name is not null or address ? 'place');
|
||||
ELSE
|
||||
-- for all other places the search terms may change as well
|
||||
update placex set indexed_status = 2 where indexed_status = 0 and rank_search > NEW.rank_search and ST_DWithin(placex.geometry, NEW.geometry, diameter) and (rank_search < 28 or name is not null);
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
|
||||
-- add to tables for special search
|
||||
-- Note: won't work on initial import because the classtype tables
|
||||
-- do not yet exist. It won't hurt either.
|
||||
classtable := 'place_classtype_' || NEW.class || '_' || NEW.type;
|
||||
SELECT count(*)>0 FROM pg_tables WHERE tablename = classtable and schemaname = current_schema() INTO result;
|
||||
IF result THEN
|
||||
EXECUTE 'INSERT INTO ' || classtable::regclass || ' (place_id, centroid) VALUES ($1,$2)'
|
||||
USING NEW.place_id, ST_Centroid(NEW.geometry);
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION get_parent_address_level(geom GEOMETRY, in_level SMALLINT)
|
||||
RETURNS SMALLINT
|
||||
AS $$
|
||||
DECLARE
|
||||
address_rank SMALLINT;
|
||||
BEGIN
|
||||
IF in_level <= 3 or in_level > 15 THEN
|
||||
address_rank := 3;
|
||||
ELSE
|
||||
SELECT rank_address INTO address_rank
|
||||
FROM placex
|
||||
WHERE osm_type = 'R' and class = 'boundary' and type = 'administrative'
|
||||
and admin_level < in_level
|
||||
and geometry && geom and ST_Covers(geometry, geom)
|
||||
ORDER BY admin_level desc LIMIT 1;
|
||||
END IF;
|
||||
|
||||
IF address_rank is NULL or address_rank <= 3 THEN
|
||||
RETURN 3;
|
||||
END IF;
|
||||
|
||||
RETURN address_rank;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION placex_update()
|
||||
RETURNS TRIGGER
|
||||
AS $$
|
||||
DECLARE
|
||||
i INTEGER;
|
||||
location RECORD;
|
||||
relation_members TEXT[];
|
||||
|
||||
centroid GEOMETRY;
|
||||
parent_address_level SMALLINT;
|
||||
|
||||
addr_street TEXT;
|
||||
addr_place TEXT;
|
||||
|
||||
name_vector INTEGER[];
|
||||
nameaddress_vector INTEGER[];
|
||||
|
||||
linked_node_id BIGINT;
|
||||
linked_importance FLOAT;
|
||||
linked_wikipedia TEXT;
|
||||
|
||||
result BOOLEAN;
|
||||
BEGIN
|
||||
-- deferred delete
|
||||
IF OLD.indexed_status = 100 THEN
|
||||
--DEBUG: RAISE WARNING 'placex_update delete % %',NEW.osm_type,NEW.osm_id;
|
||||
delete from placex where place_id = OLD.place_id;
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
--DEBUG: RAISE WARNING 'placex_update % % (%)',NEW.osm_type,NEW.osm_id,NEW.place_id;
|
||||
|
||||
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);
|
||||
DELETE FROM place_addressline WHERE place_id = NEW.place_id;
|
||||
result := deleteRoad(NEW.partition, NEW.place_id);
|
||||
result := deleteLocationArea(NEW.partition, NEW.place_id, NEW.rank_search);
|
||||
UPDATE placex set linked_place_id = null, indexed_status = 2
|
||||
where linked_place_id = NEW.place_id;
|
||||
-- update not necessary for osmline, cause linked_place_id does not exist
|
||||
|
||||
IF NEW.linked_place_id is not null THEN
|
||||
--DEBUG: RAISE WARNING 'place already linked to %', NEW.linked_place_id;
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
-- recompute the ranks, they might change when linking changes
|
||||
SELECT * INTO NEW.rank_search, NEW.rank_address
|
||||
FROM compute_place_rank(NEW.country_code,
|
||||
CASE WHEN ST_GeometryType(NEW.geometry)
|
||||
IN ('ST_Polygon','ST_MultiPolygon')
|
||||
THEN 'A' ELSE NEW.osm_type END,
|
||||
NEW.class, NEW.type, NEW.admin_level,
|
||||
(NEW.extratags->'capital') = 'yes',
|
||||
NEW.address->'postcode');
|
||||
|
||||
|
||||
--DEBUG: RAISE WARNING 'Copy over address tags';
|
||||
-- housenumber is a computed field, so start with an empty value
|
||||
NEW.housenumber := NULL;
|
||||
IF NEW.address is not NULL THEN
|
||||
IF NEW.address ? 'conscriptionnumber' THEN
|
||||
i := getorcreate_housenumber_id(make_standard_name(NEW.address->'conscriptionnumber'));
|
||||
IF NEW.address ? 'streetnumber' THEN
|
||||
i := getorcreate_housenumber_id(make_standard_name(NEW.address->'streetnumber'));
|
||||
NEW.housenumber := (NEW.address->'conscriptionnumber') || '/' || (NEW.address->'streetnumber');
|
||||
ELSE
|
||||
NEW.housenumber := NEW.address->'conscriptionnumber';
|
||||
END IF;
|
||||
ELSEIF NEW.address ? 'streetnumber' THEN
|
||||
NEW.housenumber := NEW.address->'streetnumber';
|
||||
i := getorcreate_housenumber_id(make_standard_name(NEW.address->'streetnumber'));
|
||||
ELSEIF NEW.address ? 'housenumber' THEN
|
||||
NEW.housenumber := NEW.address->'housenumber';
|
||||
i := getorcreate_housenumber_id(make_standard_name(NEW.housenumber));
|
||||
END IF;
|
||||
|
||||
addr_street := NEW.address->'street';
|
||||
addr_place := NEW.address->'place';
|
||||
|
||||
IF NEW.address ? 'postcode' and NEW.address->'postcode' not similar to '%(,|;)%' THEN
|
||||
i := getorcreate_postcode_id(NEW.address->'postcode');
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Speed up searches - just use the centroid of the feature
|
||||
-- cheaper but less acurate
|
||||
NEW.centroid := ST_PointOnSurface(NEW.geometry);
|
||||
--DEBUG: RAISE WARNING 'Computing preliminary centroid at %',ST_AsText(NEW.centroid);
|
||||
|
||||
NEW.postcode := null;
|
||||
|
||||
-- recalculate country and partition
|
||||
IF NEW.rank_search = 4 AND NEW.address is not NULL AND NEW.address ? 'country' THEN
|
||||
-- for countries, believe the mapped country code,
|
||||
-- so that we remain in the right partition if the boundaries
|
||||
-- suddenly expand.
|
||||
NEW.country_code := lower(NEW.address->'country');
|
||||
NEW.partition := get_partition(lower(NEW.country_code));
|
||||
IF NEW.partition = 0 THEN
|
||||
NEW.country_code := lower(get_country_code(NEW.centroid));
|
||||
NEW.partition := get_partition(NEW.country_code);
|
||||
END IF;
|
||||
ELSE
|
||||
IF NEW.rank_search >= 4 THEN
|
||||
NEW.country_code := lower(get_country_code(NEW.centroid));
|
||||
ELSE
|
||||
NEW.country_code := NULL;
|
||||
END IF;
|
||||
NEW.partition := get_partition(NEW.country_code);
|
||||
END IF;
|
||||
--DEBUG: RAISE WARNING 'Country updated: "%"', NEW.country_code;
|
||||
|
||||
-- waterway ways are linked when they are part of a relation and have the same class/type
|
||||
IF NEW.osm_type = 'R' and NEW.class = 'waterway' THEN
|
||||
FOR relation_members IN select members from planet_osm_rels r where r.id = NEW.osm_id and r.parts != array[]::bigint[]
|
||||
LOOP
|
||||
FOR i IN 1..array_upper(relation_members, 1) BY 2 LOOP
|
||||
IF relation_members[i+1] in ('', 'main_stream', 'side_stream') AND substring(relation_members[i],1,1) = 'w' THEN
|
||||
--DEBUG: RAISE WARNING 'waterway parent %, child %/%', NEW.osm_id, i, relation_members[i];
|
||||
FOR linked_node_id IN SELECT place_id FROM placex
|
||||
WHERE osm_type = 'W' and osm_id = substring(relation_members[i],2,200)::bigint
|
||||
and class = NEW.class and type in ('river', 'stream', 'canal', 'drain', 'ditch')
|
||||
and ( relation_members[i+1] != 'side_stream' or NEW.name->'name' = name->'name')
|
||||
LOOP
|
||||
UPDATE placex SET linked_place_id = NEW.place_id WHERE place_id = linked_node_id;
|
||||
IF NOT %REVERSE-ONLY% THEN
|
||||
DELETE FROM search_name WHERE place_id = linked_node_id;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END LOOP;
|
||||
--DEBUG: RAISE WARNING 'Waterway processed';
|
||||
END IF;
|
||||
|
||||
NEW.importance := null;
|
||||
SELECT wikipedia, importance
|
||||
FROM compute_importance(NEW.extratags, NEW.country_code, NEW.osm_type, NEW.osm_id)
|
||||
INTO NEW.wikipedia,NEW.importance;
|
||||
|
||||
--DEBUG: RAISE WARNING 'Importance computed from wikipedia: %', NEW.importance;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- For low level elements we inherit from our parent road
|
||||
IF (NEW.rank_search > 27 OR (NEW.type = 'postcode' AND NEW.rank_search = 25)) THEN
|
||||
|
||||
--DEBUG: RAISE WARNING 'finding street for % %', NEW.osm_type, NEW.osm_id;
|
||||
NEW.parent_place_id := null;
|
||||
|
||||
-- if we have a POI and there is no address information,
|
||||
-- see if we can get it from a surrounding building
|
||||
IF NEW.osm_type = 'N' AND addr_street IS NULL AND addr_place IS NULL
|
||||
AND NEW.housenumber IS NULL THEN
|
||||
FOR location IN
|
||||
-- The additional && condition works around the misguided query
|
||||
-- planner of postgis 3.0.
|
||||
SELECT address from placex where ST_Covers(geometry, NEW.centroid)
|
||||
and geometry && NEW.centroid
|
||||
and (address ? 'housenumber' or address ? 'street' or address ? 'place')
|
||||
and rank_search > 28 AND ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon')
|
||||
limit 1
|
||||
LOOP
|
||||
NEW.housenumber := location.address->'housenumber';
|
||||
addr_street := location.address->'street';
|
||||
addr_place := location.address->'place';
|
||||
--DEBUG: RAISE WARNING 'Found surrounding building % %', location.osm_type, location.osm_id;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
-- We have to find our parent road.
|
||||
NEW.parent_place_id := find_parent_for_poi(NEW.osm_type, NEW.osm_id,
|
||||
NEW.partition,
|
||||
ST_Envelope(NEW.geometry),
|
||||
addr_street, addr_place);
|
||||
|
||||
-- If we found the road take a shortcut here.
|
||||
-- Otherwise fall back to the full address getting method below.
|
||||
IF NEW.parent_place_id is not null THEN
|
||||
|
||||
-- Get the details of the parent road
|
||||
SELECT p.country_code, p.postcode FROM placex p
|
||||
WHERE p.place_id = NEW.parent_place_id INTO location;
|
||||
|
||||
NEW.country_code := location.country_code;
|
||||
--DEBUG: RAISE WARNING 'Got parent details from search name';
|
||||
|
||||
-- determine postcode
|
||||
IF NEW.address is not null AND NEW.address ? 'postcode' THEN
|
||||
NEW.postcode = upper(trim(NEW.address->'postcode'));
|
||||
ELSE
|
||||
NEW.postcode := location.postcode;
|
||||
END IF;
|
||||
IF NEW.postcode is null THEN
|
||||
NEW.postcode := get_nearest_postcode(NEW.country_code, NEW.geometry);
|
||||
END IF;
|
||||
|
||||
-- If there is no name it isn't searchable, don't bother to create a search record
|
||||
IF NEW.name is NULL THEN
|
||||
--DEBUG: RAISE WARNING 'Not a searchable place % %', NEW.osm_type, NEW.osm_id;
|
||||
return NEW;
|
||||
END IF;
|
||||
|
||||
NEW.name := add_default_place_name(NEW.country_code, NEW.name);
|
||||
name_vector := make_keywords(NEW.name);
|
||||
|
||||
-- Performance, it would be more acurate to do all the rest of the import
|
||||
-- process but it takes too long
|
||||
-- Just be happy with inheriting from parent road only
|
||||
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);
|
||||
--DEBUG: RAISE WARNING 'Place added to location table';
|
||||
END IF;
|
||||
|
||||
result := insertSearchName(NEW.partition, NEW.place_id, name_vector,
|
||||
NEW.rank_search, NEW.rank_address, NEW.geometry);
|
||||
|
||||
IF NOT %REVERSE-ONLY% THEN
|
||||
-- Merge address from parent
|
||||
SELECT array_merge(s.name_vector, s.nameaddress_vector)
|
||||
INTO nameaddress_vector
|
||||
FROM search_name s
|
||||
WHERE s.place_id = NEW.parent_place_id;
|
||||
|
||||
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, NEW.centroid);
|
||||
--DEBUG: RAISE WARNING 'Place added to search table';
|
||||
END IF;
|
||||
|
||||
return NEW;
|
||||
END IF;
|
||||
|
||||
END IF;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Full indexing
|
||||
--DEBUG: RAISE WARNING 'Using full index mode for % %', NEW.osm_type, NEW.osm_id;
|
||||
SELECT * INTO location FROM find_linked_place(NEW);
|
||||
IF location.place_id is not null THEN
|
||||
--DEBUG: RAISE WARNING 'Linked %', location;
|
||||
|
||||
-- Use the linked point as the centre point of the geometry,
|
||||
-- but only if it is within the area of the boundary.
|
||||
centroid := coalesce(location.centroid, ST_Centroid(location.geometry));
|
||||
IF centroid is not NULL AND ST_Within(centroid, NEW.geometry) THEN
|
||||
NEW.centroid := centroid;
|
||||
END IF;
|
||||
|
||||
-- Use the address rank of the linked place, if it has one
|
||||
parent_address_level := get_parent_address_level(NEW.geometry, NEW.admin_level);
|
||||
--DEBUG: RAISE WARNING 'parent address: % rank address: %', parent_address_level, location.rank_address;
|
||||
IF location.rank_address > parent_address_level
|
||||
and location.rank_address < 26
|
||||
THEN
|
||||
NEW.rank_address := location.rank_address;
|
||||
END IF;
|
||||
|
||||
-- merge in the label name
|
||||
IF NOT location.name IS NULL THEN
|
||||
NEW.name := location.name || NEW.name;
|
||||
END IF;
|
||||
|
||||
-- merge in extra tags
|
||||
NEW.extratags := hstore('linked_' || location.class, location.type)
|
||||
|| coalesce(location.extratags, ''::hstore)
|
||||
|| coalesce(NEW.extratags, ''::hstore);
|
||||
|
||||
-- mark the linked place (excludes from search results)
|
||||
UPDATE placex set linked_place_id = NEW.place_id
|
||||
WHERE place_id = location.place_id;
|
||||
-- ensure that those places are not found anymore
|
||||
IF NOT %REVERSE-ONLY% THEN
|
||||
DELETE FROM search_name WHERE place_id = location.place_id;
|
||||
END IF;
|
||||
PERFORM deleteLocationArea(NEW.partition, location.place_id, NEW.rank_search);
|
||||
|
||||
SELECT wikipedia, importance
|
||||
FROM compute_importance(location.extratags, NEW.country_code,
|
||||
'N', location.osm_id)
|
||||
INTO linked_wikipedia,linked_importance;
|
||||
|
||||
-- Use the maximum importance if one could be computed from the linked object.
|
||||
IF linked_importance is not null AND
|
||||
(NEW.importance is null or NEW.importance < linked_importance)
|
||||
THEN
|
||||
NEW.importance = linked_importance;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Initialise the name vector using our name
|
||||
NEW.name := add_default_place_name(NEW.country_code, NEW.name);
|
||||
name_vector := make_keywords(NEW.name);
|
||||
|
||||
-- make sure all names are in the word table
|
||||
IF NEW.admin_level = 2
|
||||
AND NEW.class = 'boundary' AND NEW.type = 'administrative'
|
||||
AND NEW.country_code IS NOT NULL AND NEW.osm_type = 'R'
|
||||
THEN
|
||||
PERFORM create_country(NEW.name, lower(NEW.country_code));
|
||||
--DEBUG: RAISE WARNING 'Country names updated';
|
||||
END IF;
|
||||
|
||||
SELECT * FROM insert_addresslines(NEW.place_id, NEW.partition,
|
||||
NEW.rank_search, NEW.address,
|
||||
CASE WHEN NEW.rank_search >= 26
|
||||
AND NEW.rank_search < 30
|
||||
THEN NEW.geometry ELSE NEW.centroid END)
|
||||
INTO NEW.parent_place_id, NEW.postcode, nameaddress_vector;
|
||||
|
||||
--DEBUG: RAISE WARNING 'RETURN insert_addresslines: %, %, %', NEW.parent_place_id, NEW.postcode, nameaddress_vector;
|
||||
|
||||
IF NEW.address is not null AND NEW.address ? 'postcode'
|
||||
AND NEW.address->'postcode' not similar to '%(,|;)%' THEN
|
||||
NEW.postcode := upper(trim(NEW.address->'postcode'));
|
||||
END IF;
|
||||
|
||||
IF NEW.postcode is null AND NEW.rank_search > 8 THEN
|
||||
NEW.postcode := get_nearest_postcode(NEW.country_code, NEW.geometry);
|
||||
END IF;
|
||||
|
||||
-- if we have a name add this to the name search table
|
||||
IF NEW.name IS NOT NULL 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);
|
||||
--DEBUG: RAISE WARNING 'added to location (full)';
|
||||
END IF;
|
||||
|
||||
IF NEW.rank_search between 26 and 27 and NEW.class = 'highway' THEN
|
||||
result := insertLocationRoad(NEW.partition, NEW.place_id, NEW.country_code, NEW.geometry);
|
||||
--DEBUG: RAISE WARNING 'insert into road location table (full)';
|
||||
END IF;
|
||||
|
||||
result := insertSearchName(NEW.partition, NEW.place_id, name_vector,
|
||||
NEW.rank_search, NEW.rank_address, NEW.geometry);
|
||||
--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, NEW.centroid);
|
||||
END IF;
|
||||
|
||||
END IF;
|
||||
|
||||
--DEBUG: RAISE WARNING 'place update % % finsihed.', NEW.osm_type, NEW.osm_id;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION placex_delete()
|
||||
RETURNS TRIGGER
|
||||
AS $$
|
||||
DECLARE
|
||||
b BOOLEAN;
|
||||
classtable TEXT;
|
||||
BEGIN
|
||||
-- RAISE WARNING 'placex_delete % %',OLD.osm_type,OLD.osm_id;
|
||||
|
||||
update placex set linked_place_id = null, indexed_status = 2 where linked_place_id = OLD.place_id and indexed_status = 0;
|
||||
--DEBUG: RAISE WARNING 'placex_delete:01 % %',OLD.osm_type,OLD.osm_id;
|
||||
update placex set linked_place_id = null where linked_place_id = OLD.place_id;
|
||||
--DEBUG: RAISE WARNING 'placex_delete:02 % %',OLD.osm_type,OLD.osm_id;
|
||||
|
||||
IF OLD.rank_address < 30 THEN
|
||||
|
||||
-- mark everything linked to this place for re-indexing
|
||||
--DEBUG: RAISE WARNING 'placex_delete:03 % %',OLD.osm_type,OLD.osm_id;
|
||||
UPDATE placex set indexed_status = 2 from place_addressline where address_place_id = OLD.place_id
|
||||
and placex.place_id = place_addressline.place_id and indexed_status = 0 and place_addressline.isaddress;
|
||||
|
||||
--DEBUG: RAISE WARNING 'placex_delete:04 % %',OLD.osm_type,OLD.osm_id;
|
||||
DELETE FROM place_addressline where address_place_id = OLD.place_id;
|
||||
|
||||
--DEBUG: RAISE WARNING 'placex_delete:05 % %',OLD.osm_type,OLD.osm_id;
|
||||
b := deleteRoad(OLD.partition, OLD.place_id);
|
||||
|
||||
--DEBUG: RAISE WARNING 'placex_delete:06 % %',OLD.osm_type,OLD.osm_id;
|
||||
update placex set indexed_status = 2 where parent_place_id = OLD.place_id and indexed_status = 0;
|
||||
--DEBUG: RAISE WARNING 'placex_delete:07 % %',OLD.osm_type,OLD.osm_id;
|
||||
-- reparenting also for OSM Interpolation Lines (and for Tiger?)
|
||||
update location_property_osmline set indexed_status = 2 where indexed_status = 0 and parent_place_id = OLD.place_id;
|
||||
|
||||
END IF;
|
||||
|
||||
--DEBUG: RAISE WARNING 'placex_delete:08 % %',OLD.osm_type,OLD.osm_id;
|
||||
|
||||
IF OLD.rank_address < 26 THEN
|
||||
b := deleteLocationArea(OLD.partition, OLD.place_id, OLD.rank_search);
|
||||
END IF;
|
||||
|
||||
--DEBUG: RAISE WARNING 'placex_delete:09 % %',OLD.osm_type,OLD.osm_id;
|
||||
|
||||
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);
|
||||
END IF;
|
||||
|
||||
--DEBUG: RAISE WARNING 'placex_delete:10 % %',OLD.osm_type,OLD.osm_id;
|
||||
|
||||
DELETE FROM place_addressline where place_id = OLD.place_id;
|
||||
|
||||
--DEBUG: RAISE WARNING 'placex_delete:11 % %',OLD.osm_type,OLD.osm_id;
|
||||
|
||||
-- remove from tables for special search
|
||||
classtable := 'place_classtype_' || OLD.class || '_' || OLD.type;
|
||||
SELECT count(*)>0 FROM pg_tables WHERE tablename = classtable and schemaname = current_schema() INTO b;
|
||||
IF b THEN
|
||||
EXECUTE 'DELETE FROM ' || classtable::regclass || ' WHERE place_id = $1' USING OLD.place_id;
|
||||
END IF;
|
||||
|
||||
--DEBUG: RAISE WARNING 'placex_delete:12 % %',OLD.osm_type,OLD.osm_id;
|
||||
|
||||
RETURN OLD;
|
||||
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
40
sql/functions/postcode_triggers.sql
Normal file
40
sql/functions/postcode_triggers.sql
Normal file
@@ -0,0 +1,40 @@
|
||||
-- Trigger functions for location_postcode table.
|
||||
|
||||
|
||||
-- Trigger for updates of location_postcode
|
||||
--
|
||||
-- Computes the parent object the postcode most likely refers to.
|
||||
-- This will be the place that determines the address displayed when
|
||||
-- searching for this postcode.
|
||||
CREATE OR REPLACE FUNCTION postcode_update()
|
||||
RETURNS TRIGGER
|
||||
AS $$
|
||||
DECLARE
|
||||
partition SMALLINT;
|
||||
location RECORD;
|
||||
BEGIN
|
||||
IF NEW.indexed_status != 0 OR OLD.indexed_status = 0 THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
NEW.indexed_date = now();
|
||||
|
||||
partition := get_partition(NEW.country_code);
|
||||
|
||||
SELECT * FROM get_postcode_rank(NEW.country_code, NEW.postcode)
|
||||
INTO NEW.rank_search, NEW.rank_address;
|
||||
|
||||
NEW.parent_place_id = 0;
|
||||
FOR location IN
|
||||
SELECT place_id
|
||||
FROM getNearFeatures(partition, NEW.geometry, NEW.rank_search, '{}'::int[])
|
||||
WHERE NOT isguess ORDER BY rank_address DESC LIMIT 1
|
||||
LOOP
|
||||
NEW.parent_place_id = location.place_id;
|
||||
END LOOP;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
192
sql/functions/ranking.sql
Normal file
192
sql/functions/ranking.sql
Normal file
@@ -0,0 +1,192 @@
|
||||
-- Functions related to search and address ranks
|
||||
|
||||
-- Return an approximate search radius according to the search rank.
|
||||
CREATE OR REPLACE FUNCTION reverse_place_diameter(rank_search SMALLINT)
|
||||
RETURNS FLOAT
|
||||
AS $$
|
||||
BEGIN
|
||||
IF rank_search <= 4 THEN
|
||||
RETURN 5.0;
|
||||
ELSIF rank_search <= 8 THEN
|
||||
RETURN 1.8;
|
||||
ELSIF rank_search <= 12 THEN
|
||||
RETURN 0.6;
|
||||
ELSIF rank_search <= 17 THEN
|
||||
RETURN 0.16;
|
||||
ELSIF rank_search <= 18 THEN
|
||||
RETURN 0.08;
|
||||
ELSIF rank_search <= 19 THEN
|
||||
RETURN 0.04;
|
||||
END IF;
|
||||
|
||||
RETURN 0.02;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
|
||||
-- Return an approximate update radius according to the search rank.
|
||||
CREATE OR REPLACE FUNCTION update_place_diameter(rank_search SMALLINT)
|
||||
RETURNS FLOAT
|
||||
AS $$
|
||||
BEGIN
|
||||
-- postcodes
|
||||
IF rank_search = 11 or rank_search = 5 THEN
|
||||
RETURN 0.05;
|
||||
-- anything higher than city is effectively ignored (polygon required)
|
||||
ELSIF rank_search < 16 THEN
|
||||
RETURN 0;
|
||||
ELSIF rank_search < 18 THEN
|
||||
RETURN 0.1;
|
||||
ELSIF rank_search < 20 THEN
|
||||
RETURN 0.05;
|
||||
ELSIF rank_search = 21 THEN
|
||||
RETURN 0.001;
|
||||
ELSIF rank_search < 24 THEN
|
||||
RETURN 0.02;
|
||||
ELSIF rank_search < 26 THEN
|
||||
RETURN 0.002;
|
||||
ELSIF rank_search < 28 THEN
|
||||
RETURN 0.001;
|
||||
END IF;
|
||||
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
|
||||
-- Guess a ranking for postcodes from country and postcode format.
|
||||
CREATE OR REPLACE FUNCTION get_postcode_rank(country_code VARCHAR(2), postcode TEXT,
|
||||
OUT rank_search SMALLINT,
|
||||
OUT rank_address SMALLINT)
|
||||
AS $$
|
||||
DECLARE
|
||||
part TEXT;
|
||||
BEGIN
|
||||
rank_search := 30;
|
||||
rank_address := 30;
|
||||
postcode := upper(postcode);
|
||||
|
||||
IF country_code = 'gb' THEN
|
||||
IF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9][A-Z][A-Z])$' THEN
|
||||
rank_search := 25;
|
||||
rank_address := 5;
|
||||
ELSEIF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z]? [0-9])$' THEN
|
||||
rank_search := 23;
|
||||
rank_address := 5;
|
||||
ELSEIF postcode ~ '^([A-Z][A-Z]?[0-9][0-9A-Z])$' THEN
|
||||
rank_search := 21;
|
||||
rank_address := 5;
|
||||
END IF;
|
||||
|
||||
ELSEIF country_code = 'sg' THEN
|
||||
IF postcode ~ '^([0-9]{6})$' THEN
|
||||
rank_search := 25;
|
||||
rank_address := 11;
|
||||
END IF;
|
||||
|
||||
ELSEIF country_code = 'de' THEN
|
||||
IF postcode ~ '^([0-9]{5})$' THEN
|
||||
rank_search := 21;
|
||||
rank_address := 11;
|
||||
END IF;
|
||||
|
||||
ELSE
|
||||
-- Guess at the postcode format and coverage (!)
|
||||
IF postcode ~ '^[A-Z0-9]{1,5}$' THEN -- Probably too short to be very local
|
||||
rank_search := 21;
|
||||
rank_address := 11;
|
||||
ELSE
|
||||
-- Does it look splitable into and area and local code?
|
||||
part := substring(postcode from '^([- :A-Z0-9]+)([- :][A-Z0-9]+)$');
|
||||
|
||||
IF part IS NOT NULL THEN
|
||||
rank_search := 25;
|
||||
rank_address := 11;
|
||||
ELSEIF postcode ~ '^[- :A-Z0-9]{6,}$' THEN
|
||||
rank_search := 21;
|
||||
rank_address := 11;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
|
||||
-- Get standard search and address rank for an object.
|
||||
--
|
||||
-- \param country Two-letter country code where the object is in.
|
||||
-- \param extended_type OSM type (N, W, R) or area type (A).
|
||||
-- \param place_class Class (or tag key) of object.
|
||||
-- \param place_type Type (or tag value) of object.
|
||||
-- \param admin_level Value of admin_level tag.
|
||||
-- \param is_major If true, boost search rank by one.
|
||||
-- \param postcode Value of addr:postcode tag.
|
||||
-- \param[out] search_rank Computed search rank.
|
||||
-- \param[out] address_rank Computed address rank.
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION compute_place_rank(country VARCHAR(2),
|
||||
extended_type VARCHAR(1),
|
||||
place_class TEXT, place_type TEXT,
|
||||
admin_level SMALLINT,
|
||||
is_major BOOLEAN,
|
||||
postcode TEXT,
|
||||
OUT search_rank SMALLINT,
|
||||
OUT address_rank SMALLINT)
|
||||
AS $$
|
||||
DECLARE
|
||||
classtype TEXT;
|
||||
BEGIN
|
||||
IF place_class in ('place','boundary')
|
||||
and place_type in ('postcode','postal_code')
|
||||
THEN
|
||||
SELECT * INTO search_rank, address_rank
|
||||
FROM get_postcode_rank(country, postcode);
|
||||
|
||||
IF NOT extended_type = 'A' THEN
|
||||
address_rank := 0;
|
||||
END IF;
|
||||
ELSEIF extended_type = 'N' AND place_class = 'highway' THEN
|
||||
search_rank = 30;
|
||||
address_rank = 0;
|
||||
ELSEIF place_class = 'landuse' AND extended_type != 'A' THEN
|
||||
search_rank = 30;
|
||||
address_rank = 0;
|
||||
ELSE
|
||||
IF place_class = 'boundary' and place_type = 'administrative' THEN
|
||||
classtype = place_type || admin_level::TEXT;
|
||||
ELSE
|
||||
classtype = place_type;
|
||||
END IF;
|
||||
|
||||
SELECT l.rank_search, l.rank_address INTO search_rank, address_rank
|
||||
FROM address_levels l
|
||||
WHERE (l.country_code = country or l.country_code is NULL)
|
||||
AND l.class = place_class AND (l.type = classtype or l.type is NULL)
|
||||
ORDER BY l.country_code, l.class, l.type LIMIT 1;
|
||||
|
||||
IF search_rank is NULL THEN
|
||||
search_rank := 30;
|
||||
END IF;
|
||||
|
||||
IF address_rank is NULL THEN
|
||||
address_rank := 30;
|
||||
END IF;
|
||||
|
||||
-- some postcorrections
|
||||
IF place_class = 'waterway' AND extended_type = 'R' THEN
|
||||
-- Slightly promote waterway relations so that they are processed
|
||||
-- before their members.
|
||||
search_rank := search_rank - 1;
|
||||
END IF;
|
||||
|
||||
IF is_major THEN
|
||||
search_rank := search_rank - 1;
|
||||
END IF;
|
||||
END IF;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql IMMUTABLE;
|
||||
505
sql/functions/utils.sql
Normal file
505
sql/functions/utils.sql
Normal file
@@ -0,0 +1,505 @@
|
||||
-- Assorted helper functions for the triggers.
|
||||
|
||||
CREATE OR REPLACE FUNCTION geometry_sector(partition INTEGER, place geometry)
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
DECLARE
|
||||
NEWgeometry geometry;
|
||||
BEGIN
|
||||
-- RAISE WARNING '%',place;
|
||||
NEWgeometry := ST_PointOnSurface(place);
|
||||
RETURN (partition*1000000) + (500-ST_X(NEWgeometry)::integer)*1000 + (500-ST_Y(NEWgeometry)::integer);
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION array_merge(a INTEGER[], b INTEGER[])
|
||||
RETURNS INTEGER[]
|
||||
AS $$
|
||||
DECLARE
|
||||
i INTEGER;
|
||||
r INTEGER[];
|
||||
BEGIN
|
||||
IF array_upper(a, 1) IS NULL THEN
|
||||
RETURN b;
|
||||
END IF;
|
||||
IF array_upper(b, 1) IS NULL THEN
|
||||
RETURN a;
|
||||
END IF;
|
||||
r := a;
|
||||
FOR i IN 1..array_upper(b, 1) LOOP
|
||||
IF NOT (ARRAY[b[i]] <@ r) THEN
|
||||
r := r || b[i];
|
||||
END IF;
|
||||
END LOOP;
|
||||
RETURN r;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
-- Return the node members with a given label from a relation member list
|
||||
-- as a set.
|
||||
--
|
||||
-- \param members Member list in osm2pgsql middle format.
|
||||
-- \param memberLabels Array of labels to accept.
|
||||
--
|
||||
-- \returns Set of OSM ids of nodes that are found.
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION get_rel_node_members(members TEXT[],
|
||||
memberLabels TEXT[])
|
||||
RETURNS SETOF BIGINT
|
||||
AS $$
|
||||
DECLARE
|
||||
i INTEGER;
|
||||
BEGIN
|
||||
FOR i IN 1..ARRAY_UPPER(members,1) BY 2 LOOP
|
||||
IF members[i+1] = ANY(memberLabels)
|
||||
AND upper(substring(members[i], 1, 1))::char(1) = 'N'
|
||||
THEN
|
||||
RETURN NEXT substring(members[i], 2)::bigint;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
RETURN;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
-- Copy 'name' to or from the default language.
|
||||
--
|
||||
-- \param country_code Country code of the object being named.
|
||||
-- \param[inout] name List of names of the object.
|
||||
--
|
||||
-- If the country named by country_code has a single default language,
|
||||
-- then a `name` tag is copied to `name:<country_code>` if this tag does
|
||||
-- not yet exist and vice versa.
|
||||
CREATE OR REPLACE FUNCTION add_default_place_name(country_code VARCHAR(2),
|
||||
INOUT name HSTORE)
|
||||
AS $$
|
||||
DECLARE
|
||||
default_language VARCHAR(10);
|
||||
BEGIN
|
||||
IF name is not null AND array_upper(akeys(name),1) > 1 THEN
|
||||
default_language := get_country_language_code(country_code);
|
||||
IF default_language IS NOT NULL THEN
|
||||
IF name ? 'name' AND NOT name ? ('name:'||default_language) THEN
|
||||
name := name || hstore(('name:'||default_language), (name -> 'name'));
|
||||
ELSEIF name ? ('name:'||default_language) AND NOT name ? 'name' THEN
|
||||
name := name || hstore('name', (name -> ('name:'||default_language)));
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
|
||||
-- Find the nearest artificial postcode for the given geometry.
|
||||
-- TODO For areas there should not be more than two inside the geometry.
|
||||
CREATE OR REPLACE FUNCTION get_nearest_postcode(country VARCHAR(2), geom GEOMETRY)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
outcode TEXT;
|
||||
cnt INTEGER;
|
||||
BEGIN
|
||||
-- If the geometry is an area then only one postcode must be within
|
||||
-- that area, otherwise consider the area as not having a postcode.
|
||||
IF ST_GeometryType(geom) in ('ST_Polygon','ST_MultiPolygon') THEN
|
||||
SELECT min(postcode), count(*) FROM
|
||||
(SELECT postcode FROM location_postcode
|
||||
WHERE ST_Contains(geom, location_postcode.geometry) LIMIT 2) sub
|
||||
INTO outcode, cnt;
|
||||
|
||||
IF cnt = 1 THEN
|
||||
RETURN outcode;
|
||||
ELSE
|
||||
RETURN null;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
SELECT postcode FROM location_postcode
|
||||
WHERE ST_DWithin(geom, location_postcode.geometry, 0.05)
|
||||
AND location_postcode.country_code = country
|
||||
ORDER BY ST_Distance(geom, location_postcode.geometry) LIMIT 1
|
||||
INTO outcode;
|
||||
|
||||
RETURN outcode;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql STABLE;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION get_country_code(place geometry)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
place_centre GEOMETRY;
|
||||
nearcountry RECORD;
|
||||
BEGIN
|
||||
place_centre := ST_PointOnSurface(place);
|
||||
|
||||
-- RAISE WARNING 'get_country_code, start: %', ST_AsText(place_centre);
|
||||
|
||||
-- Try for a OSM polygon
|
||||
FOR nearcountry IN
|
||||
SELECT country_code from location_area_country
|
||||
WHERE country_code is not null and st_covers(geometry, place_centre) limit 1
|
||||
LOOP
|
||||
RETURN nearcountry.country_code;
|
||||
END LOOP;
|
||||
|
||||
-- RAISE WARNING 'osm fallback: %', ST_AsText(place_centre);
|
||||
|
||||
-- Try for OSM fallback data
|
||||
-- The order is to deal with places like HongKong that are 'states' within another polygon
|
||||
FOR nearcountry IN
|
||||
SELECT country_code from country_osm_grid
|
||||
WHERE st_covers(geometry, place_centre) order by area asc limit 1
|
||||
LOOP
|
||||
RETURN nearcountry.country_code;
|
||||
END LOOP;
|
||||
|
||||
-- RAISE WARNING 'near osm fallback: %', ST_AsText(place_centre);
|
||||
|
||||
--
|
||||
FOR nearcountry IN
|
||||
SELECT country_code from country_osm_grid
|
||||
WHERE st_dwithin(geometry, place_centre, 0.5)
|
||||
ORDER BY st_distance(geometry, place_centre) asc, area asc limit 1
|
||||
LOOP
|
||||
RETURN nearcountry.country_code;
|
||||
END LOOP;
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql STABLE;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION get_country_language_code(search_country_code VARCHAR(2))
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
nearcountry RECORD;
|
||||
BEGIN
|
||||
FOR nearcountry IN
|
||||
SELECT distinct country_default_language_code from country_name
|
||||
WHERE country_code = search_country_code limit 1
|
||||
LOOP
|
||||
RETURN lower(nearcountry.country_default_language_code);
|
||||
END LOOP;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql STABLE;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION get_partition(in_country_code VARCHAR(10))
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
DECLARE
|
||||
nearcountry RECORD;
|
||||
BEGIN
|
||||
FOR nearcountry IN
|
||||
SELECT partition from country_name where country_code = in_country_code
|
||||
LOOP
|
||||
RETURN nearcountry.partition;
|
||||
END LOOP;
|
||||
RETURN 0;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql STABLE;
|
||||
|
||||
|
||||
-- Find the parent of an address with addr:street/addr:place tag.
|
||||
--
|
||||
-- \param street Value of addr:street or NULL if tag is missing.
|
||||
-- \param place Value of addr:place or NULL if tag is missing.
|
||||
-- \param partition Partition where to search the parent.
|
||||
-- \param centroid Location of the address.
|
||||
--
|
||||
-- \return Place ID of the parent if one was found, NULL otherwise.
|
||||
CREATE OR REPLACE FUNCTION find_parent_for_address(street TEXT, place TEXT,
|
||||
partition SMALLINT,
|
||||
centroid GEOMETRY)
|
||||
RETURNS BIGINT
|
||||
AS $$
|
||||
DECLARE
|
||||
parent_place_id BIGINT;
|
||||
word_ids INTEGER[];
|
||||
BEGIN
|
||||
IF street is not null THEN
|
||||
-- Check for addr:street attributes
|
||||
-- Note that addr:street links can only be indexed, once the street itself is indexed
|
||||
word_ids := word_ids_from_name(street);
|
||||
IF word_ids is not null THEN
|
||||
parent_place_id := getNearestNamedRoadPlaceId(partition, centroid, word_ids);
|
||||
IF parent_place_id is not null THEN
|
||||
--DEBUG: RAISE WARNING 'Get parent form addr:street: %', parent.place_id;
|
||||
RETURN parent_place_id;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Check for addr:place attributes.
|
||||
IF place is not null THEN
|
||||
word_ids := word_ids_from_name(place);
|
||||
IF word_ids is not null THEN
|
||||
parent_place_id := getNearestNamedPlacePlaceId(partition, centroid, word_ids);
|
||||
IF parent_place_id is not null THEN
|
||||
--DEBUG: RAISE WARNING 'Get parent form addr:place: %', parent.place_id;
|
||||
RETURN parent_place_id;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql STABLE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION delete_location(OLD_place_id BIGINT)
|
||||
RETURNS BOOLEAN
|
||||
AS $$
|
||||
DECLARE
|
||||
BEGIN
|
||||
DELETE FROM location_area where place_id = OLD_place_id;
|
||||
-- TODO:location_area
|
||||
RETURN true;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION add_location(place_id BIGINT, country_code varchar(2),
|
||||
partition INTEGER, keywords INTEGER[],
|
||||
rank_search INTEGER, rank_address INTEGER,
|
||||
in_postcode TEXT, geometry GEOMETRY)
|
||||
RETURNS BOOLEAN
|
||||
AS $$
|
||||
DECLARE
|
||||
locationid INTEGER;
|
||||
centroid GEOMETRY;
|
||||
diameter FLOAT;
|
||||
x BOOLEAN;
|
||||
splitGeom RECORD;
|
||||
secgeo GEOMETRY;
|
||||
postcode TEXT;
|
||||
BEGIN
|
||||
|
||||
IF rank_search > 25 THEN
|
||||
RAISE EXCEPTION 'Adding location with rank > 25 (% rank %)', place_id, rank_search;
|
||||
END IF;
|
||||
|
||||
x := deleteLocationArea(partition, place_id, rank_search);
|
||||
|
||||
-- add postcode only if it contains a single entry, i.e. ignore postcode lists
|
||||
postcode := NULL;
|
||||
IF in_postcode is not null AND in_postcode not similar to '%(,|;)%' THEN
|
||||
postcode := upper(trim (in_postcode));
|
||||
END IF;
|
||||
|
||||
IF ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon') THEN
|
||||
centroid := ST_Centroid(geometry);
|
||||
|
||||
FOR secgeo IN select split_geometry(geometry) AS geom LOOP
|
||||
x := insertLocationAreaLarge(partition, place_id, country_code, keywords, rank_search, rank_address, false, postcode, centroid, secgeo);
|
||||
END LOOP;
|
||||
|
||||
ELSE
|
||||
|
||||
diameter := 0.02;
|
||||
IF rank_address = 0 THEN
|
||||
diameter := 0.02;
|
||||
ELSEIF rank_search <= 14 THEN
|
||||
diameter := 1.2;
|
||||
ELSEIF rank_search <= 15 THEN
|
||||
diameter := 1;
|
||||
ELSEIF rank_search <= 16 THEN
|
||||
diameter := 0.5;
|
||||
ELSEIF rank_search <= 17 THEN
|
||||
diameter := 0.2;
|
||||
ELSEIF rank_search <= 21 THEN
|
||||
diameter := 0.05;
|
||||
ELSEIF rank_search = 25 THEN
|
||||
diameter := 0.005;
|
||||
END IF;
|
||||
|
||||
-- RAISE WARNING 'adding % diameter %', place_id, diameter;
|
||||
|
||||
secgeo := ST_Buffer(geometry, diameter);
|
||||
x := insertLocationAreaLarge(partition, place_id, country_code, keywords, rank_search, rank_address, true, postcode, ST_Centroid(geometry), secgeo);
|
||||
|
||||
END IF;
|
||||
|
||||
RETURN true;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION quad_split_geometry(geometry GEOMETRY, maxarea FLOAT,
|
||||
maxdepth INTEGER)
|
||||
RETURNS SETOF GEOMETRY
|
||||
AS $$
|
||||
DECLARE
|
||||
xmin FLOAT;
|
||||
ymin FLOAT;
|
||||
xmax FLOAT;
|
||||
ymax FLOAT;
|
||||
xmid FLOAT;
|
||||
ymid FLOAT;
|
||||
secgeo GEOMETRY;
|
||||
secbox GEOMETRY;
|
||||
seg INTEGER;
|
||||
geo RECORD;
|
||||
area FLOAT;
|
||||
remainingdepth INTEGER;
|
||||
added INTEGER;
|
||||
BEGIN
|
||||
|
||||
-- RAISE WARNING 'quad_split_geometry: maxarea=%, depth=%',maxarea,maxdepth;
|
||||
|
||||
IF (ST_GeometryType(geometry) not in ('ST_Polygon','ST_MultiPolygon') OR NOT ST_IsValid(geometry)) THEN
|
||||
RETURN NEXT geometry;
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
remainingdepth := maxdepth - 1;
|
||||
area := ST_AREA(geometry);
|
||||
IF remainingdepth < 1 OR area < maxarea THEN
|
||||
RETURN NEXT geometry;
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
xmin := st_xmin(geometry);
|
||||
xmax := st_xmax(geometry);
|
||||
ymin := st_ymin(geometry);
|
||||
ymax := st_ymax(geometry);
|
||||
secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(ymin,xmin),ST_Point(ymax,xmax)),4326);
|
||||
|
||||
-- if the geometry completely covers the box don't bother to slice any more
|
||||
IF ST_AREA(secbox) = area THEN
|
||||
RETURN NEXT geometry;
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
xmid := (xmin+xmax)/2;
|
||||
ymid := (ymin+ymax)/2;
|
||||
|
||||
added := 0;
|
||||
FOR seg IN 1..4 LOOP
|
||||
|
||||
IF seg = 1 THEN
|
||||
secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmin,ymin),ST_Point(xmid,ymid)),4326);
|
||||
END IF;
|
||||
IF seg = 2 THEN
|
||||
secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmin,ymid),ST_Point(xmid,ymax)),4326);
|
||||
END IF;
|
||||
IF seg = 3 THEN
|
||||
secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmid,ymin),ST_Point(xmax,ymid)),4326);
|
||||
END IF;
|
||||
IF seg = 4 THEN
|
||||
secbox := ST_SetSRID(ST_MakeBox2D(ST_Point(xmid,ymid),ST_Point(xmax,ymax)),4326);
|
||||
END IF;
|
||||
|
||||
IF st_intersects(geometry, secbox) THEN
|
||||
secgeo := st_intersection(geometry, secbox);
|
||||
IF NOT ST_IsEmpty(secgeo) AND ST_GeometryType(secgeo) in ('ST_Polygon','ST_MultiPolygon') THEN
|
||||
FOR geo IN select quad_split_geometry(secgeo, maxarea, remainingdepth) as geom LOOP
|
||||
IF NOT ST_IsEmpty(geo.geom) AND ST_GeometryType(geo.geom) in ('ST_Polygon','ST_MultiPolygon') THEN
|
||||
added := added + 1;
|
||||
RETURN NEXT geo.geom;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
RETURN;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION split_geometry(geometry GEOMETRY)
|
||||
RETURNS SETOF GEOMETRY
|
||||
AS $$
|
||||
DECLARE
|
||||
geo RECORD;
|
||||
BEGIN
|
||||
-- 10000000000 is ~~ 1x1 degree
|
||||
FOR geo IN select quad_split_geometry(geometry, 0.25, 20) as geom LOOP
|
||||
RETURN NEXT geo.geom;
|
||||
END LOOP;
|
||||
RETURN;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION place_force_delete(placeid BIGINT)
|
||||
RETURNS BOOLEAN
|
||||
AS $$
|
||||
DECLARE
|
||||
osmid BIGINT;
|
||||
osmtype character(1);
|
||||
pclass text;
|
||||
ptype text;
|
||||
BEGIN
|
||||
SELECT osm_type, osm_id, class, type FROM placex WHERE place_id = placeid INTO osmtype, osmid, pclass, ptype;
|
||||
DELETE FROM import_polygon_delete where osm_type = osmtype and osm_id = osmid and class = pclass and type = ptype;
|
||||
DELETE FROM import_polygon_error where osm_type = osmtype and osm_id = osmid and class = pclass and type = ptype;
|
||||
-- force delete from place/placex by making it a very small geometry
|
||||
UPDATE place set geometry = ST_SetSRID(ST_Point(0,0), 4326) where osm_type = osmtype and osm_id = osmid and class = pclass and type = ptype;
|
||||
DELETE FROM place where osm_type = osmtype and osm_id = osmid and class = pclass and type = ptype;
|
||||
|
||||
RETURN TRUE;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION place_force_update(placeid BIGINT)
|
||||
RETURNS BOOLEAN
|
||||
AS $$
|
||||
DECLARE
|
||||
placegeom GEOMETRY;
|
||||
geom GEOMETRY;
|
||||
diameter FLOAT;
|
||||
rank INTEGER;
|
||||
BEGIN
|
||||
UPDATE placex SET indexed_status = 2 WHERE place_id = placeid;
|
||||
SELECT geometry, rank_search FROM placex WHERE place_id = placeid INTO placegeom, rank;
|
||||
IF placegeom IS NOT NULL AND ST_IsValid(placegeom) THEN
|
||||
IF ST_GeometryType(placegeom) in ('ST_Polygon','ST_MultiPolygon') THEN
|
||||
FOR geom IN select split_geometry(placegeom) FROM placex WHERE place_id = placeid LOOP
|
||||
update placex set indexed_status = 2 where (st_covers(geom, placex.geometry) OR ST_Intersects(geom, placex.geometry))
|
||||
AND rank_search > rank and indexed_status = 0 and ST_geometrytype(placex.geometry) = 'ST_Point' and (rank_search < 28 or name is not null or (rank >= 16 and address ? 'place'));
|
||||
update placex set indexed_status = 2 where (st_covers(geom, placex.geometry) OR ST_Intersects(geom, placex.geometry))
|
||||
AND rank_search > rank and indexed_status = 0 and ST_geometrytype(placex.geometry) != 'ST_Point' and (rank_search < 28 or name is not null or (rank >= 16 and address ? 'place'));
|
||||
END LOOP;
|
||||
ELSE
|
||||
diameter := update_place_diameter(rank);
|
||||
IF diameter > 0 THEN
|
||||
IF rank >= 26 THEN
|
||||
-- roads may cause reparenting for >27 rank places
|
||||
update placex set indexed_status = 2 where indexed_status = 0 and rank_search > rank and ST_DWithin(placex.geometry, placegeom, diameter);
|
||||
ELSEIF rank >= 16 THEN
|
||||
-- up to rank 16, street-less addresses may need reparenting
|
||||
update placex set indexed_status = 2 where indexed_status = 0 and rank_search > rank and ST_DWithin(placex.geometry, placegeom, diameter) and (rank_search < 28 or name is not null or address ? 'place');
|
||||
ELSE
|
||||
-- for all other places the search terms may change as well
|
||||
update placex set indexed_status = 2 where indexed_status = 0 and rank_search > rank and ST_DWithin(placex.geometry, placegeom, diameter) and (rank_search < 28 or name is not null);
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
RETURN TRUE;
|
||||
END IF;
|
||||
|
||||
RETURN FALSE;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
@@ -1,27 +1,26 @@
|
||||
-- Indices used only during search and update.
|
||||
-- These indices are created only after the indexing process is done.
|
||||
|
||||
CREATE INDEX CONCURRENTLY 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 CONCURRENTLY 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 CONCURRENTLY IF EXISTS idx_placex_rank_search;
|
||||
CREATE INDEX CONCURRENTLY idx_placex_rank_search ON placex USING BTREE (rank_search) {ts:search-index};
|
||||
CREATE INDEX CONCURRENTLY idx_placex_rank_address ON placex USING BTREE (rank_address) {ts:search-index};
|
||||
CREATE INDEX CONCURRENTLY idx_placex_pendingsector ON placex USING BTREE (rank_search,geometry_sector) {ts:address-index} where indexed_status > 0;
|
||||
CREATE INDEX CONCURRENTLY idx_placex_parent_place_id ON placex USING BTREE (parent_place_id) {ts:search-index} where parent_place_id IS NOT NULL;
|
||||
DROP INDEX IF EXISTS idx_placex_rank_search;
|
||||
CREATE INDEX idx_placex_rank_search ON placex USING BTREE (rank_search) {ts:search-index};
|
||||
CREATE INDEX idx_placex_rank_address ON placex USING BTREE (rank_address) {ts:search-index};
|
||||
CREATE INDEX idx_placex_parent_place_id ON placex USING BTREE (parent_place_id) {ts:search-index} where parent_place_id IS NOT NULL;
|
||||
|
||||
CREATE INDEX CONCURRENTLY idx_placex_geometry_reverse_lookupPoint
|
||||
CREATE INDEX idx_placex_geometry_reverse_lookupPoint
|
||||
ON placex USING gist (geometry) {ts:search-index}
|
||||
WHERE (name is not null or housenumber is not null or rank_address between 26 and 27)
|
||||
AND class not in ('railway','tunnel','bridge','man_made')
|
||||
AND rank_address >= 26 AND indexed_status = 0 AND linked_place_id is null;
|
||||
CREATE INDEX CONCURRENTLY idx_placex_geometry_reverse_lookupPolygon
|
||||
CREATE INDEX idx_placex_geometry_reverse_lookupPolygon
|
||||
ON placex USING gist (geometry) {ts:search-index}
|
||||
WHERE St_GeometryType(geometry) in ('ST_Polygon', 'ST_MultiPolygon')
|
||||
AND rank_address between 4 and 25 AND type != 'postcode'
|
||||
AND name is not null AND indexed_status = 0 AND linked_place_id is null;
|
||||
CREATE INDEX CONCURRENTLY idx_placex_geometry_reverse_placeNode
|
||||
CREATE INDEX idx_placex_geometry_reverse_placeNode
|
||||
ON placex USING gist (geometry) {ts:search-index}
|
||||
WHERE osm_type = 'N' AND rank_search between 5 and 25
|
||||
AND class = 'place' AND type != 'postcode'
|
||||
@@ -29,14 +28,8 @@ CREATE INDEX CONCURRENTLY idx_placex_geometry_reverse_placeNode
|
||||
|
||||
GRANT SELECT ON table country_osm_grid to "{www-user}";
|
||||
|
||||
CREATE INDEX CONCURRENTLY 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_osm_id ON location_property_osmline USING BTREE (osm_id) {ts:search-index};
|
||||
|
||||
CREATE INDEX CONCURRENTLY idx_osmline_parent_place_id ON location_property_osmline USING BTREE (parent_place_id) {ts:search-index};
|
||||
CREATE INDEX CONCURRENTLY idx_osmline_parent_osm_id ON location_property_osmline USING BTREE (osm_id) {ts:search-index};
|
||||
|
||||
DROP INDEX CONCURRENTLY IF EXISTS place_id_idx;
|
||||
CREATE UNIQUE INDEX CONCURRENTLY idx_place_osm_unique on place using btree(osm_id,osm_type,class,type) {ts:address-index};
|
||||
|
||||
|
||||
CREATE UNIQUE INDEX CONCURRENTLY idx_postcode_id ON location_postcode USING BTREE (place_id) {ts:search-index};
|
||||
CREATE INDEX CONCURRENTLY idx_postcode_postcode ON location_postcode USING BTREE (postcode) {ts:search-index};
|
||||
CREATE UNIQUE INDEX idx_postcode_id ON location_postcode USING BTREE (place_id) {ts:search-index};
|
||||
CREATE INDEX idx_postcode_postcode ON location_postcode USING BTREE (postcode) {ts:search-index};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user