mirror of
https://github.com/osm-search/Nominatim.git
synced 2026-02-14 01:47:57 +00:00
Compare commits
548 Commits
v4.5.0
...
b2f868d2fc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2f868d2fc | ||
|
|
ae7301921a | ||
|
|
8188689765 | ||
|
|
135453e463 | ||
|
|
cc9c8963f3 | ||
|
|
c882718355 | ||
|
|
3f02a4e33b | ||
|
|
1cf5464d3a | ||
|
|
dcbfa2a3d0 | ||
|
|
5cdc6724de | ||
|
|
45972811e3 | ||
|
|
e021f558bf | ||
|
|
fcc5ce3f92 | ||
|
|
9a979b7429 | ||
|
|
6ad87db1eb | ||
|
|
f4820bed0e | ||
|
|
bf6eb01d68 | ||
|
|
f07676a376 | ||
|
|
5e2ce10fe0 | ||
|
|
58cae70596 | ||
|
|
bf0ee6685b | ||
|
|
ff1f1b06d9 | ||
|
|
67ecf5f6a0 | ||
|
|
e77a4c2f35 | ||
|
|
9fa980bca2 | ||
|
|
fe773c12b2 | ||
|
|
cc96912580 | ||
|
|
77a3ecd72d | ||
|
|
6a6a064ef7 | ||
|
|
35b42ad9ce | ||
|
|
c4dc2c862e | ||
|
|
7e44256f4a | ||
|
|
eefd0efa59 | ||
|
|
2698382552 | ||
|
|
954771a42d | ||
|
|
e47601754a | ||
|
|
2cdf2db184 | ||
|
|
5200e11f33 | ||
|
|
ba1fc5a5b8 | ||
|
|
d35a71c123 | ||
|
|
e31862b7b5 | ||
|
|
9ac5e0256d | ||
|
|
a4a2176ded | ||
|
|
f30fcdcd9d | ||
|
|
77b8e76be6 | ||
|
|
20a333dd9b | ||
|
|
084e1b8177 | ||
|
|
2e2ce2c979 | ||
|
|
99643aa0e9 | ||
|
|
c05b8f241c | ||
|
|
da94d7eea3 | ||
|
|
f9864b7ec7 | ||
|
|
df4abfd5cc | ||
|
|
42d139a5d0 | ||
|
|
f2110e12d6 | ||
|
|
3bcd1aa721 | ||
|
|
354aa07cad | ||
|
|
deb6654cfd | ||
|
|
6a67cfcddf | ||
|
|
f9cf320794 | ||
|
|
d1cb578535 | ||
|
|
a97b5d97cb | ||
|
|
9ec607b556 | ||
|
|
89821d01e0 | ||
|
|
7ef3f99fa4 | ||
|
|
0aa9eee3e7 | ||
|
|
340fe64e8b | ||
|
|
0b11dd0eba | ||
|
|
3b182afa72 | ||
|
|
ae77a9512a | ||
|
|
f7ba1fc9e1 | ||
|
|
26e62fda19 | ||
|
|
4fd616254a | ||
|
|
049164086a | ||
|
|
5e965d5216 | ||
|
|
2097401b12 | ||
|
|
58e56ec53d | ||
|
|
fe170c9286 | ||
|
|
0c5af2e3e4 | ||
|
|
681daeea29 | ||
|
|
49454048c4 | ||
|
|
4919240377 | ||
|
|
56cb183c4e | ||
|
|
35060164ab | ||
|
|
4cfc1792fb | ||
|
|
3bb5d00848 | ||
|
|
b366b9df6f | ||
|
|
6b12501c7a | ||
|
|
ffd5c32f17 | ||
|
|
6c8869439f | ||
|
|
8188946394 | ||
|
|
19134cc15c | ||
|
|
d0b9aac400 | ||
|
|
48d13c593b | ||
|
|
96d04e3a2e | ||
|
|
23db1ab981 | ||
|
|
cd1b1736a9 | ||
|
|
9447c90b09 | ||
|
|
81c6cb72e6 | ||
|
|
f2a122c5c0 | ||
|
|
57ef0e1f98 | ||
|
|
922667b650 | ||
|
|
fba803167c | ||
|
|
782df52ea0 | ||
|
|
c36da68a48 | ||
|
|
716de13bc9 | ||
|
|
1df56d7548 | ||
|
|
9cfef7a31a | ||
|
|
139678f367 | ||
|
|
e578c60ff4 | ||
|
|
7b4a3c8500 | ||
|
|
7751f9a6b6 | ||
|
|
303ac42b47 | ||
|
|
6a2d2daad5 | ||
|
|
a51c771107 | ||
|
|
55547723bf | ||
|
|
362088775f | ||
|
|
9a13b62fb9 | ||
|
|
3ecda751c4 | ||
|
|
5d4c29b84b | ||
|
|
f1fbc04f33 | ||
|
|
353c985b9f | ||
|
|
2dda9079f0 | ||
|
|
4c91a0bc8d | ||
|
|
31c8ec6db0 | ||
|
|
e2330ff4c1 | ||
|
|
589825d37e | ||
|
|
a93113bc44 | ||
|
|
b042eca382 | ||
|
|
d202a8f7d8 | ||
|
|
af6386bd68 | ||
|
|
862bfdf6fb | ||
|
|
28029edc8b | ||
|
|
d6e9196177 | ||
|
|
e0a750e089 | ||
|
|
93b2a0f194 | ||
|
|
aa3fce6852 | ||
|
|
535ffc1e3f | ||
|
|
77ed4635f2 | ||
|
|
7715a9d500 | ||
|
|
58d570ca8a | ||
|
|
5a8aa6cce4 | ||
|
|
72592da0cc | ||
|
|
193d6c4173 | ||
|
|
4fd881bcb2 | ||
|
|
54620f9566 | ||
|
|
42b687f545 | ||
|
|
43ffceff27 | ||
|
|
2fb03cd103 | ||
|
|
8d3d24a1e4 | ||
|
|
8efdab1d6f | ||
|
|
1d1d80e1e3 | ||
|
|
670cf98f93 | ||
|
|
433c40cd68 | ||
|
|
a049569020 | ||
|
|
bf49f6a46f | ||
|
|
45a44f1411 | ||
|
|
5a2bfd7a19 | ||
|
|
fd12d2e9f3 | ||
|
|
3d0867ff16 | ||
|
|
177b16b89b | ||
|
|
0b7bde2500 | ||
|
|
7ac3591433 | ||
|
|
07c2907064 | ||
|
|
355cbcc7b8 | ||
|
|
8339c2b928 | ||
|
|
341c09ee95 | ||
|
|
b0b909be93 | ||
|
|
bf604e36ee | ||
|
|
3a50f749dd | ||
|
|
563255202d | ||
|
|
94d22bbdac | ||
|
|
32d26f12c4 | ||
|
|
0f324c8cb2 | ||
|
|
1e3b56d215 | ||
|
|
e855552e01 | ||
|
|
79a1907c49 | ||
|
|
91e345f77f | ||
|
|
d0ad65f696 | ||
|
|
e916d27b7c | ||
|
|
823ad5d279 | ||
|
|
048d571e46 | ||
|
|
f5e4b74c38 | ||
|
|
c2a311e69c | ||
|
|
5968f7d646 | ||
|
|
4cdd2526b6 | ||
|
|
4ff7696ed3 | ||
|
|
247afe1f56 | ||
|
|
6f74141fa4 | ||
|
|
75ccf97de3 | ||
|
|
196de9e974 | ||
|
|
6b627df4fb | ||
|
|
b7d77b9b43 | ||
|
|
7e84d38a92 | ||
|
|
c7df8738ed | ||
|
|
0045203092 | ||
|
|
b325413486 | ||
|
|
6270c90052 | ||
|
|
a7709c768d | ||
|
|
47c0a101b9 | ||
|
|
64bb8c2a9c | ||
|
|
194b607491 | ||
|
|
9bad3b1e61 | ||
|
|
69e882096c | ||
|
|
f300b00c2d | ||
|
|
242fcc6e4d | ||
|
|
83c6f27f5c | ||
|
|
1111597db5 | ||
|
|
866e6bade9 | ||
|
|
4cbbe04f7f | ||
|
|
e1cef3de0a | ||
|
|
c6088cb4e7 | ||
|
|
a725cab2fc | ||
|
|
8bb53c22be | ||
|
|
8a96e4f802 | ||
|
|
a9cd706bb6 | ||
|
|
09b5ea097b | ||
|
|
e111257644 | ||
|
|
93ac1023f7 | ||
|
|
1fe2353682 | ||
|
|
6d2b79870c | ||
|
|
621d8e785b | ||
|
|
830307484b | ||
|
|
5d6967a1d0 | ||
|
|
26903aec0b | ||
|
|
c39183e3a5 | ||
|
|
21ef3be433 | ||
|
|
99562a197e | ||
|
|
fe30663b21 | ||
|
|
73ee17af95 | ||
|
|
b9252cc348 | ||
|
|
71025f3f43 | ||
|
|
e4b671f8b1 | ||
|
|
7ebd121abc | ||
|
|
4634ad0720 | ||
|
|
4a9253a0a9 | ||
|
|
1aeb8a262c | ||
|
|
ef7e842702 | ||
|
|
ec42fda1bd | ||
|
|
287ba2570e | ||
|
|
4711deeccb | ||
|
|
cf9e8d6b8e | ||
|
|
06d5ab4c2d | ||
|
|
e327512667 | ||
|
|
3e04eb2ffe | ||
|
|
970d81fb27 | ||
|
|
cecdbeb7cf | ||
|
|
c634e9fc5f | ||
|
|
13eaea8aae | ||
|
|
ab5f348a4a | ||
|
|
11d624e92a | ||
|
|
a7797f8b37 | ||
|
|
c4dd0d4f95 | ||
|
|
f43fec0d57 | ||
|
|
af82c3debb | ||
|
|
1ab4d445ea | ||
|
|
678702ceb7 | ||
|
|
f9eb93c4ab | ||
|
|
f97a0a76f2 | ||
|
|
cf9b946eba | ||
|
|
7dc3924a3c | ||
|
|
20cf4b56b9 | ||
|
|
40d5b78eb8 | ||
|
|
8d0e767826 | ||
|
|
87a8c246a0 | ||
|
|
90050de717 | ||
|
|
10a7d1106d | ||
|
|
f2236f68f1 | ||
|
|
831fccdaee | ||
|
|
d2e691b63f | ||
|
|
2a508b6c99 | ||
|
|
02c3a6fffa | ||
|
|
26348764d4 | ||
|
|
f8a56ab6e6 | ||
|
|
75b4c7e56b | ||
|
|
9f1dfb1876 | ||
|
|
730b4204f6 | ||
|
|
4898704b5a | ||
|
|
0cf470f863 | ||
|
|
6220bde2d6 | ||
|
|
a4d3b57f37 | ||
|
|
618fbc63d7 | ||
|
|
3f51cb3fd1 | ||
|
|
59a947c5f5 | ||
|
|
1952290359 | ||
|
|
1a323165f9 | ||
|
|
9c2fdf5eae | ||
|
|
800c56642b | ||
|
|
b51fed025c | ||
|
|
34b72591cc | ||
|
|
bc450d110c | ||
|
|
388acf4727 | ||
|
|
3999977941 | ||
|
|
df58870e3f | ||
|
|
478a8741db | ||
|
|
7f710d2394 | ||
|
|
06e39e42d8 | ||
|
|
2ef0e20a3f | ||
|
|
b680d81f0a | ||
|
|
e0e067b1d6 | ||
|
|
3980791cfd | ||
|
|
497e27bb9a | ||
|
|
1db717b886 | ||
|
|
b47c8ccfb1 | ||
|
|
63b055283d | ||
|
|
b80e6914e7 | ||
|
|
9d00a137fe | ||
|
|
97d9e3c548 | ||
|
|
e4180936c1 | ||
|
|
34e0ecb44f | ||
|
|
d95e9737da | ||
|
|
b34991d85f | ||
|
|
5f44aa2873 | ||
|
|
dae643c040 | ||
|
|
ee62d5e1cf | ||
|
|
fb440f29a2 | ||
|
|
0f725b1880 | ||
|
|
39f56ba4b8 | ||
|
|
6959577aa4 | ||
|
|
50d4b0a386 | ||
|
|
9ff93bdb3d | ||
|
|
e0bf553aa5 | ||
|
|
2ce2d031fa | ||
|
|
186f562dd7 | ||
|
|
c5bbeb626f | ||
|
|
3bc77629c8 | ||
|
|
6cf1287c4e | ||
|
|
a49e8b9cf7 | ||
|
|
2eeec46040 | ||
|
|
6d5a4a20c5 | ||
|
|
4665ea3e77 | ||
|
|
9cf5eee5d4 | ||
|
|
fce279226f | ||
|
|
54d895c4ce | ||
|
|
896a1c9d12 | ||
|
|
32728d6c89 | ||
|
|
12ad95067d | ||
|
|
bfd1c83cb0 | ||
|
|
bbadc62371 | ||
|
|
5c9d3ca8d2 | ||
|
|
be4ba370ef | ||
|
|
3cb183ffb0 | ||
|
|
58ef032a2b | ||
|
|
1705bb5f57 | ||
|
|
f2aa15778f | ||
|
|
efe65c3e49 | ||
|
|
51847ebfeb | ||
|
|
46579f08e4 | ||
|
|
d4994a152b | ||
|
|
00b3ace3cf | ||
|
|
522bc942cf | ||
|
|
d6e749d621 | ||
|
|
13cfb7efe2 | ||
|
|
35baf77b18 | ||
|
|
7e68613cc7 | ||
|
|
b1fc721f4b | ||
|
|
d400fd5f76 | ||
|
|
e4295dba10 | ||
|
|
9419c5adb2 | ||
|
|
2c61fe08a0 | ||
|
|
7b3c725f2a | ||
|
|
edc5ada625 | ||
|
|
72d3360fa2 | ||
|
|
0ffe384c57 | ||
|
|
9dad5edeb6 | ||
|
|
d86d491f2e | ||
|
|
3026c333ca | ||
|
|
ad84bbdec7 | ||
|
|
f5755a7a82 | ||
|
|
cd08956c61 | ||
|
|
12f5719184 | ||
|
|
78f839fbd3 | ||
|
|
c70dfccaca | ||
|
|
4cc788f69e | ||
|
|
5a245e33e0 | ||
|
|
6ff51712fe | ||
|
|
c431e0e45d | ||
|
|
c2d62a59cb | ||
|
|
cd64788a58 | ||
|
|
800a41721a | ||
|
|
1b44fe2555 | ||
|
|
6b0d58d9fd | ||
|
|
afb89f9c7a | ||
|
|
6712627d5e | ||
|
|
434fbbfd18 | ||
|
|
921db8bb2f | ||
|
|
a574b98e4a | ||
|
|
b2af358f66 | ||
|
|
e67ae701ac | ||
|
|
fc1c6261ed | ||
|
|
6759edfb5d | ||
|
|
e362a965e1 | ||
|
|
eff60ba6be | ||
|
|
157414a053 | ||
|
|
18d4996bec | ||
|
|
13db4c9731 | ||
|
|
f567ea89cc | ||
|
|
3e718e40d9 | ||
|
|
49bd18b048 | ||
|
|
31412e0674 | ||
|
|
4577669213 | ||
|
|
9bf1428d81 | ||
|
|
b56edf3d0a | ||
|
|
abc911079e | ||
|
|
adabfee3be | ||
|
|
46c4446dc2 | ||
|
|
add9244a2f | ||
|
|
96d7a8e8f6 | ||
|
|
55c3176957 | ||
|
|
e29823e28f | ||
|
|
97ed168996 | ||
|
|
9b8ef97d4b | ||
|
|
4f3c88f0c1 | ||
|
|
7781186f3c | ||
|
|
f78686edb8 | ||
|
|
e330cd3162 | ||
|
|
671af4cff2 | ||
|
|
e612b7d550 | ||
|
|
0b49d01703 | ||
|
|
f6bc8e153f | ||
|
|
f143ecaf1c | ||
|
|
6730c8bac8 | ||
|
|
ee8915f2b6 | ||
|
|
5475bf7b9c | ||
|
|
95e2d8c846 | ||
|
|
7552818866 | ||
|
|
db3991af74 | ||
|
|
4523b9aaed | ||
|
|
8b1cabebd6 | ||
|
|
0cf636a80c | ||
|
|
c2cb6722fe | ||
|
|
f8337bedb2 | ||
|
|
efc09a5cfc | ||
|
|
86ad9efa8a | ||
|
|
d984100e23 | ||
|
|
499110f549 | ||
|
|
267e5dac0d | ||
|
|
32d3eb46d5 | ||
|
|
c8a0dc8af1 | ||
|
|
14ecfc7834 | ||
|
|
cad44eb00c | ||
|
|
f76dbb0a16 | ||
|
|
8dd218a1d0 | ||
|
|
501e13483e | ||
|
|
b1d25e404f | ||
|
|
71fceb6854 | ||
|
|
a06e123d70 | ||
|
|
df6f70d223 | ||
|
|
9058dabf1a | ||
|
|
2535780282 | ||
|
|
48333bfbd4 | ||
|
|
99cf552c17 | ||
|
|
ad214753fc | ||
|
|
0d500d4bd1 | ||
|
|
b1e5265d33 | ||
|
|
e2a9b5fdf7 | ||
|
|
eeb3d5dd0a | ||
|
|
a75dd32f75 | ||
|
|
e1e8182c72 | ||
|
|
59bce26afe | ||
|
|
d1b7c14f79 | ||
|
|
59416178bd | ||
|
|
70e351c528 | ||
|
|
1eed2fa395 | ||
|
|
438b8fed35 | ||
|
|
4760e8341b | ||
|
|
639630d5fe | ||
|
|
5b40aa579b | ||
|
|
fbb6edfdaf | ||
|
|
2b87c016db | ||
|
|
a894e0f3a4 | ||
|
|
046665f8d9 | ||
|
|
d9b4d1591d | ||
|
|
0862671104 | ||
|
|
416e70b97e | ||
|
|
494640c535 | ||
|
|
abe9737229 | ||
|
|
5d237a06ea | ||
|
|
d5bfab02c2 | ||
|
|
79836e51d6 | ||
|
|
0770eaa5d0 | ||
|
|
c172ca8c6c | ||
|
|
4e59efa178 | ||
|
|
9cf5970e22 | ||
|
|
4cce681ead | ||
|
|
c4a726c96b | ||
|
|
a408da4ccc | ||
|
|
e0318344f6 | ||
|
|
02364ce6c8 | ||
|
|
bf683d434b | ||
|
|
f1ba285319 | ||
|
|
98c1b923fc | ||
|
|
fd1f2bc719 | ||
|
|
689bcbd6ea | ||
|
|
3acd7df5c4 | ||
|
|
7d418da564 | ||
|
|
04d5f674eb | ||
|
|
23369ce970 | ||
|
|
20d0fb35ce | ||
|
|
754ff15ebd | ||
|
|
7c9002cae7 | ||
|
|
1a64c3bfcd | ||
|
|
2735ea768a | ||
|
|
ae8694a6a6 | ||
|
|
5ec07321d4 | ||
|
|
d77aa7dfc9 | ||
|
|
122ecd4626 | ||
|
|
1f07967787 | ||
|
|
8c14df55a6 | ||
|
|
af756d61dd | ||
|
|
2d115ea412 | ||
|
|
981b879830 | ||
|
|
6415c9cf95 | ||
|
|
7b21354a8a | ||
|
|
ad50016c49 | ||
|
|
b9e4563beb | ||
|
|
2c0f2e1ede | ||
|
|
ea98317370 | ||
|
|
2fe8b98d55 | ||
|
|
bea9249e38 | ||
|
|
6299afcad7 | ||
|
|
5160a1d577 | ||
|
|
83013f819b | ||
|
|
15eb7f0bb1 | ||
|
|
b2dc01ad81 | ||
|
|
c2aa7a9b43 | ||
|
|
90e207a497 | ||
|
|
d856788bf5 | ||
|
|
f960a9bf7f | ||
|
|
188c770f5c | ||
|
|
a690605a96 | ||
|
|
290c22a153 | ||
|
|
4825a0bda3 | ||
|
|
b54ff7d766 | ||
|
|
9734bbf240 | ||
|
|
d4f3eda314 | ||
|
|
74c39267d9 | ||
|
|
b87d6226fb | ||
|
|
e92e03e2e6 | ||
|
|
9545f0bf80 | ||
|
|
c4f30de7a3 | ||
|
|
7717bbf59d | ||
|
|
7ba5152493 | ||
|
|
6bc044d9c7 | ||
|
|
06683edaae | ||
|
|
979aebbfcd | ||
|
|
1e4677b668 | ||
|
|
7f909dbbd8 |
11
.flake8
Normal file
11
.flake8
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[flake8]
|
||||||
|
max-line-length = 100
|
||||||
|
max-doc-length = 100
|
||||||
|
extend-ignore =
|
||||||
|
# something == None constructs are needed for SQLAlchemy
|
||||||
|
E711
|
||||||
|
per-file-ignores =
|
||||||
|
__init__.py: F401
|
||||||
|
test/python/utils/test_json_writer.py: E131
|
||||||
|
**/conftest.py: E402
|
||||||
|
test/bdd/*: F821
|
||||||
@@ -17,11 +17,11 @@ assignees: ''
|
|||||||
|
|
||||||
## What result did you expect?
|
## What result did you expect?
|
||||||
|
|
||||||
**When the result in the right place and just named wrongly:**
|
**When the result is in the right place and just named wrongly:**
|
||||||
|
|
||||||
<!-- Please tell us the display name you expected. -->
|
<!-- Please tell us the display name you expected. -->
|
||||||
|
|
||||||
**When the result missing completely:**
|
**When the result is missing completely:**
|
||||||
|
|
||||||
<!-- Make sure that the data you are looking for is in OpenStreetMap. Provide a link to the OpenStreetMap object or if you cannot get it, a link to the map on https://openstreetmap.org where you expect the result to be.
|
<!-- Make sure that the data you are looking for is in OpenStreetMap. Provide a link to the OpenStreetMap object or if you cannot get it, a link to the map on https://openstreetmap.org where you expect the result to be.
|
||||||
|
|
||||||
|
|||||||
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
## Summary
|
||||||
|
<!-- Describe the purpose of your pull request and, if present, link to existing issues. -->
|
||||||
|
|
||||||
|
## AI usage
|
||||||
|
<!-- Please list where and to what extent AI was used. -->
|
||||||
|
|
||||||
|
## Contributor guidelines (mandatory)
|
||||||
|
<!-- We only accept pull requests that follow our guidelines. A deliberate violation may result in a ban. -->
|
||||||
|
|
||||||
|
- [ ] I have adhered to the [coding style](https://github.com/osm-search/Nominatim/blob/master/CONTRIBUTING.md#coding-style)
|
||||||
|
- [ ] I have [tested](https://github.com/osm-search/Nominatim/blob/master/CONTRIBUTING.md#testing) the proposed changes
|
||||||
|
- [ ] I have [disclosed](https://github.com/osm-search/Nominatim/blob/master/CONTRIBUTING.md#using-ai-assisted-code-generators) above any use of AI to generate code, documentation, or the pull request description
|
||||||
58
.github/actions/build-nominatim/action.yml
vendored
58
.github/actions/build-nominatim/action.yml
vendored
@@ -1,18 +1,10 @@
|
|||||||
name: 'Build Nominatim'
|
name: 'Build Nominatim'
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
flavour:
|
dependencies:
|
||||||
description: 'Version of Ubuntu to install on'
|
description: 'Where to install dependencies from (pip/apt)'
|
||||||
required: false
|
required: false
|
||||||
default: 'ubuntu-20'
|
default: 'pip'
|
||||||
cmake-args:
|
|
||||||
description: 'Additional options to hand to cmake'
|
|
||||||
required: false
|
|
||||||
default: ''
|
|
||||||
lua:
|
|
||||||
description: 'Version of Lua to use'
|
|
||||||
required: false
|
|
||||||
default: '5.3'
|
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
@@ -23,30 +15,30 @@ runs:
|
|||||||
sudo rm -rf /opt/hostedtoolcache/go /opt/hostedtoolcache/CodeQL /usr/lib/jvm /usr/local/share/chromium /usr/local/lib/android
|
sudo rm -rf /opt/hostedtoolcache/go /opt/hostedtoolcache/CodeQL /usr/lib/jvm /usr/local/share/chromium /usr/local/lib/android
|
||||||
df -h
|
df -h
|
||||||
shell: bash
|
shell: bash
|
||||||
- name: Install${{ matrix.flavour }} prerequisites
|
- name: Install general prerequisites
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install -y -qq libboost-system-dev libboost-filesystem-dev libexpat1-dev zlib1g-dev libbz2-dev libpq-dev libproj-dev libicu-dev liblua${LUA_VERSION}-dev lua${LUA_VERSION} lua-dkjson nlohmann-json3-dev libspatialite7 libsqlite3-mod-spatialite
|
sudo apt-get install -y -qq libspatialite-dev libsqlite3-mod-spatialite libicu-dev virtualenv python3-dev osm2pgsql
|
||||||
if [ "$FLAVOUR" == "oldstuff" ]; then
|
|
||||||
pip3 install MarkupSafe==2.0.1 python-dotenv jinja2==2.8 psutil==5.4.2 pyicu==2.9 osmium PyYAML==5.1 sqlalchemy==1.4.31 psycopg==3.1.7 datrie asyncpg aiosqlite
|
|
||||||
else
|
|
||||||
sudo apt-get install -y -qq python3-icu python3-datrie python3-pyosmium python3-jinja2 python3-psutil python3-dotenv python3-yaml
|
|
||||||
pip3 install sqlalchemy psycopg aiosqlite
|
|
||||||
fi
|
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
|
||||||
FLAVOUR: ${{ inputs.flavour }}
|
|
||||||
CMAKE_ARGS: ${{ inputs.cmake-args }}
|
|
||||||
LUA_VERSION: ${{ inputs.lua }}
|
|
||||||
|
|
||||||
- name: Configure
|
- name: Install prerequisites from apt
|
||||||
run: mkdir build && cd build && cmake $CMAKE_ARGS ../Nominatim
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
CMAKE_ARGS: ${{ inputs.cmake-args }}
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
run: |
|
||||||
make -j2 all
|
sudo apt-get install -y -qq python3-icu python3-datrie python3-jinja2 python3-psutil python3-dotenv python3-yaml python3-sqlalchemy python3-psycopg python3-asyncpg python3-mwparserfromhell
|
||||||
sudo make install
|
shell: bash
|
||||||
|
if: inputs.dependencies == 'apt'
|
||||||
|
|
||||||
|
- name: Setup virtual environment (for pip)
|
||||||
|
run: |
|
||||||
|
virtualenv venv
|
||||||
|
./venv/bin/pip install -U pip
|
||||||
|
shell: bash
|
||||||
|
if: inputs.dependencies == 'pip'
|
||||||
|
|
||||||
|
- name: Setup virtual environment (for apt)
|
||||||
|
run: |
|
||||||
|
virtualenv venv --system-site-packages
|
||||||
|
shell: bash
|
||||||
|
if: inputs.dependencies == 'apt'
|
||||||
|
|
||||||
|
- name: Build nominatim
|
||||||
|
run: ./venv/bin/pip install Nominatim/packaging/nominatim-{api,db}
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: build
|
|
||||||
|
|||||||
10
.github/actions/setup-postgresql/action.yml
vendored
10
.github/actions/setup-postgresql/action.yml
vendored
@@ -4,9 +4,6 @@ inputs:
|
|||||||
postgresql-version:
|
postgresql-version:
|
||||||
description: 'Version of PostgreSQL to install'
|
description: 'Version of PostgreSQL to install'
|
||||||
required: true
|
required: true
|
||||||
postgis-version:
|
|
||||||
description: 'Version of Postgis to install'
|
|
||||||
required: true
|
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
@@ -14,21 +11,18 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Remove existing PostgreSQL
|
- name: Remove existing PostgreSQL
|
||||||
run: |
|
run: |
|
||||||
|
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
|
||||||
sudo apt-get purge -yq postgresql*
|
sudo apt-get purge -yq postgresql*
|
||||||
sudo apt install curl ca-certificates gnupg
|
|
||||||
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg >/dev/null
|
|
||||||
sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
|
|
||||||
sudo apt-get update -qq
|
sudo apt-get update -qq
|
||||||
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Install PostgreSQL
|
- name: Install PostgreSQL
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install -y -qq --no-install-suggests --no-install-recommends postgresql-client-${PGVER} postgresql-${PGVER}-postgis-${POSTGISVER} postgresql-${PGVER}-postgis-${POSTGISVER}-scripts postgresql-contrib-${PGVER} postgresql-${PGVER}
|
sudo apt-get install -y -qq --no-install-suggests --no-install-recommends postgresql-client-${PGVER} postgresql-${PGVER}-postgis-3 postgresql-${PGVER}-postgis-3-scripts postgresql-contrib-${PGVER} postgresql-${PGVER}
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
PGVER: ${{ inputs.postgresql-version }}
|
PGVER: ${{ inputs.postgresql-version }}
|
||||||
POSTGISVER: ${{ inputs.postgis-version }}
|
|
||||||
|
|
||||||
- name: Adapt postgresql configuration
|
- name: Adapt postgresql configuration
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
284
.github/workflows/ci-tests.yml
vendored
284
.github/workflows/ci-tests.yml
vendored
@@ -37,23 +37,20 @@ jobs:
|
|||||||
needs: create-archive
|
needs: create-archive
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
flavour: [oldstuff, "ubuntu-20", "ubuntu-22"]
|
flavour: ["ubuntu-22", "ubuntu-24"]
|
||||||
include:
|
include:
|
||||||
- flavour: oldstuff
|
|
||||||
ubuntu: 20
|
|
||||||
postgresql: '9.6'
|
|
||||||
postgis: '2.5'
|
|
||||||
lua: '5.1'
|
|
||||||
- flavour: ubuntu-20
|
|
||||||
ubuntu: 20
|
|
||||||
postgresql: 13
|
|
||||||
postgis: 3
|
|
||||||
lua: '5.3'
|
|
||||||
- flavour: ubuntu-22
|
- flavour: ubuntu-22
|
||||||
ubuntu: 22
|
ubuntu: 22
|
||||||
postgresql: 15
|
postgresql: 12
|
||||||
postgis: 3
|
lua: '5.1'
|
||||||
|
dependencies: pip
|
||||||
|
python: '3.9'
|
||||||
|
- flavour: ubuntu-24
|
||||||
|
ubuntu: 24
|
||||||
|
postgresql: 18
|
||||||
lua: '5.3'
|
lua: '5.3'
|
||||||
|
dependencies: apt
|
||||||
|
python: 'builtin'
|
||||||
|
|
||||||
runs-on: ubuntu-${{ matrix.ubuntu }}.04
|
runs-on: ubuntu-${{ matrix.ubuntu }}.04
|
||||||
|
|
||||||
@@ -65,164 +62,83 @@ jobs:
|
|||||||
- name: Unpack Nominatim
|
- name: Unpack Nominatim
|
||||||
run: tar xf nominatim-src.tar.bz2
|
run: tar xf nominatim-src.tar.bz2
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: 3.7
|
|
||||||
if: matrix.flavour == 'oldstuff'
|
|
||||||
|
|
||||||
- uses: ./Nominatim/.github/actions/setup-postgresql
|
- uses: ./Nominatim/.github/actions/setup-postgresql
|
||||||
with:
|
with:
|
||||||
postgresql-version: ${{ matrix.postgresql }}
|
postgresql-version: ${{ matrix.postgresql }}
|
||||||
postgis-version: ${{ matrix.postgis }}
|
|
||||||
|
|
||||||
- uses: ./Nominatim/.github/actions/build-nominatim
|
- uses: ./Nominatim/.github/actions/build-nominatim
|
||||||
with:
|
with:
|
||||||
flavour: ${{ matrix.flavour }}
|
dependencies: ${{ matrix.dependencies }}
|
||||||
lua: ${{ matrix.lua }}
|
|
||||||
|
|
||||||
- name: Install test prerequisites (behave from apt)
|
- uses: actions/cache@v4
|
||||||
run: sudo apt-get install -y -qq python3-behave
|
with:
|
||||||
if: matrix.flavour == 'ubuntu-20'
|
path: |
|
||||||
|
/usr/local/bin/osm2pgsql
|
||||||
|
key: osm2pgsql-bin-22-1
|
||||||
|
if: matrix.ubuntu == '22'
|
||||||
|
|
||||||
- name: Install test prerequisites (behave from pip)
|
- name: Set up Python
|
||||||
run: pip3 install behave==1.2.6
|
uses: actions/setup-python@v5
|
||||||
if: (matrix.flavour == 'oldstuff') || (matrix.flavour == 'ubuntu-22')
|
with:
|
||||||
|
python-version: ${{ matrix.python }}
|
||||||
|
if: matrix.python != 'builtin'
|
||||||
|
|
||||||
- name: Install test prerequisites (from apt for Ununtu 2x)
|
- name: Compile osm2pgsql
|
||||||
run: sudo apt-get install -y -qq python3-pytest python3-pytest-asyncio uvicorn
|
run: |
|
||||||
if: matrix.flavour != 'oldstuff'
|
if [ ! -f /usr/local/bin/osm2pgsql ]; then
|
||||||
|
sudo apt-get install -y -qq libboost-system-dev libboost-filesystem-dev libexpat1-dev zlib1g-dev libbz2-dev libpq-dev libproj-dev libicu-dev liblua${LUA_VERSION}-dev lua-dkjson nlohmann-json3-dev
|
||||||
|
mkdir osm2pgsql-build
|
||||||
|
cd osm2pgsql-build
|
||||||
|
git clone https://github.com/osm2pgsql-dev/osm2pgsql
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake ../osm2pgsql
|
||||||
|
make
|
||||||
|
sudo make install
|
||||||
|
cd ../..
|
||||||
|
rm -rf osm2pgsql-build
|
||||||
|
else
|
||||||
|
sudo apt-get install -y -qq libexpat1 liblua${LUA_VERSION}
|
||||||
|
fi
|
||||||
|
if: matrix.ubuntu == '22'
|
||||||
|
env:
|
||||||
|
LUA_VERSION: ${{ matrix.lua }}
|
||||||
|
|
||||||
- name: Install newer pytest-asyncio
|
- name: Install test prerequisites (apt)
|
||||||
run: pip3 install -U pytest-asyncio
|
run: sudo apt-get install -y -qq python3-pytest python3-pytest-asyncio uvicorn python3-falcon python3-aiosqlite python3-pyosmium
|
||||||
if: matrix.flavour == 'ubuntu-20'
|
if: matrix.dependencies == 'apt'
|
||||||
|
|
||||||
- name: Install test prerequisites (from pip for Ubuntu 18)
|
- name: Install test prerequisites (pip)
|
||||||
run: pip3 install pytest pytest-asyncio uvicorn
|
run: ./venv/bin/pip install pytest-asyncio falcon starlette asgi_lifespan aiosqlite osmium uvicorn
|
||||||
if: matrix.flavour == 'oldstuff'
|
if: matrix.dependencies == 'pip'
|
||||||
|
|
||||||
- name: Install Python webservers
|
- name: Install test prerequisites
|
||||||
run: pip3 install falcon starlette asgi_lifespan
|
run: ./venv/bin/pip install pytest-bdd
|
||||||
|
|
||||||
- name: Install latest pylint
|
- name: Install latest flake8
|
||||||
run: pip3 install -U pylint
|
run: ./venv/bin/pip install -U flake8
|
||||||
if: matrix.flavour == 'ubuntu-22'
|
|
||||||
|
|
||||||
- name: Python linting
|
- name: Python linting
|
||||||
run: python3 -m pylint src
|
run: ../venv/bin/python -m flake8 src test/python test/bdd
|
||||||
working-directory: Nominatim
|
working-directory: Nominatim
|
||||||
if: matrix.flavour == 'ubuntu-22'
|
|
||||||
|
- name: Install mypy and typechecking info
|
||||||
|
run: ./venv/bin/pip install -U mypy types-PyYAML types-jinja2 types-psutil types-requests types-ujson types-Pygments typing-extensions
|
||||||
|
if: matrix.dependencies == 'pip'
|
||||||
|
|
||||||
|
- name: Python static typechecking
|
||||||
|
run: ../venv/bin/python -m mypy --strict --python-version 3.9 src
|
||||||
|
working-directory: Nominatim
|
||||||
|
if: matrix.dependencies == 'pip'
|
||||||
|
|
||||||
- name: Python unit tests
|
- name: Python unit tests
|
||||||
run: python3 -m pytest test/python
|
run: ../venv/bin/python -m pytest test/python
|
||||||
working-directory: Nominatim
|
working-directory: Nominatim
|
||||||
|
|
||||||
- name: BDD tests
|
- name: BDD tests
|
||||||
run: |
|
run: |
|
||||||
export PATH=$GITHUB_WORKSPACE/build/osm2pgsql:$PATH
|
../venv/bin/python -m pytest test/bdd --nominatim-purge
|
||||||
python3 -m behave -DREMOVE_TEMPLATE=1 --format=progress3
|
|
||||||
working-directory: Nominatim/test/bdd
|
|
||||||
|
|
||||||
- name: Install mypy and typechecking info
|
|
||||||
run: pip3 install -U mypy osmium uvicorn types-PyYAML types-jinja2 types-psycopg2 types-psutil types-requests types-ujson types-Pygments typing-extensions
|
|
||||||
if: matrix.flavour != 'oldstuff'
|
|
||||||
|
|
||||||
- name: Python static typechecking
|
|
||||||
run: python3 -m mypy --strict src
|
|
||||||
working-directory: Nominatim
|
working-directory: Nominatim
|
||||||
if: matrix.flavour != 'oldstuff'
|
|
||||||
|
|
||||||
legacy-test:
|
|
||||||
needs: create-archive
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
postgresql: ["13", "16"]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: full-source
|
|
||||||
|
|
||||||
- name: Unpack Nominatim
|
|
||||||
run: tar xf nominatim-src.tar.bz2
|
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: '7.4'
|
|
||||||
|
|
||||||
- uses: ./Nominatim/.github/actions/setup-postgresql
|
|
||||||
with:
|
|
||||||
postgresql-version: ${{ matrix.postgresql }}
|
|
||||||
postgis-version: 3
|
|
||||||
|
|
||||||
- name: Install Postgresql server dev
|
|
||||||
run: sudo apt-get install postgresql-server-dev-$PGVER
|
|
||||||
env:
|
|
||||||
PGVER: ${{ matrix.postgresql }}
|
|
||||||
|
|
||||||
- uses: ./Nominatim/.github/actions/build-nominatim
|
|
||||||
with:
|
|
||||||
cmake-args: -DBUILD_MODULE=on
|
|
||||||
|
|
||||||
- name: Install test prerequisites
|
|
||||||
run: sudo apt-get install -y -qq python3-behave
|
|
||||||
|
|
||||||
- name: BDD tests (legacy tokenizer)
|
|
||||||
run: |
|
|
||||||
export PATH=$GITHUB_WORKSPACE/build/osm2pgsql:$PATH
|
|
||||||
python3 -m behave -DREMOVE_TEMPLATE=1 -DSERVER_MODULE_PATH=$GITHUB_WORKSPACE/build/module -DAPI_ENGINE=php -DTOKENIZER=legacy --format=progress3
|
|
||||||
working-directory: Nominatim/test/bdd
|
|
||||||
|
|
||||||
|
|
||||||
php-test:
|
|
||||||
needs: create-archive
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: full-source
|
|
||||||
|
|
||||||
- name: Unpack Nominatim
|
|
||||||
run: tar xf nominatim-src.tar.bz2
|
|
||||||
|
|
||||||
- uses: ./Nominatim/.github/actions/setup-postgresql
|
|
||||||
with:
|
|
||||||
postgresql-version: 15
|
|
||||||
postgis-version: 3
|
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: 8.1
|
|
||||||
tools: phpunit:9, phpcs, composer
|
|
||||||
ini-values: opcache.jit=disable
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: PHP linting
|
|
||||||
run: phpcs --report-width=120 .
|
|
||||||
working-directory: Nominatim
|
|
||||||
|
|
||||||
- name: PHP unit tests
|
|
||||||
run: phpunit ./
|
|
||||||
working-directory: Nominatim/test/php
|
|
||||||
|
|
||||||
- uses: ./Nominatim/.github/actions/build-nominatim
|
|
||||||
with:
|
|
||||||
flavour: 'ubuntu-22'
|
|
||||||
|
|
||||||
- name: Install test prerequisites
|
|
||||||
run: sudo apt-get install -y -qq python3-behave
|
|
||||||
|
|
||||||
- name: BDD tests (php)
|
|
||||||
run: |
|
|
||||||
export PATH=$GITHUB_WORKSPACE/build/osm2pgsql:$PATH
|
|
||||||
python3 -m behave -DREMOVE_TEMPLATE=1 -DAPI_ENGINE=php --format=progress3
|
|
||||||
working-directory: Nominatim/test/bdd
|
|
||||||
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -288,9 +204,6 @@ jobs:
|
|||||||
- name: Prepare import environment
|
- name: Prepare import environment
|
||||||
run: |
|
run: |
|
||||||
mv Nominatim/test/testdb/apidb-test-data.pbf test.pbf
|
mv Nominatim/test/testdb/apidb-test-data.pbf test.pbf
|
||||||
mv Nominatim/settings/flex-base.lua flex-base.lua
|
|
||||||
mv Nominatim/settings/import-extratags.lua import-extratags.lua
|
|
||||||
mv Nominatim/settings/taginfo.lua taginfo.lua
|
|
||||||
rm -rf Nominatim
|
rm -rf Nominatim
|
||||||
mkdir data-env-reverse
|
mkdir data-env-reverse
|
||||||
working-directory: /home/nominatim
|
working-directory: /home/nominatim
|
||||||
@@ -298,19 +211,17 @@ jobs:
|
|||||||
- name: Add nominatim to path
|
- name: Add nominatim to path
|
||||||
run: |
|
run: |
|
||||||
sudo ln -s /home/nominatim/nominatim-venv/bin/nominatim /usr/local/bin/nominatim
|
sudo ln -s /home/nominatim/nominatim-venv/bin/nominatim /usr/local/bin/nominatim
|
||||||
if: matrix.ubuntu == 24
|
|
||||||
|
|
||||||
- name: Need lua binary
|
- name: Need lua binary
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install -y lua5.4 lua-dkjson
|
sudo apt-get install -y lua5.4 lua-dkjson
|
||||||
if: matrix.ubuntu == 24
|
|
||||||
|
|
||||||
- name: Print version
|
- name: Print version
|
||||||
run: nominatim --version
|
run: nominatim --version
|
||||||
working-directory: /home/nominatim/nominatim-project
|
working-directory: /home/nominatim/nominatim-project
|
||||||
|
|
||||||
- name: Print taginfo
|
- name: Print taginfo
|
||||||
run: lua taginfo.lua
|
run: lua ./nominatim-venv/lib/*/site-packages/nominatim_db/resources/lib-lua/taginfo.lua
|
||||||
working-directory: /home/nominatim
|
working-directory: /home/nominatim
|
||||||
|
|
||||||
- name: Collect host OS information
|
- name: Collect host OS information
|
||||||
@@ -333,19 +244,9 @@ jobs:
|
|||||||
run: nominatim admin --warm
|
run: nominatim admin --warm
|
||||||
working-directory: /home/nominatim/nominatim-project
|
working-directory: /home/nominatim/nominatim-project
|
||||||
|
|
||||||
- name: Prepare update (Ubuntu)
|
- name: Install osmium
|
||||||
run: apt-get install -y python3-pip
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Install osmium (Ubuntu 22)
|
|
||||||
run: |
|
|
||||||
pip3 install --user osmium
|
|
||||||
if: matrix.ubuntu == 22
|
|
||||||
|
|
||||||
- name: Install osmium (Ubuntu 24)
|
|
||||||
run: |
|
run: |
|
||||||
/home/nominatim/nominatim-venv/bin/pip install osmium
|
/home/nominatim/nominatim-venv/bin/pip install osmium
|
||||||
if: matrix.ubuntu == 24
|
|
||||||
|
|
||||||
- name: Run update
|
- name: Run update
|
||||||
run: |
|
run: |
|
||||||
@@ -372,7 +273,7 @@ jobs:
|
|||||||
working-directory: /home/nominatim/nominatim-project
|
working-directory: /home/nominatim/nominatim-project
|
||||||
|
|
||||||
install-no-superuser:
|
install-no-superuser:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
needs: create-archive
|
needs: create-archive
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -386,12 +287,8 @@ jobs:
|
|||||||
- uses: ./Nominatim/.github/actions/setup-postgresql
|
- uses: ./Nominatim/.github/actions/setup-postgresql
|
||||||
with:
|
with:
|
||||||
postgresql-version: 16
|
postgresql-version: 16
|
||||||
postgis-version: 3
|
|
||||||
|
|
||||||
- uses: ./Nominatim/.github/actions/build-nominatim
|
- uses: ./Nominatim/.github/actions/build-nominatim
|
||||||
with:
|
|
||||||
flavour: ubuntu-22
|
|
||||||
lua: 5.3
|
|
||||||
|
|
||||||
- name: Prepare import environment
|
- name: Prepare import environment
|
||||||
run: |
|
run: |
|
||||||
@@ -400,7 +297,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Prepare Database
|
- name: Prepare Database
|
||||||
run: |
|
run: |
|
||||||
nominatim import --prepare-database
|
./venv/bin/nominatim import --prepare-database
|
||||||
|
|
||||||
- name: Create import user
|
- name: Create import user
|
||||||
run: |
|
run: |
|
||||||
@@ -410,10 +307,51 @@ jobs:
|
|||||||
|
|
||||||
- name: Run import
|
- name: Run import
|
||||||
run: |
|
run: |
|
||||||
NOMINATIM_DATABASE_DSN="pgsql:host=127.0.0.1;dbname=nominatim;user=osm-import;password=osm-import" nominatim import --continue import-from-file --osm-file test.pbf
|
NOMINATIM_DATABASE_DSN="pgsql:host=127.0.0.1;dbname=nominatim;user=osm-import;password=osm-import" ./venv/bin/nominatim import --continue import-from-file --osm-file test.pbf
|
||||||
|
|
||||||
- name: Check full import
|
- name: Check full import
|
||||||
run: nominatim admin --check-database
|
run: ./venv/bin/nominatim admin --check-database
|
||||||
|
|
||||||
|
migrate:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs: create-archive
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: full-source
|
||||||
|
|
||||||
|
- name: Unpack Nominatim
|
||||||
|
run: tar xf nominatim-src.tar.bz2
|
||||||
|
|
||||||
|
- uses: ./Nominatim/.github/actions/setup-postgresql
|
||||||
|
with:
|
||||||
|
postgresql-version: 18
|
||||||
|
|
||||||
|
- name: Install Python dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get install --no-install-recommends virtualenv osm2pgsql
|
||||||
|
|
||||||
|
- name: Install Nominatim master version
|
||||||
|
run: |
|
||||||
|
virtualenv master
|
||||||
|
cd Nominatim
|
||||||
|
../master/bin/pip install packaging/nominatim-db
|
||||||
|
|
||||||
|
- name: Install Nominatim from pypi
|
||||||
|
run: |
|
||||||
|
virtualenv release
|
||||||
|
./release/bin/pip install nominatim-db
|
||||||
|
|
||||||
|
- name: Import Nominatim database using release
|
||||||
|
run: |
|
||||||
|
./release/bin/nominatim import --osm-file Nominatim/test/testdb/apidb-test-data.pbf
|
||||||
|
./release/bin/nominatim add-data --file Nominatim/test/testdb/additional_api_test.data.osm
|
||||||
|
|
||||||
|
- name: Migrate to master version
|
||||||
|
run: |
|
||||||
|
./master/bin/nominatim admin --migrate
|
||||||
|
./release/bin/nominatim add-data --file Nominatim/test/testdb/additional_api_test.data.osm
|
||||||
|
|
||||||
codespell:
|
codespell:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -1,4 +0,0 @@
|
|||||||
[submodule "osm2pgsql"]
|
|
||||||
path = osm2pgsql
|
|
||||||
url = https://github.com/openstreetmap/osm2pgsql.git
|
|
||||||
ignore = dirty
|
|
||||||
|
|||||||
22
.pylintrc
22
.pylintrc
@@ -1,22 +0,0 @@
|
|||||||
[MASTER]
|
|
||||||
|
|
||||||
extension-pkg-whitelist=osmium,falcon
|
|
||||||
ignored-modules=icu,datrie
|
|
||||||
|
|
||||||
[MESSAGES CONTROL]
|
|
||||||
|
|
||||||
[TYPECHECK]
|
|
||||||
|
|
||||||
# closing added here because it sometimes triggers a false positive with
|
|
||||||
# 'with' statements.
|
|
||||||
ignored-classes=NominatimArgs,closing
|
|
||||||
# 'too-many-ancestors' is triggered already by deriving from UserDict
|
|
||||||
# 'not-context-manager' disabled because it causes false positives once
|
|
||||||
# typed Python is enabled. See also https://github.com/PyCQA/pylint/issues/5273
|
|
||||||
disable=too-few-public-methods,duplicate-code,too-many-ancestors,bad-option-value,no-self-use,not-context-manager,use-dict-literal,chained-comparison,attribute-defined-outside-init,too-many-boolean-expressions,contextmanager-generator-missing-cleanup
|
|
||||||
|
|
||||||
good-names=i,j,x,y,m,t,fd,db,cc,x1,x2,y1,y2,pt,k,v,nr
|
|
||||||
|
|
||||||
[DESIGN]
|
|
||||||
|
|
||||||
max-returns=7
|
|
||||||
277
CMakeLists.txt
277
CMakeLists.txt
@@ -1,277 +0,0 @@
|
|||||||
#-----------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# CMake Config
|
|
||||||
#
|
|
||||||
# Nominatim
|
|
||||||
#
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
|
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
|
||||||
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# Project version
|
|
||||||
#
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
project(nominatim)
|
|
||||||
|
|
||||||
set(NOMINATIM_VERSION_MAJOR 4)
|
|
||||||
set(NOMINATIM_VERSION_MINOR 5)
|
|
||||||
set(NOMINATIM_VERSION_PATCH 0)
|
|
||||||
|
|
||||||
set(NOMINATIM_VERSION "${NOMINATIM_VERSION_MAJOR}.${NOMINATIM_VERSION_MINOR}.${NOMINATIM_VERSION_PATCH}")
|
|
||||||
|
|
||||||
add_definitions(-DNOMINATIM_VERSION="${NOMINATIM_VERSION}")
|
|
||||||
|
|
||||||
# Setting GIT_HASH
|
|
||||||
find_package(Git)
|
|
||||||
if (GIT_FOUND)
|
|
||||||
execute_process(
|
|
||||||
COMMAND "${GIT_EXECUTABLE}" log -1 --format=%h
|
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
|
||||||
OUTPUT_VARIABLE GIT_HASH
|
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
||||||
ERROR_QUIET
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
# Configuration
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
set(BUILD_IMPORTER on CACHE BOOL "Build everything for importing/updating the database")
|
|
||||||
set(BUILD_API on CACHE BOOL "Build everything for the API server")
|
|
||||||
set(BUILD_MODULE off CACHE BOOL "Build PostgreSQL module for legacy tokenizer")
|
|
||||||
set(BUILD_TESTS on CACHE BOOL "Build test suite")
|
|
||||||
set(BUILD_OSM2PGSQL on CACHE BOOL "Build osm2pgsql (expert only)")
|
|
||||||
set(INSTALL_MUNIN_PLUGINS on CACHE BOOL "Install Munin plugins for supervising Nominatim")
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
# 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)
|
|
||||||
add_subdirectory(osm2pgsql)
|
|
||||||
set(BUILD_TESTS ${BUILD_TESTS_SAVED})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
# python (imports/updates only)
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
if (BUILD_IMPORTER OR BUILD_API)
|
|
||||||
find_package(PythonInterp 3.7 REQUIRED)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
# PHP
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Setting PHP binary variable as to command line (prevailing) or auto detect
|
|
||||||
|
|
||||||
if (BUILD_API)
|
|
||||||
if (NOT PHP_BIN)
|
|
||||||
find_program (PHP_BIN php)
|
|
||||||
endif()
|
|
||||||
# sanity check if PHP binary exists
|
|
||||||
if (NOT EXISTS ${PHP_BIN})
|
|
||||||
message(WARNING "PHP binary not found. Only Python frontend can be used.")
|
|
||||||
set(PHP_BIN "")
|
|
||||||
else()
|
|
||||||
message (STATUS "Using PHP binary " ${PHP_BIN})
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
# import scripts and utilities (importer only)
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
if (BUILD_IMPORTER)
|
|
||||||
find_file(COUNTRY_GRID_FILE country_osm_grid.sql.gz
|
|
||||||
PATHS ${PROJECT_SOURCE_DIR}/data
|
|
||||||
NO_DEFAULT_PATH
|
|
||||||
DOC "Location of the country grid file."
|
|
||||||
)
|
|
||||||
|
|
||||||
if (NOT COUNTRY_GRID_FILE)
|
|
||||||
message(FATAL_ERROR "\nYou need to download the country_osm_grid first:\n"
|
|
||||||
" wget -O ${PROJECT_SOURCE_DIR}/data/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
configure_file(${PROJECT_SOURCE_DIR}/cmake/tool.tmpl
|
|
||||||
${PROJECT_BINARY_DIR}/nominatim)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
# Tests
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
if (BUILD_TESTS)
|
|
||||||
include(CTest)
|
|
||||||
|
|
||||||
set(TEST_BDD db osm2pgsql api)
|
|
||||||
|
|
||||||
find_program(PYTHON_BEHAVE behave)
|
|
||||||
find_program(PYLINT NAMES pylint3 pylint)
|
|
||||||
find_program(PYTEST NAMES pytest py.test-3 py.test)
|
|
||||||
find_program(PHPCS phpcs)
|
|
||||||
find_program(PHPUNIT phpunit)
|
|
||||||
|
|
||||||
if (PYTHON_BEHAVE)
|
|
||||||
message(STATUS "Using Python behave binary ${PYTHON_BEHAVE}")
|
|
||||||
foreach (test ${TEST_BDD})
|
|
||||||
add_test(NAME bdd_${test}
|
|
||||||
COMMAND ${PYTHON_BEHAVE} ${test}
|
|
||||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test/bdd)
|
|
||||||
set_tests_properties(bdd_${test}
|
|
||||||
PROPERTIES ENVIRONMENT "NOMINATIM_DIR=${PROJECT_BINARY_DIR}")
|
|
||||||
endforeach()
|
|
||||||
else()
|
|
||||||
message(WARNING "behave not found. BDD tests disabled." )
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (PHPUNIT)
|
|
||||||
message(STATUS "Using phpunit binary ${PHPUNIT}")
|
|
||||||
add_test(NAME php
|
|
||||||
COMMAND ${PHPUNIT} ./
|
|
||||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test/php)
|
|
||||||
else()
|
|
||||||
message(WARNING "phpunit not found. PHP unit tests disabled." )
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (PHPCS)
|
|
||||||
message(STATUS "Using phpcs binary ${PHPCS}")
|
|
||||||
add_test(NAME phpcs
|
|
||||||
COMMAND ${PHPCS} --report-width=120 --colors lib-php
|
|
||||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
|
||||||
else()
|
|
||||||
message(WARNING "phpcs not found. PHP linting tests disabled." )
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (PYLINT)
|
|
||||||
message(STATUS "Using pylint binary ${PYLINT}")
|
|
||||||
add_test(NAME pylint
|
|
||||||
COMMAND ${PYLINT} nominatim
|
|
||||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
|
||||||
else()
|
|
||||||
message(WARNING "pylint not found. Python linting tests disabled.")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (PYTEST)
|
|
||||||
message(STATUS "Using pytest binary ${PYTEST}")
|
|
||||||
add_test(NAME pytest
|
|
||||||
COMMAND ${PYTEST} test/python
|
|
||||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
|
||||||
else()
|
|
||||||
message(WARNING "pytest not found. Python tests disabled." )
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
# Postgres module
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
if (BUILD_MODULE)
|
|
||||||
add_subdirectory(module)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
# Installation
|
|
||||||
#-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
|
||||||
set(NOMINATIM_DATADIR ${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT_NAME})
|
|
||||||
set(NOMINATIM_LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}/${PROJECT_NAME})
|
|
||||||
set(NOMINATIM_CONFIGDIR ${CMAKE_INSTALL_FULL_SYSCONFDIR}/${PROJECT_NAME})
|
|
||||||
set(NOMINATIM_MUNINDIR ${CMAKE_INSTALL_FULL_DATADIR}/munin/plugins)
|
|
||||||
|
|
||||||
if (BUILD_IMPORTER)
|
|
||||||
configure_file(${PROJECT_SOURCE_DIR}/cmake/tool-installed.tmpl installed.bin)
|
|
||||||
install(PROGRAMS ${PROJECT_BINARY_DIR}/installed.bin
|
|
||||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
||||||
RENAME nominatim)
|
|
||||||
|
|
||||||
if (EXISTS ${PHP_BIN})
|
|
||||||
configure_file(${PROJECT_SOURCE_DIR}/cmake/paths-py.tmpl paths-py.installed)
|
|
||||||
else()
|
|
||||||
configure_file(${PROJECT_SOURCE_DIR}/cmake/paths-py-no-php.tmpl paths-py.installed)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
foreach (submodule nominatim_db nominatim_api)
|
|
||||||
install(DIRECTORY src/${submodule}
|
|
||||||
DESTINATION ${NOMINATIM_LIBDIR}/lib-python
|
|
||||||
FILES_MATCHING PATTERN "*.py"
|
|
||||||
PATTERN "paths.py" EXCLUDE
|
|
||||||
PATTERN __pycache__ EXCLUDE)
|
|
||||||
install(FILES ${PROJECT_BINARY_DIR}/paths-py.installed
|
|
||||||
DESTINATION ${NOMINATIM_LIBDIR}/lib-python/${submodule}
|
|
||||||
RENAME paths.py)
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
install(DIRECTORY lib-sql DESTINATION ${NOMINATIM_LIBDIR})
|
|
||||||
|
|
||||||
install(FILES ${COUNTRY_GRID_FILE}
|
|
||||||
data/words.sql
|
|
||||||
DESTINATION ${NOMINATIM_DATADIR})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (BUILD_OSM2PGSQL)
|
|
||||||
if (${CMAKE_VERSION} VERSION_LESS 3.13)
|
|
||||||
# Installation of subdirectory targets was only introduced in 3.13.
|
|
||||||
# So just copy the osm2pgsql file for older versions.
|
|
||||||
install(PROGRAMS ${PROJECT_BINARY_DIR}/osm2pgsql/osm2pgsql
|
|
||||||
DESTINATION ${NOMINATIM_LIBDIR})
|
|
||||||
else()
|
|
||||||
install(TARGETS osm2pgsql RUNTIME DESTINATION ${NOMINATIM_LIBDIR})
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (BUILD_MODULE)
|
|
||||||
install(PROGRAMS ${PROJECT_BINARY_DIR}/module/nominatim.so
|
|
||||||
DESTINATION ${NOMINATIM_LIBDIR}/module)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (BUILD_API AND EXISTS ${PHP_BIN})
|
|
||||||
install(DIRECTORY lib-php DESTINATION ${NOMINATIM_LIBDIR})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
install(FILES settings/env.defaults
|
|
||||||
settings/address-levels.json
|
|
||||||
settings/phrase-settings.json
|
|
||||||
settings/import-admin.lua
|
|
||||||
settings/import-street.lua
|
|
||||||
settings/import-address.lua
|
|
||||||
settings/import-full.lua
|
|
||||||
settings/import-extratags.lua
|
|
||||||
settings/flex-base.lua
|
|
||||||
settings/icu_tokenizer.yaml
|
|
||||||
settings/country_settings.yaml
|
|
||||||
DESTINATION ${NOMINATIM_CONFIGDIR})
|
|
||||||
|
|
||||||
install(DIRECTORY settings/icu-rules
|
|
||||||
DESTINATION ${NOMINATIM_CONFIGDIR})
|
|
||||||
install(DIRECTORY settings/country-names
|
|
||||||
DESTINATION ${NOMINATIM_CONFIGDIR})
|
|
||||||
|
|
||||||
if (INSTALL_MUNIN_PLUGINS)
|
|
||||||
install(FILES munin/nominatim_importlag
|
|
||||||
munin/nominatim_query_speed
|
|
||||||
munin/nominatim_requests
|
|
||||||
DESTINATION ${NOMINATIM_MUNINDIR})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
message(WARNING "Building with CMake is deprecated and will be removed in Nominatim 5.0."
|
|
||||||
"Use Nominatim pip packages instead.\n"
|
|
||||||
"See https://nominatim.org/release-docs/develop/admin/Installation/#downloading-and-building-nominatim")
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Bugs can be reported at https://github.com/openstreetmap/Nominatim/issues.
|
Bugs can be reported at https://github.com/openstreetmap/Nominatim/issues.
|
||||||
Please always open a separate issue for each problem. In particular, do
|
Please always open a separate issue for each problem. In particular, do
|
||||||
not add your bugs to closed issues. They may looks similar to you but
|
not add your bugs to closed issues. They may look similar to you but
|
||||||
often are completely different from the maintainer's point of view.
|
often are completely different from the maintainer's point of view.
|
||||||
|
|
||||||
## Workflow for Pull Requests
|
## Workflow for Pull Requests
|
||||||
@@ -21,7 +21,7 @@ that you are responsible for your pull requests. You should be prepared
|
|||||||
to get change requests because as the maintainers we have to make sure
|
to get change requests because as the maintainers we have to make sure
|
||||||
that your contribution fits well with the rest of the code. Please make
|
that your contribution fits well with the rest of the code. Please make
|
||||||
sure that you have time to react to these comments and amend the code or
|
sure that you have time to react to these comments and amend the code or
|
||||||
engage in a conversion. Do not expect that others will pick up your code,
|
engage in a conversation. Do not expect that others will pick up your code,
|
||||||
it will almost never happen.
|
it will almost never happen.
|
||||||
|
|
||||||
Please open a separate pull request for each issue you want to address.
|
Please open a separate pull request for each issue you want to address.
|
||||||
@@ -30,6 +30,19 @@ feature pull requests. If you plan to make larger changes, please open
|
|||||||
an issue first or comment on the appropriate issue already existing so
|
an issue first or comment on the appropriate issue already existing so
|
||||||
that duplicate work can be avoided.
|
that duplicate work can be avoided.
|
||||||
|
|
||||||
|
### Using AI-assisted code generators
|
||||||
|
|
||||||
|
PRs that include AI-generated content, may that be in code, in the PR
|
||||||
|
description or in documentation need to
|
||||||
|
|
||||||
|
1. clearly mark the AI-generated sections as such, for example, by
|
||||||
|
mentioning all use of AI in the PR description, and
|
||||||
|
2. include proof that you have run the generated code on an actual
|
||||||
|
installation of Nominatim. Adding and executing tests will not be
|
||||||
|
sufficient. You need to show that the code actually solves the problem
|
||||||
|
the PR claims to solve.
|
||||||
|
|
||||||
|
|
||||||
## Coding style
|
## Coding style
|
||||||
|
|
||||||
Nominatim historically hasn't followed a particular coding style but we
|
Nominatim historically hasn't followed a particular coding style but we
|
||||||
@@ -46,14 +59,11 @@ are in process of consolidating the style. The following rules apply:
|
|||||||
* no spaces after opening and before closing bracket
|
* no spaces after opening and before closing bracket
|
||||||
* leave out space between a function name and bracket
|
* leave out space between a function name and bracket
|
||||||
but add one between control statement(if, while, etc.) and bracket
|
but add one between control statement(if, while, etc.) and bracket
|
||||||
* for PHP variables use CamelCase with a prefixing letter indicating the type
|
|
||||||
(i - integer, f - float, a - array, s - string, o - object)
|
|
||||||
|
|
||||||
The coding style is enforced with PHPCS and pylint. It can be tested with:
|
The coding style is enforced with flake8. It can be tested with:
|
||||||
|
|
||||||
```
|
```
|
||||||
phpcs --report-width=120 --colors .
|
make lint
|
||||||
pylint3 --extension-pkg-whitelist=osmium nominatim
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
@@ -61,8 +71,7 @@ pylint3 --extension-pkg-whitelist=osmium nominatim
|
|||||||
Before submitting a pull request make sure that the tests pass:
|
Before submitting a pull request make sure that the tests pass:
|
||||||
|
|
||||||
```
|
```
|
||||||
cd build
|
make tests
|
||||||
make test
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
@@ -78,18 +87,18 @@ Checklist for releases:
|
|||||||
* [ ] increase versions in
|
* [ ] increase versions in
|
||||||
* `src/nominatim_api/version.py`
|
* `src/nominatim_api/version.py`
|
||||||
* `src/nominatim_db/version.py`
|
* `src/nominatim_db/version.py`
|
||||||
* CMakeLists.txt
|
|
||||||
* [ ] update `ChangeLog` (copy information from patch releases from release branch)
|
* [ ] update `ChangeLog` (copy information from patch releases from release branch)
|
||||||
* [ ] complete `docs/admin/Migration.md`
|
* [ ] complete `docs/admin/Migration.md`
|
||||||
* [ ] update EOL dates in `SECURITY.md`
|
* [ ] update EOL dates in `SECURITY.md`
|
||||||
* [ ] commit and make sure CI tests pass
|
* [ ] commit and make sure CI tests pass
|
||||||
|
* [ ] update OSMF production repo and release new version -post1 there
|
||||||
* [ ] test migration
|
* [ ] test migration
|
||||||
* download, build and import previous version
|
* download, build and import previous version
|
||||||
* migrate using master version
|
* migrate using master version
|
||||||
* run updates using master version
|
* run updates using master version
|
||||||
* [ ] prepare tarball:
|
* [ ] prepare tarball:
|
||||||
* `git clone --recursive https://github.com/osm-search/Nominatim` (switch to right branch!)
|
* `git clone https://github.com/osm-search/Nominatim` (switch to right branch!)
|
||||||
* `rm -r .git* osm2pgsql/.git*`
|
* `rm -r .git*`
|
||||||
* copy country data into `data/`
|
* copy country data into `data/`
|
||||||
* add version to base directory and package
|
* add version to base directory and package
|
||||||
* [ ] upload tarball to https://nominatim.org
|
* [ ] upload tarball to https://nominatim.org
|
||||||
@@ -104,3 +113,5 @@ Checklist for releases:
|
|||||||
* run `nominatim --version` to confirm correct version
|
* run `nominatim --version` to confirm correct version
|
||||||
* [ ] tag new release and add a release on github.com
|
* [ ] tag new release and add a release on github.com
|
||||||
* [ ] build pip packages and upload to pypi
|
* [ ] build pip packages and upload to pypi
|
||||||
|
* `make build`
|
||||||
|
* `twine upload dist/*`
|
||||||
|
|||||||
80
ChangeLog
80
ChangeLog
@@ -1,3 +1,83 @@
|
|||||||
|
5.2.0
|
||||||
|
* increase minimum required Python to 3.9
|
||||||
|
* index and output entrances of buildings and areas (thanks @emlove)
|
||||||
|
* name tags used for creating display names are now configurable
|
||||||
|
(thanks @astridx)
|
||||||
|
* new pattern-replacement query preprocessor (thanks @TuringVerified)
|
||||||
|
* special phrases can now be filtered by presence of tags (thanks @anqixxx)
|
||||||
|
* lua import style now always includes tags required by Nominatim
|
||||||
|
* improved query time reporting and logging
|
||||||
|
* improve word matching for languages with no word boundaries
|
||||||
|
* POIs with addresses inherited from surrounding building are no
|
||||||
|
longer returned in the address layer
|
||||||
|
* avoid creating a directory for the tokenizer when not needed
|
||||||
|
* replace behave with pytest-bdd for BDD testing
|
||||||
|
* refactoring and performance improvements to query parsing
|
||||||
|
* various smaller updates to styles
|
||||||
|
* remove English as default language for South Korea
|
||||||
|
* remove Japanese word variants
|
||||||
|
* updated country names for Norwegians (thanks @Johannes-Andersen)
|
||||||
|
* remove support for deprecated osm2pgsql gazetteer style
|
||||||
|
* fix updating of importances (also needs to update search_name table)
|
||||||
|
* fix query for deletable endpoint to use index again
|
||||||
|
* fix reindexing of contained places when a boundary is deleted and reinstated
|
||||||
|
* fix difference computation error when updating postcodes
|
||||||
|
* bracket handling sanitizer no longer strips bracket terms in the middle of
|
||||||
|
name
|
||||||
|
* reduce precision of stored coordinates to 7-digits everywhere
|
||||||
|
* avoid ST_Relate as it seems buggy on some systems
|
||||||
|
* remove setting for logging queries in DB, no longer functional
|
||||||
|
* postcode updates no longer require a project directory (needed for tests)
|
||||||
|
* refactor locale handling code (thanks @anqixxx)
|
||||||
|
* code updates for newer Python (thanks @emmanuel-ferdman)
|
||||||
|
* better test coverage (thanks @asharmalik19)
|
||||||
|
* various fixes and improvements to documentation
|
||||||
|
(thanks @anqixxx, @dave-meyer, @hasandiwan)
|
||||||
|
|
||||||
|
5.1.0
|
||||||
|
* replace datrie with simple internal trie implementation
|
||||||
|
* add pattern-based postcode parser for queries,
|
||||||
|
postcodes no longer need to be present in OSM to be found
|
||||||
|
* take variants into account when computing token similarity
|
||||||
|
* add extratags output to geocodejson format
|
||||||
|
* fix default layer setting used for structured queries
|
||||||
|
* update abbreviation lists for Russian and English
|
||||||
|
(thanks @shoorick, @IvanShift, @mhsrn21)
|
||||||
|
* fix variant generation for Norwegian
|
||||||
|
* fix normalization around space-like characters
|
||||||
|
* improve postcode search and handling of postcodes in queries
|
||||||
|
* reorganise internal query structure and get rid of slow enums
|
||||||
|
* enable code linting for tests
|
||||||
|
* various code moderinsations in test code (thanks @eumiro)
|
||||||
|
* remove setting osm2pgsql location via config.lib_dir
|
||||||
|
* make SQL functions parallel save as far as possible (thanks @otbutz)
|
||||||
|
* various fixes and improvements to documentation (thanks @TuringVerified)
|
||||||
|
|
||||||
|
5.0.0
|
||||||
|
* increase required versions for PostgreSQL (12+), PostGIS (3.0+)
|
||||||
|
* remove installation via cmake and debundle osm2pgsql
|
||||||
|
* remove deprecated PHP frontend
|
||||||
|
* remove deprecated legacy tokenizer
|
||||||
|
* add configurable pre-processing of queries
|
||||||
|
* add query pre-processor to split up Japanese addresses
|
||||||
|
* rewrite of osm2pgsql style implementation
|
||||||
|
(also adds support for osm2pgsql-themepark)
|
||||||
|
* reduce the number of SQL queries needed to complete a 'lookup' call
|
||||||
|
* improve computation of centroid for lines with only two points
|
||||||
|
* improve bbox output for postcode areas
|
||||||
|
* improve result order by returning the largest object when other things are
|
||||||
|
equal
|
||||||
|
* add fallback for reverse geocoding to default country tables
|
||||||
|
* exclude postcode areas from reverse geocoding
|
||||||
|
* disable search endpoint when database is reverse-only (regression)
|
||||||
|
* minor performance improvements to area split algorithm
|
||||||
|
* switch table and index creation to use autocommit mode to avoid deadlocks
|
||||||
|
* drop overly long ways during import
|
||||||
|
* restrict automatic migrations to versions 4.3+
|
||||||
|
* switch linting from pylint to flake8
|
||||||
|
* switch tests to use a wikimedia test file in the new CSV style
|
||||||
|
* various fixes and improvements to documentation
|
||||||
|
|
||||||
4.5.0
|
4.5.0
|
||||||
* allow building Nominatim as a pip package
|
* allow building Nominatim as a pip package
|
||||||
* make osm2pgsql building optional
|
* make osm2pgsql building optional
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -18,16 +18,16 @@ build-api:
|
|||||||
tests: mypy lint pytest bdd
|
tests: mypy lint pytest bdd
|
||||||
|
|
||||||
mypy:
|
mypy:
|
||||||
mypy --strict src
|
mypy --strict --python-version 3.9 src
|
||||||
|
|
||||||
pytest:
|
pytest:
|
||||||
pytest test/python
|
pytest test/python
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
pylint src
|
flake8 src test/python test/bdd
|
||||||
|
|
||||||
bdd:
|
bdd:
|
||||||
cd test/bdd; behave -DREMOVE_TEMPLATE=1
|
pytest test/bdd --nominatim-purge
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
|
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -27,21 +27,28 @@ can be found at nominatim.org as well.
|
|||||||
|
|
||||||
A quick summary of the necessary steps:
|
A quick summary of the necessary steps:
|
||||||
|
|
||||||
1. Compile Nominatim:
|
|
||||||
|
|
||||||
mkdir build
|
1. Clone this git repository and download the country grid
|
||||||
cd build
|
|
||||||
cmake ..
|
|
||||||
make
|
|
||||||
sudo make install
|
|
||||||
|
|
||||||
2. Create a project directory, get OSM data and import:
|
git clone https://github.com/osm-search/Nominatim.git
|
||||||
|
wget -O Nominatim/data/country_osm_grid.sql.gz https://nominatim.org/data/country_grid.sql.gz
|
||||||
|
|
||||||
|
2. Create a Python virtualenv and install the packages:
|
||||||
|
|
||||||
|
python3 -m venv nominatim-venv
|
||||||
|
./nominatim-venv/bin/pip install packaging/nominatim-{api,db}
|
||||||
|
|
||||||
|
3. Create a project directory, get OSM data and import:
|
||||||
|
|
||||||
mkdir nominatim-project
|
mkdir nominatim-project
|
||||||
cd nominatim-project
|
cd nominatim-project
|
||||||
nominatim import --osm-file <your planet file>
|
../nominatim-venv/bin/nominatim import --osm-file <your planet file> 2>&1 | tee setup.log
|
||||||
|
|
||||||
3. Point your webserver to the nominatim-project/website directory.
|
|
||||||
|
4. Start the webserver:
|
||||||
|
|
||||||
|
./nominatim-venv/bin/pip install uvicorn falcon
|
||||||
|
../nominatim-venv/bin/nominatim serve
|
||||||
|
|
||||||
|
|
||||||
License
|
License
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ versions.
|
|||||||
|
|
||||||
| Version | End of support for security updates |
|
| Version | End of support for security updates |
|
||||||
| ------- | ----------------------------------- |
|
| ------- | ----------------------------------- |
|
||||||
|
| 5.2.x | 2027-10-29 |
|
||||||
|
| 5.1.x | 2027-04-01 |
|
||||||
|
| 5.0.x | 2027-02-06 |
|
||||||
| 4.5.x | 2026-09-12 |
|
| 4.5.x | 2026-09-12 |
|
||||||
| 4.4.x | 2026-03-07 |
|
| 4.4.x | 2026-03-07 |
|
||||||
| 4.3.x | 2025-09-07 |
|
|
||||||
| 4.2.x | 2024-11-24 |
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
@@ -31,8 +32,7 @@ description of the nature and severity of the issue. **
|
|||||||
Patches for identified security issues are applied to all affected versions and
|
Patches for identified security issues are applied to all affected versions and
|
||||||
new minor versions are released. At the same time we release a statement at
|
new minor versions are released. At the same time we release a statement at
|
||||||
the [Nominatim blog](https://nominatim.org/blog/) describing the nature of the
|
the [Nominatim blog](https://nominatim.org/blog/) describing the nature of the
|
||||||
incident. Announcements will also be published at the
|
incident.
|
||||||
[geocoding mailinglist](https://lists.openstreetmap.org/listinfo/geocoding).
|
|
||||||
|
|
||||||
## List of Previous Incidents
|
## List of Previous Incidents
|
||||||
|
|
||||||
|
|||||||
90
VAGRANT.md
90
VAGRANT.md
@@ -1,6 +1,6 @@
|
|||||||
# Install Nominatim in a virtual machine for development and testing
|
# Install Nominatim in a virtual machine for development and testing
|
||||||
|
|
||||||
This document describes how you can install Nominatim inside a Ubuntu 22
|
This document describes how you can install Nominatim inside a Ubuntu 24
|
||||||
virtual machine on your desktop/laptop (host machine). The goal is to give
|
virtual machine on your desktop/laptop (host machine). The goal is to give
|
||||||
you a development environment to easily edit code and run the test suite
|
you a development environment to easily edit code and run the test suite
|
||||||
without affecting the rest of your system.
|
without affecting the rest of your system.
|
||||||
@@ -15,29 +15,22 @@ is.
|
|||||||
|
|
||||||
2. [Vagrant](https://www.vagrantup.com/downloads.html)
|
2. [Vagrant](https://www.vagrantup.com/downloads.html)
|
||||||
|
|
||||||
3. Nominatim
|
3. Nominatim
|
||||||
|
|
||||||
git clone --recursive https://github.com/openstreetmap/Nominatim.git
|
|
||||||
|
|
||||||
If you forgot `--recursive`, it you can later load the submodules using
|
|
||||||
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
|
|
||||||
|
git clone https://github.com/openstreetmap/Nominatim.git
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Start the virtual machine
|
1. Start the virtual machine
|
||||||
|
|
||||||
vagrant up ubuntu
|
vagrant up ubuntu24-nginx
|
||||||
|
|
||||||
2. Log into the virtual machine
|
2. Log into the virtual machine
|
||||||
|
|
||||||
vagrant ssh ubuntu
|
vagrant ssh ubuntu24-nginx
|
||||||
|
|
||||||
3. Import a small country (Monaco)
|
3. Import a small country (Monaco)
|
||||||
|
|
||||||
See the FAQ how to skip this step and point Nominatim to an existing database.
|
See the FAQ how to skip this step and point Nominatim to an existing database.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -61,73 +54,22 @@ see Nominatim in action on [localhost:8089](http://localhost:8089/nominatim/).
|
|||||||
You edit code on your host machine in any editor you like. There is no need to
|
You edit code on your host machine in any editor you like. There is no need to
|
||||||
restart any software: just refresh your browser window.
|
restart any software: just refresh your browser window.
|
||||||
|
|
||||||
Note that the webserver uses files from the /build directory. If you change
|
Use the functions of the `log()` object to create temporary debug output.
|
||||||
files in Nominatim/website or Nominatim/utils for example you first need to
|
Add `&debug=1` to the URL to see the output.
|
||||||
copy them into the /build directory by running the `cmake` step from the
|
|
||||||
installation.
|
|
||||||
|
|
||||||
PHP errors are written to `/var/log/apache2/error.log`.
|
|
||||||
|
|
||||||
With `echo` and `var_dump()` you write into the output (HTML/XML/JSON) when
|
|
||||||
you either add `&debug=1` to the URL.
|
|
||||||
|
|
||||||
In the Python BDD test you can use `logger.info()` for temporary debug
|
In the Python BDD test you can use `logger.info()` for temporary debug
|
||||||
statements.
|
statements.
|
||||||
|
|
||||||
|
For more information on running tests, see
|
||||||
|
https://nominatim.org/release-docs/develop/develop/Testing/
|
||||||
## Running unit tests
|
|
||||||
|
|
||||||
cd ~/Nominatim/tests/php
|
|
||||||
phpunit ./
|
|
||||||
|
|
||||||
|
|
||||||
## Running PHP code style tests
|
|
||||||
|
|
||||||
cd ~/Nominatim
|
|
||||||
phpcs --colors .
|
|
||||||
|
|
||||||
|
|
||||||
## Running functional tests
|
|
||||||
|
|
||||||
Tests in `test/bdd/db` and `test/bdd/osm2pgsql` have to pass 100%. Other
|
|
||||||
tests might require full planet-wide data. Sadly even if you have your own
|
|
||||||
planet-wide data there will be enough differences to the openstreetmap.org
|
|
||||||
installation to cause false positives in the other tests (see FAQ).
|
|
||||||
|
|
||||||
To run the full test suite
|
|
||||||
|
|
||||||
cd ~/Nominatim/test/bdd
|
|
||||||
behave -DBUILDDIR=/home/vagrant/build/ db osm2pgsql
|
|
||||||
|
|
||||||
To run a single file
|
|
||||||
|
|
||||||
behave -DBUILDDIR=/home/vagrant/build/ api/lookup/simple.feature
|
|
||||||
|
|
||||||
Or a single test by line number
|
|
||||||
|
|
||||||
behave -DBUILDDIR=/home/vagrant/build/ api/lookup/simple.feature:34
|
|
||||||
|
|
||||||
To run specific groups of tests you can add tags just before the `Scenario line`, e.g.
|
|
||||||
|
|
||||||
@bug-34
|
|
||||||
Scenario: address lookup for non-existing or invalid node, way, relation
|
|
||||||
|
|
||||||
and then
|
|
||||||
|
|
||||||
behave -DBUILDDIR=/home/vagrant/build/ --tags @bug-34
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
##### Will it run on Windows?
|
##### Will it run on Windows?
|
||||||
|
|
||||||
Yes, Vagrant and Virtualbox can be installed on MS Windows just fine. You need a 64bit
|
Yes, Vagrant and Virtualbox can be installed on MS Windows just fine. You need
|
||||||
version of Windows.
|
a 64bit version of Windows.
|
||||||
|
|
||||||
##### Will it run on Apple Silicon?
|
##### Will it run on Apple Silicon?
|
||||||
|
|
||||||
@@ -136,11 +78,11 @@ There is no free/open source version of Parallels.
|
|||||||
|
|
||||||
##### Why Monaco, can I use another country?
|
##### Why Monaco, can I use another country?
|
||||||
|
|
||||||
Of course! The Monaco import takes less than 30 minutes and works with 2GB RAM.
|
Of course! The Monaco import takes less than 10 minutes and works with 2GB RAM.
|
||||||
|
|
||||||
##### Will the results be the same as those from nominatim.openstreetmap.org?
|
##### Will the results be the same as those from nominatim.openstreetmap.org?
|
||||||
|
|
||||||
No. Long running Nominatim installations will differ once new import features (or
|
No. Long-running Nominatim installations will differ once new import features (or
|
||||||
bug fixes) get added since those usually only get applied to new/changed data.
|
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
|
Also this document skips the optional Wikipedia data import which affects ranking
|
||||||
@@ -188,7 +130,3 @@ e.g. `psql --host localhost --port 9999 nominatim_it`
|
|||||||
|
|
||||||
Yes. It's possible to start the virtual machine on [Amazon AWS (plugin)](https://github.com/mitchellh/vagrant-aws)
|
Yes. It's possible to start the virtual machine on [Amazon AWS (plugin)](https://github.com/mitchellh/vagrant-aws)
|
||||||
or [DigitalOcean (plugin)](https://github.com/smdahlen/vagrant-digitalocean).
|
or [DigitalOcean (plugin)](https://github.com/smdahlen/vagrant-digitalocean).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
# This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
#
|
|
||||||
# Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
# For a full list of authors see the git log.
|
|
||||||
"""
|
|
||||||
Path settings for extra data used by Nominatim (installed version).
|
|
||||||
"""
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
PHPLIB_DIR = None
|
|
||||||
SQLLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-sql').resolve()
|
|
||||||
DATA_DIR = Path('@NOMINATIM_DATADIR@').resolve()
|
|
||||||
CONFIG_DIR = Path('@NOMINATIM_CONFIGDIR@').resolve()
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
#
|
|
||||||
# This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
#
|
|
||||||
# Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
# For a full list of authors see the git log.
|
|
||||||
"""
|
|
||||||
Path settings for extra data used by Nominatim (installed version).
|
|
||||||
"""
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
PHPLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-php').resolve()
|
|
||||||
SQLLIB_DIR = (Path('@NOMINATIM_LIBDIR@') / 'lib-sql').resolve()
|
|
||||||
DATA_DIR = Path('@NOMINATIM_DATADIR@').resolve()
|
|
||||||
CONFIG_DIR = Path('@NOMINATIM_CONFIGDIR@').resolve()
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
sys.path.insert(1, '@NOMINATIM_LIBDIR@/lib-python')
|
|
||||||
|
|
||||||
from nominatim_db import cli
|
|
||||||
from nominatim_db import version
|
|
||||||
|
|
||||||
version.GIT_COMMIT_HASH = '@GIT_HASH@'
|
|
||||||
|
|
||||||
exit(cli.nominatim(module_dir='@NOMINATIM_LIBDIR@/module',
|
|
||||||
osm2pgsql_path='@NOMINATIM_LIBDIR@/osm2pgsql'))
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
sys.path.insert(1, '@CMAKE_SOURCE_DIR@/src')
|
|
||||||
|
|
||||||
from nominatim_db import cli
|
|
||||||
from nominatim_db import version
|
|
||||||
|
|
||||||
version.GIT_COMMIT_HASH = '@GIT_HASH@'
|
|
||||||
|
|
||||||
exit(cli.nominatim(module_dir='@CMAKE_BINARY_DIR@/module',
|
|
||||||
osm2pgsql_path='@CMAKE_BINARY_DIR@/osm2pgsql/osm2pgsql'))
|
|
||||||
@@ -131,76 +131,13 @@ script ([Geofabrik](https://download.geofabrik.de)) provides daily updates.
|
|||||||
|
|
||||||
## Using an external PostgreSQL database
|
## Using an external PostgreSQL database
|
||||||
|
|
||||||
You can install Nominatim using a database that runs on a different server when
|
You can install Nominatim using a database that runs on a different server.
|
||||||
you have physical access to the file system on the other server. Nominatim
|
Simply point the configuration variable `NOMINATIM_DATABASE_DSN` to the
|
||||||
uses a custom normalization library that needs to be made accessible to the
|
server and follow the standard import documentation.
|
||||||
PostgreSQL server. This section explains how to set up the normalization
|
|
||||||
library.
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
The external module is only needed when using the legacy tokenizer.
|
|
||||||
If you have chosen the ICU tokenizer, then you can ignore this section
|
|
||||||
and follow the standard import documentation.
|
|
||||||
|
|
||||||
### Option 1: Compiling the library on the database server
|
|
||||||
|
|
||||||
The most sure way to get a working library is to compile it on the database
|
|
||||||
server. From the prerequisites you need at least cmake, gcc and the
|
|
||||||
PostgreSQL server package.
|
|
||||||
|
|
||||||
Clone or unpack the Nominatim source code, enter the source directory and
|
|
||||||
create and enter a build directory.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd Nominatim
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
```
|
|
||||||
|
|
||||||
Now configure cmake to only build the PostgreSQL module and build it:
|
|
||||||
|
|
||||||
```
|
|
||||||
cmake -DBUILD_IMPORTER=off -DBUILD_API=off -DBUILD_TESTS=off -DBUILD_DOCS=off -DBUILD_OSM2PGSQL=off ..
|
|
||||||
make
|
|
||||||
```
|
|
||||||
|
|
||||||
When done, you find the normalization library in `build/module/nominatim.so`.
|
|
||||||
Copy it to a place where it is readable and executable by the PostgreSQL server
|
|
||||||
process.
|
|
||||||
|
|
||||||
### Option 2: Compiling the library on the import machine
|
|
||||||
|
|
||||||
You can also compile the normalization library on the machine from where you
|
|
||||||
run the import.
|
|
||||||
|
|
||||||
!!! important
|
|
||||||
You can only do this when the database server and the import machine have
|
|
||||||
the same architecture and run the same version of Linux. Otherwise there is
|
|
||||||
no guarantee that the compiled library is compatible with the PostgreSQL
|
|
||||||
server running on the database server.
|
|
||||||
|
|
||||||
Make sure that the PostgreSQL server package is installed on the machine
|
|
||||||
**with the same version as on the database server**. You do not need to install
|
|
||||||
the PostgreSQL server itself.
|
|
||||||
|
|
||||||
Download and compile Nominatim as per standard instructions. Once done, you find
|
|
||||||
the normalization library in `build/module/nominatim.so`. Copy the file to
|
|
||||||
the database server at a location where it is readable and executable by the
|
|
||||||
PostgreSQL server process.
|
|
||||||
|
|
||||||
### Running the import
|
|
||||||
|
|
||||||
On the client side you now need to configure the import to point to the
|
|
||||||
correct location of the library **on the database server**. Add the following
|
|
||||||
line to your your `.env` file:
|
|
||||||
|
|
||||||
```php
|
|
||||||
NOMINATIM_DATABASE_MODULE_PATH="<directory on the database server where nominatim.so resides>"
|
|
||||||
```
|
|
||||||
|
|
||||||
Now change the `NOMINATIM_DATABASE_DSN` to point to your remote server and continue
|
|
||||||
to follow the [standard instructions for importing](Import.md).
|
|
||||||
|
|
||||||
|
The import will be faster, if the import is run directly from the database
|
||||||
|
machine. You can easily switch to a different machine for the query frontend
|
||||||
|
after the import.
|
||||||
|
|
||||||
## Moving the database to another machine
|
## Moving the database to another machine
|
||||||
|
|
||||||
@@ -225,20 +162,9 @@ target machine.
|
|||||||
data updates but the resulting database is only about a third of the size
|
data updates but the resulting database is only about a third of the size
|
||||||
of a full database.
|
of a full database.
|
||||||
|
|
||||||
Next install Nominatim on the target machine by following the standard installation
|
Next install nominatim-api on the target machine by following the standard
|
||||||
instructions. Again, make sure to use the same version as the source machine.
|
installation instructions. Again, make sure to use the same version as the
|
||||||
|
source machine.
|
||||||
|
|
||||||
Create a project directory on your destination machine and set up the `.env`
|
Create a project directory on your destination machine and set up the `.env`
|
||||||
file to match the configuration on the source machine. Finally run
|
file to match the configuration on the source machine. That's all.
|
||||||
|
|
||||||
nominatim refresh --website
|
|
||||||
|
|
||||||
to make sure that the local installation of Nominatim will be used.
|
|
||||||
|
|
||||||
If you are using the legacy tokenizer you might also have to switch to the
|
|
||||||
PostgreSQL module that was compiled on your target machine. If you get errors
|
|
||||||
that PostgreSQL cannot find or access `nominatim.so` then rerun
|
|
||||||
|
|
||||||
nominatim refresh --functions
|
|
||||||
|
|
||||||
on the target machine to update the the location of the module.
|
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
# Deploying Nominatim using the PHP frontend
|
|
||||||
|
|
||||||
!!! danger
|
|
||||||
The PHP frontend is deprecated and will be removed in Nominatim 5.0.
|
|
||||||
|
|
||||||
The Nominatim API is implemented as a PHP application. The `website/` directory
|
|
||||||
in the project directory contains the configured website. You can serve this
|
|
||||||
in a production environment with any web server that is capable to run
|
|
||||||
PHP scripts.
|
|
||||||
|
|
||||||
This section gives a quick overview on how to configure Apache and Nginx to
|
|
||||||
serve Nominatim. It is not meant as a full system administration guide on how
|
|
||||||
to run a web service. Please refer to the documentation of
|
|
||||||
[Apache](https://httpd.apache.org/docs/current/) and
|
|
||||||
[Nginx](https://nginx.org/en/docs/)
|
|
||||||
for background information on configuring the services.
|
|
||||||
|
|
||||||
!!! Note
|
|
||||||
Throughout this page, we assume your Nominatim project directory is
|
|
||||||
located in `/srv/nominatim-project` and you have installed Nominatim
|
|
||||||
using the default installation prefix `/usr/local`. If you have put it
|
|
||||||
somewhere else, you need to adjust the commands and configuration
|
|
||||||
accordingly.
|
|
||||||
|
|
||||||
We further assume that your web server runs as user `www-data`. Older
|
|
||||||
versions of CentOS may still use the user name `apache`. You also need
|
|
||||||
to adapt the instructions in this case.
|
|
||||||
|
|
||||||
## Making the website directory accessible
|
|
||||||
|
|
||||||
You need to make sure that the `website` directory is accessible for the
|
|
||||||
web server user. You can check that the permissions are correct by accessing
|
|
||||||
on of the php files as the web server user:
|
|
||||||
|
|
||||||
``` sh
|
|
||||||
sudo -u www-data head -n 1 /srv/nominatim-project/website/search.php
|
|
||||||
```
|
|
||||||
|
|
||||||
If this shows a permission error, then you need to adapt the permissions of
|
|
||||||
each directory in the path so that it is executable for `www-data`.
|
|
||||||
|
|
||||||
If you have SELinux enabled, further adjustments may be necessary to give the
|
|
||||||
web server access. At a minimum the following SELinux labelling should be done
|
|
||||||
for Nominatim:
|
|
||||||
|
|
||||||
``` sh
|
|
||||||
sudo semanage fcontext -a -t httpd_sys_content_t "/usr/local/nominatim/lib/lib-php(/.*)?"
|
|
||||||
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/nominatim-project/website(/.*)?"
|
|
||||||
sudo semanage fcontext -a -t lib_t "/srv/nominatim-project/module/nominatim.so"
|
|
||||||
sudo restorecon -R -v /usr/local/lib/nominatim
|
|
||||||
sudo restorecon -R -v /srv/nominatim-project
|
|
||||||
```
|
|
||||||
|
|
||||||
## Nominatim with Apache
|
|
||||||
|
|
||||||
### Installing the required packages
|
|
||||||
|
|
||||||
With Apache you can use the PHP module to run Nominatim.
|
|
||||||
|
|
||||||
Under Ubuntu/Debian install them with:
|
|
||||||
|
|
||||||
``` sh
|
|
||||||
sudo apt install apache2 libapache2-mod-php
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuring Apache
|
|
||||||
|
|
||||||
Make sure your Apache configuration contains the required permissions for the
|
|
||||||
directory and create an alias:
|
|
||||||
|
|
||||||
``` apache
|
|
||||||
<Directory "/srv/nominatim-project/website">
|
|
||||||
Options FollowSymLinks MultiViews
|
|
||||||
AddType text/html .php
|
|
||||||
DirectoryIndex search.php
|
|
||||||
Require all granted
|
|
||||||
</Directory>
|
|
||||||
Alias /nominatim /srv/nominatim-project/website
|
|
||||||
```
|
|
||||||
|
|
||||||
After making changes in the apache config you need to restart apache.
|
|
||||||
The website should now be available on `http://localhost/nominatim`.
|
|
||||||
|
|
||||||
## Nominatim with Nginx
|
|
||||||
|
|
||||||
### Installing the required packages
|
|
||||||
|
|
||||||
Nginx has no built-in PHP interpreter. You need to use php-fpm as a daemon for
|
|
||||||
serving PHP cgi.
|
|
||||||
|
|
||||||
On Ubuntu/Debian install nginx and php-fpm with:
|
|
||||||
|
|
||||||
``` sh
|
|
||||||
sudo apt install nginx php-fpm
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configure php-fpm and Nginx
|
|
||||||
|
|
||||||
By default php-fpm listens on a network socket. If you want it to listen to a
|
|
||||||
Unix socket instead, change the pool configuration
|
|
||||||
(`/etc/php/<php version>/fpm/pool.d/www.conf`) as follows:
|
|
||||||
|
|
||||||
``` ini
|
|
||||||
; Replace the tcp listener and add the unix socket
|
|
||||||
listen = /var/run/php-fpm-nominatim.sock
|
|
||||||
|
|
||||||
; Ensure that the daemon runs as the correct user
|
|
||||||
listen.owner = www-data
|
|
||||||
listen.group = www-data
|
|
||||||
listen.mode = 0666
|
|
||||||
```
|
|
||||||
|
|
||||||
Tell nginx that php files are special and to fastcgi_pass to the php-fpm
|
|
||||||
unix socket by adding the location definition to the default configuration.
|
|
||||||
|
|
||||||
``` nginx
|
|
||||||
root /srv/nominatim-project/website;
|
|
||||||
index search.php;
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ @php;
|
|
||||||
}
|
|
||||||
|
|
||||||
location @php {
|
|
||||||
fastcgi_param SCRIPT_FILENAME "$document_root$uri.php";
|
|
||||||
fastcgi_param PATH_TRANSLATED "$document_root$uri.php";
|
|
||||||
fastcgi_param QUERY_STRING $args;
|
|
||||||
fastcgi_pass unix:/var/run/php-fpm-nominatim.sock;
|
|
||||||
fastcgi_index index.php;
|
|
||||||
include fastcgi_params;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ [^/]\.php(/|$) {
|
|
||||||
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
|
||||||
if (!-f $document_root$fastcgi_script_name) {
|
|
||||||
return 404;
|
|
||||||
}
|
|
||||||
fastcgi_pass unix:/var/run/php-fpm-nominatim.sock;
|
|
||||||
fastcgi_index search.php;
|
|
||||||
include fastcgi.conf;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Restart the nginx and php-fpm services and the website should now be available
|
|
||||||
at `http://localhost/`.
|
|
||||||
|
|
||||||
## Nominatim with other webservers
|
|
||||||
|
|
||||||
Users have created instructions for other webservers:
|
|
||||||
|
|
||||||
* [Caddy](https://github.com/osm-search/Nominatim/discussions/2580)
|
|
||||||
|
|
||||||
@@ -10,14 +10,14 @@ Nominatim. Please refer to the documentation of
|
|||||||
[Nginx](https://nginx.org/en/docs/) for background information on how
|
[Nginx](https://nginx.org/en/docs/) for background information on how
|
||||||
to configure it.
|
to configure it.
|
||||||
|
|
||||||
!!! Note
|
|
||||||
Throughout this page, we assume your Nominatim project directory is
|
|
||||||
located in `/srv/nominatim-project`. If you have put it somewhere else,
|
|
||||||
you need to adjust the commands and configuration accordingly.
|
|
||||||
|
|
||||||
|
|
||||||
### Installing the required packages
|
### Installing the required packages
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
ASGI support in gunicorn requires at least version 25.0. If you need
|
||||||
|
to work with an older version of gunicorn, please refer to
|
||||||
|
[older Nominatim deployment documentation](https://nominatim.org/release-docs/5.2/admin/Deployment-Python/)
|
||||||
|
to learn how to run gunicorn with uvicorn.
|
||||||
|
|
||||||
The Nominatim frontend is best run from its own virtual environment. If
|
The Nominatim frontend is best run from its own virtual environment. If
|
||||||
you have already created one for the database backend during the
|
you have already created one for the database backend during the
|
||||||
[installation](Installation.md#building-nominatim), you can use that. Otherwise
|
[installation](Installation.md#building-nominatim), you can use that. Otherwise
|
||||||
@@ -37,23 +37,27 @@ cd Nominatim
|
|||||||
```
|
```
|
||||||
|
|
||||||
The recommended way to deploy a Python ASGI application is to run
|
The recommended way to deploy a Python ASGI application is to run
|
||||||
the ASGI runner [uvicorn](https://uvicorn.org/)
|
the [gunicorn](https://gunicorn.org/) HTTP server. We use
|
||||||
together with [gunicorn](https://gunicorn.org/) HTTP server. We use
|
|
||||||
Falcon here as the web framework.
|
Falcon here as the web framework.
|
||||||
|
|
||||||
Add the necessary packages to your virtual environment:
|
Add the necessary packages to your virtual environment:
|
||||||
|
|
||||||
``` sh
|
``` sh
|
||||||
/srv/nominatim-venv/bin/pip install falcon uvicorn gunicorn
|
/srv/nominatim-venv/bin/pip install falcon gunicorn
|
||||||
```
|
```
|
||||||
|
|
||||||
### Setting up Nominatim as a systemd job
|
### Setting up Nominatim as a systemd job
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
These instructions assume your Nominatim project directory is
|
||||||
|
located in `/srv/nominatim-project`. If you have put it somewhere else,
|
||||||
|
you need to adjust the commands and configuration accordingly.
|
||||||
|
|
||||||
Next you need to set up the service that runs the Nominatim frontend. This is
|
Next you need to set up the service that runs the Nominatim frontend. This is
|
||||||
easiest done with a systemd job.
|
easiest done with a systemd job.
|
||||||
|
|
||||||
First you need to tell systemd to create a socket file to be used by
|
First you need to tell systemd to create a socket file to be used by
|
||||||
hunicorn. Create the following file `/etc/systemd/system/nominatim.socket`:
|
gunicorn. Create the following file `/etc/systemd/system/nominatim.socket`:
|
||||||
|
|
||||||
``` systemd
|
``` systemd
|
||||||
[Unit]
|
[Unit]
|
||||||
@@ -81,10 +85,8 @@ Type=simple
|
|||||||
User=www-data
|
User=www-data
|
||||||
Group=www-data
|
Group=www-data
|
||||||
WorkingDirectory=/srv/nominatim-project
|
WorkingDirectory=/srv/nominatim-project
|
||||||
ExecStart=/srv/nominatim-venv/bin/gunicorn -b unix:/run/nominatim.sock -w 4 -k uvicorn.workers.UvicornWorker nominatim_api.server.falcon.server:run_wsgi
|
ExecStart=/srv/nominatim-venv/bin/gunicorn -b unix:/run/nominatim.sock -w 4 --worker-class asgi --protocol uwsgi --worker-connections 1000 "nominatim_api.server.falcon.server:run_wsgi()"
|
||||||
ExecReload=/bin/kill -s HUP $MAINPID
|
ExecReload=/bin/kill -s HUP $MAINPID
|
||||||
StandardOutput=append:/var/log/gunicorn-nominatim.log
|
|
||||||
StandardError=inherit
|
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
TimeoutStopSec=5
|
TimeoutStopSec=5
|
||||||
KillMode=mixed
|
KillMode=mixed
|
||||||
@@ -96,7 +98,10 @@ WantedBy=multi-user.target
|
|||||||
This sets up gunicorn with 4 workers (`-w 4` in ExecStart). Each worker runs
|
This sets up gunicorn with 4 workers (`-w 4` in ExecStart). Each worker runs
|
||||||
its own Python process using
|
its own Python process using
|
||||||
[`NOMINATIM_API_POOL_SIZE`](../customize/Settings.md#nominatim_api_pool_size)
|
[`NOMINATIM_API_POOL_SIZE`](../customize/Settings.md#nominatim_api_pool_size)
|
||||||
connections to the database to serve requests in parallel.
|
connections to the database to serve requests in parallel. The parameter
|
||||||
|
`--worker-connections` restricts how many requests gunicorn will queue for
|
||||||
|
each worker. This can help distribute work better when the server is under
|
||||||
|
high load.
|
||||||
|
|
||||||
Make the new services known to systemd and start it:
|
Make the new services known to systemd and start it:
|
||||||
|
|
||||||
@@ -108,13 +113,15 @@ sudo systemctl enable nominatim.service
|
|||||||
sudo systemctl start nominatim.service
|
sudo systemctl start nominatim.service
|
||||||
```
|
```
|
||||||
|
|
||||||
This sets the service up, so that Nominatim is automatically started
|
This sets the service up so that Nominatim is automatically started
|
||||||
on reboot.
|
on reboot.
|
||||||
|
|
||||||
### Configuring nginx
|
### Configuring nginx
|
||||||
|
|
||||||
To make the service available to the world, you need to proxy it through
|
To make the service available to the world, you need to proxy it through
|
||||||
nginx. Add the following definition to the default configuration:
|
nginx. We use the binary uwsgi protocol to speed up communication
|
||||||
|
between nginx and gunicorn. Add the following definition to the default
|
||||||
|
configuration:
|
||||||
|
|
||||||
``` nginx
|
``` nginx
|
||||||
upstream nominatim_service {
|
upstream nominatim_service {
|
||||||
@@ -129,11 +136,8 @@ server {
|
|||||||
index /search;
|
index /search;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_set_header Host $http_host;
|
uwsgi_pass nominatim_service;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
include uwsgi_params;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_redirect off;
|
|
||||||
proxy_pass http://nominatim_service;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -37,31 +37,6 @@ nominatim import --continue indexing
|
|||||||
Otherwise it's best to start the full setup from the beginning.
|
Otherwise it's best to start the full setup from the beginning.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### nominatim.so version mismatch
|
|
||||||
|
|
||||||
When running the import you may get a version mismatch:
|
|
||||||
`COPY_END for place failed: ERROR: incompatible library "/srv/Nominatim/nominatim/build/module/nominatim.so": version mismatch`
|
|
||||||
|
|
||||||
pg_config seems to use bad includes sometimes when multiple versions
|
|
||||||
of PostgreSQL are available in the system. Make sure you remove the
|
|
||||||
server development libraries (`postgresql-server-dev-13` on Ubuntu)
|
|
||||||
and recompile (`cmake .. && make`).
|
|
||||||
|
|
||||||
|
|
||||||
### I see the error "ERROR: permission denied for language c"
|
|
||||||
|
|
||||||
`nominatim.so`, written in C, is required to be installed on the database
|
|
||||||
server. Some managed database (cloud) services like Amazon RDS do not allow
|
|
||||||
this. There is currently no work-around other than installing a database
|
|
||||||
on a non-managed machine.
|
|
||||||
|
|
||||||
|
|
||||||
### I see the error: "function transliteration(text) does not exist"
|
|
||||||
|
|
||||||
Reinstall the nominatim functions with `nominatim refresh --functions`
|
|
||||||
and check for any errors, e.g. a missing `nominatim.so` file.
|
|
||||||
|
|
||||||
### I see the error: "ERROR: mmap (remap) failed"
|
### I see the error: "ERROR: mmap (remap) failed"
|
||||||
|
|
||||||
This may be a simple out-of-memory error. Try reducing the memory used
|
This may be a simple out-of-memory error. Try reducing the memory used
|
||||||
@@ -103,39 +78,6 @@ for default Ubuntu operating system for example it's `www-data`.
|
|||||||
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "www-data";
|
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "www-data";
|
||||||
```
|
```
|
||||||
|
|
||||||
### Website reports "Could not load library "nominatim.so"
|
|
||||||
|
|
||||||
Example error message
|
|
||||||
|
|
||||||
```
|
|
||||||
SELECT make_standard_name('3039 E MEADOWLARK LN') [nativecode=ERROR: could not
|
|
||||||
load library "/srv/nominatim/Nominatim-3.1.0/build/module/nominatim.so":
|
|
||||||
/srv/nominatim/Nominatim-3.1.0/build/module/nominatim.so: cannot open shared
|
|
||||||
object file: Permission denied
|
|
||||||
CONTEXT: PL/pgSQL function make_standard_name(text) line 5 at assignment]
|
|
||||||
```
|
|
||||||
|
|
||||||
The PostgreSQL database, i.e. user `postgres`, needs to have access to that file.
|
|
||||||
|
|
||||||
The 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
|
|
||||||
```
|
|
||||||
|
|
||||||
Try `chmod a+r nominatim.so; chmod a+x nominatim.so`.
|
|
||||||
|
|
||||||
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 fails with "DB Error: extension not found"
|
### Setup fails with "DB Error: extension not found"
|
||||||
|
|
||||||
Make sure you have the PostgreSQL extensions "hstore" and "postgis" installed.
|
Make sure you have the PostgreSQL extensions "hstore" and "postgis" installed.
|
||||||
@@ -172,4 +114,6 @@ for more information.
|
|||||||
|
|
||||||
### Can I import negative OSM ids into Nominatim?
|
### Can I import negative OSM ids into Nominatim?
|
||||||
|
|
||||||
See [this question of Stackoverflow](https://help.openstreetmap.org/questions/64662/nominatim-flatnode-with-negative-id).
|
No, negative IDs are no longer supported by osm2pgsql. You can use
|
||||||
|
large 64-bit IDs that are guaranteed not to clash with OSM IDs. However,
|
||||||
|
you will not able to use a flatnode file with them.
|
||||||
|
|||||||
@@ -257,8 +257,8 @@ successfully.
|
|||||||
nominatim admin --check-database
|
nominatim admin --check-database
|
||||||
```
|
```
|
||||||
|
|
||||||
Now you can try out your installation by executing a simple query on the
|
If you have installed the `nominatim-api` package, then you can try out
|
||||||
command line:
|
your installation by executing a simple query on the command line:
|
||||||
|
|
||||||
``` sh
|
``` sh
|
||||||
nominatim search --query Berlin
|
nominatim search --query Berlin
|
||||||
@@ -270,10 +270,8 @@ or, when you have a reverse-only installation:
|
|||||||
nominatim reverse --lat 51 --lon 45
|
nominatim reverse --lat 51 --lon 45
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to run Nominatim as a service, you need to make a choice between
|
If you want to run Nominatim as a service, make sure you have installed
|
||||||
running the modern Python frontend and the legacy PHP frontend.
|
the right packages as per [Installation](Installation.md#software).
|
||||||
Make sure you have installed the right packages as per
|
|
||||||
[Installation](Installation.md#software).
|
|
||||||
|
|
||||||
#### Testing the Python frontend
|
#### Testing the Python frontend
|
||||||
|
|
||||||
@@ -291,36 +289,15 @@ or, if you prefer to use Starlette instead of Falcon as webserver,
|
|||||||
nominatim serve --engine starlette
|
nominatim serve --engine starlette
|
||||||
```
|
```
|
||||||
|
|
||||||
Go to `http://localhost:8088/status.php` and you should see the message `OK`.
|
Go to `http://localhost:8088/status` and you should see the message `OK`.
|
||||||
You can also run a search query, e.g. `http://localhost:8088/search.php?q=Berlin`
|
You can also run a search query, e.g. `http://localhost:8088/search?q=Berlin`
|
||||||
or, for reverse-only installations a reverse query,
|
or, for reverse-only installations a reverse query,
|
||||||
e.g. `http://localhost:8088/reverse.php?lat=27.1750090510034&lon=78.04209025`.
|
e.g. `http://localhost:8088/reverse?lat=27.1750090510034&lon=78.04209025`.
|
||||||
|
|
||||||
Do not use this test server in production.
|
Do not use this test server in production.
|
||||||
To run Nominatim via webservers like Apache or nginx, please continue reading
|
To run Nominatim via webservers like Apache or nginx, please continue reading
|
||||||
[Deploy the Python frontend](Deployment-Python.md).
|
[Deploy the Python frontend](Deployment-Python.md).
|
||||||
|
|
||||||
#### Testing the PHP frontend
|
|
||||||
|
|
||||||
!!! danger
|
|
||||||
The PHP fronted is deprecated and will be removed in Nominatim 5.0.
|
|
||||||
|
|
||||||
You can run a small test server with the PHP frontend like this:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
nominatim serve --engine php
|
|
||||||
```
|
|
||||||
|
|
||||||
Go to `http://localhost:8088/status.php` and you should see the message `OK`.
|
|
||||||
You can also run a search query, e.g. `http://localhost:8088/search.php?q=Berlin`
|
|
||||||
or, for reverse-only installations a reverse query,
|
|
||||||
e.g. `http://localhost:8088/reverse.php?lat=27.1750090510034&lon=78.04209025`.
|
|
||||||
|
|
||||||
Do not use this test server in production.
|
|
||||||
To run Nominatim via webservers like Apache or nginx, please continue reading
|
|
||||||
[Deploy the PHP frontend](Deployment-PHP.md).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Enabling search by category phrases
|
## Enabling search by category phrases
|
||||||
|
|
||||||
|
|||||||
@@ -22,17 +22,12 @@ and can't offer support.
|
|||||||
|
|
||||||
### Software
|
### Software
|
||||||
|
|
||||||
!!! Warning
|
|
||||||
For larger installations you **must have** PostgreSQL 11+ and PostGIS 3+
|
|
||||||
otherwise import and queries will be slow to the point of being unusable.
|
|
||||||
Query performance has marked improvements with PostgreSQL 13+ and PostGIS 3.2+.
|
|
||||||
|
|
||||||
For running Nominatim:
|
For running Nominatim:
|
||||||
|
|
||||||
* [PostgreSQL](https://www.postgresql.org) (9.6+ will work, 11+ strongly recommended)
|
* [PostgreSQL](https://www.postgresql.org) (12+ will work, 13+ strongly recommended)
|
||||||
* [PostGIS](https://postgis.net) (2.2+ will work, 3.0+ strongly recommended)
|
* [PostGIS](https://postgis.net) (3.0+ will work, 3.2+ strongly recommended)
|
||||||
* [osm2pgsql](https://osm2pgsql.org) (1.8+, optional when building with CMake)
|
* [osm2pgsql](https://osm2pgsql.org) (1.8+)
|
||||||
* [Python 3](https://www.python.org/) (3.7+)
|
* [Python 3](https://www.python.org/) (3.9+)
|
||||||
|
|
||||||
Furthermore the following Python libraries are required:
|
Furthermore the following Python libraries are required:
|
||||||
|
|
||||||
@@ -42,23 +37,10 @@ Furthermore the following Python libraries are required:
|
|||||||
* [Jinja2](https://palletsprojects.com/p/jinja/)
|
* [Jinja2](https://palletsprojects.com/p/jinja/)
|
||||||
* [PyICU](https://pypi.org/project/PyICU/)
|
* [PyICU](https://pypi.org/project/PyICU/)
|
||||||
* [PyYaml](https://pyyaml.org/) (5.1+)
|
* [PyYaml](https://pyyaml.org/) (5.1+)
|
||||||
* [datrie](https://github.com/pytries/datrie)
|
* [mwparserfromhell](https://github.com/earwig/mwparserfromhell/)
|
||||||
|
|
||||||
These will be installed automatically when using pip installation.
|
These will be installed automatically when using pip installation.
|
||||||
|
|
||||||
When using legacy CMake-based installation:
|
|
||||||
|
|
||||||
* [cmake](https://cmake.org/)
|
|
||||||
* [expat](https://libexpat.github.io/)
|
|
||||||
* [proj](https://proj.org/)
|
|
||||||
* [bzip2](http://www.bzip.org/)
|
|
||||||
* [zlib](https://www.zlib.net/)
|
|
||||||
* [ICU](http://site.icu-project.org/)
|
|
||||||
* [nlohmann/json](https://json.nlohmann.me/)
|
|
||||||
* [Boost libraries](https://www.boost.org/), including system and file system
|
|
||||||
* PostgreSQL client libraries
|
|
||||||
* a recent C++ compiler (gcc 5+ or Clang 3.8+)
|
|
||||||
|
|
||||||
For running continuous updates:
|
For running continuous updates:
|
||||||
|
|
||||||
* [pyosmium](https://osmcode.org/pyosmium/)
|
* [pyosmium](https://osmcode.org/pyosmium/)
|
||||||
@@ -72,13 +54,6 @@ For running the Python frontend:
|
|||||||
* [starlette](https://www.starlette.io/)
|
* [starlette](https://www.starlette.io/)
|
||||||
* [uvicorn](https://www.uvicorn.org/)
|
* [uvicorn](https://www.uvicorn.org/)
|
||||||
|
|
||||||
For running the legacy PHP frontend (deprecated, will be removed in Nominatim 5.0):
|
|
||||||
|
|
||||||
* [PHP](https://php.net) (7.3+)
|
|
||||||
* PHP-pgsql
|
|
||||||
* PHP-intl (bundled with PHP)
|
|
||||||
|
|
||||||
|
|
||||||
For dependencies for running tests and building documentation, see
|
For dependencies for running tests and building documentation, see
|
||||||
the [Development section](../develop/Development-Environment.md).
|
the [Development section](../develop/Development-Environment.md).
|
||||||
|
|
||||||
@@ -127,6 +102,15 @@ you might consider setting:
|
|||||||
and even reduce `autovacuum_work_mem` further. This will reduce the amount
|
and even reduce `autovacuum_work_mem` further. This will reduce the amount
|
||||||
of memory that autovacuum takes away from the import process.
|
of memory that autovacuum takes away from the import process.
|
||||||
|
|
||||||
|
## Installing the latest release
|
||||||
|
|
||||||
|
Nominatim is easiest installed directly from Pypi. Make sure you have installed
|
||||||
|
osm2pgsql, PostgreSQL/PostGIS and libICU together with its header files.
|
||||||
|
|
||||||
|
Then you can install Nominatim with:
|
||||||
|
|
||||||
|
pip install nominatim-db nominatim-api
|
||||||
|
|
||||||
## Downloading and building Nominatim
|
## Downloading and building Nominatim
|
||||||
|
|
||||||
### Downloading the latest release
|
### Downloading the latest release
|
||||||
@@ -136,11 +120,10 @@ The release contains all necessary files. Just unpack it.
|
|||||||
|
|
||||||
### Downloading the latest development version
|
### Downloading the latest development version
|
||||||
|
|
||||||
If you want to install latest development version from github, make sure to
|
If you want to install latest development version from github:
|
||||||
also check out the osm2pgsql subproject:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone --recursive https://github.com/openstreetmap/Nominatim.git
|
git clone https://github.com/osm-search/Nominatim.git
|
||||||
```
|
```
|
||||||
|
|
||||||
The development version does not include the country grid. Download it separately:
|
The development version does not include the country grid. Download it separately:
|
||||||
@@ -151,8 +134,6 @@ wget -O Nominatim/data/country_osm_grid.sql.gz https://nominatim.org/data/countr
|
|||||||
|
|
||||||
### Building Nominatim
|
### Building Nominatim
|
||||||
|
|
||||||
#### Building the latest development version with pip
|
|
||||||
|
|
||||||
Nominatim is easiest to run from its own virtual environment. To create one, run:
|
Nominatim is easiest to run from its own virtual environment. To create one, run:
|
||||||
|
|
||||||
sudo apt-get install virtualenv
|
sudo apt-get install virtualenv
|
||||||
@@ -162,48 +143,5 @@ To install Nominatim directly from the source tree into the virtual environment,
|
|||||||
|
|
||||||
/srv/nominatim-venv/bin/pip install packaging/nominatim-{db,api}
|
/srv/nominatim-venv/bin/pip install packaging/nominatim-{db,api}
|
||||||
|
|
||||||
#### Building in legacy CMake mode
|
|
||||||
|
|
||||||
!!! warning
|
|
||||||
Installing Nominatim through CMake is now deprecated. The infrastructure
|
|
||||||
will be removed in Nominatim 5.0. Please switch to pip installation.
|
|
||||||
|
|
||||||
The code must be built in a separate directory. Create the directory and
|
|
||||||
change into it.
|
|
||||||
|
|
||||||
```
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
```
|
|
||||||
|
|
||||||
Nominatim uses cmake and make for building. Assuming that you have created the
|
|
||||||
build at the same level as the Nominatim source directory run:
|
|
||||||
|
|
||||||
```
|
|
||||||
cmake ../Nominatim
|
|
||||||
make
|
|
||||||
sudo make install
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! warning
|
|
||||||
The default installation no longer compiles the PostgreSQL module that
|
|
||||||
is needed for the legacy tokenizer from older Nominatim versions. If you
|
|
||||||
are upgrading an older database or want to run the
|
|
||||||
[legacy tokenizer](../customize/Tokenizers.md#legacy-tokenizer) for
|
|
||||||
some other reason, you need to enable the PostgreSQL module via
|
|
||||||
cmake: `cmake -DBUILD_MODULE=on ../Nominatim`. To compile the module
|
|
||||||
you need to have the server development headers for PostgreSQL installed.
|
|
||||||
On Ubuntu/Debian run: `sudo apt install postgresql-server-dev-<postgresql version>`
|
|
||||||
The legacy tokenizer is deprecated and will be removed in Nominatim 5.0
|
|
||||||
|
|
||||||
|
|
||||||
Nominatim installs itself into `/usr/local` per default. To choose a different
|
|
||||||
installation directory add `-DCMAKE_INSTALL_PREFIX=<install root>` to the
|
|
||||||
cmake command. Make sure that the `bin` directory is available in your path
|
|
||||||
in that case, e.g.
|
|
||||||
|
|
||||||
```
|
|
||||||
export PATH=<install root>/bin:$PATH
|
|
||||||
```
|
|
||||||
|
|
||||||
Now continue with [importing the database](Import.md).
|
Now continue with [importing the database](Import.md).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Database Migrations
|
# Database Migrations
|
||||||
|
|
||||||
Nominatim offers automatic migrations since version 3.7. Please follow
|
Nominatim offers automatic migrations for versions 4.3+. Please follow
|
||||||
the following steps:
|
the following steps:
|
||||||
|
|
||||||
* Stop any updates that are potentially running
|
* Stop any updates that are potentially running
|
||||||
@@ -9,16 +9,73 @@ the following steps:
|
|||||||
* Update the frontend: `pip install -U nominatim-api`
|
* Update the frontend: `pip install -U nominatim-api`
|
||||||
* (optionally) Restart updates
|
* (optionally) Restart updates
|
||||||
|
|
||||||
If you are still using CMake for the installation of Nominatim, then you
|
|
||||||
need to update the software in one step before migrating the database.
|
|
||||||
It is not recommended to do this while the machine is serving requests.
|
|
||||||
|
|
||||||
Below you find additional migrations and hints about other structural and
|
Below you find additional migrations and hints about other structural and
|
||||||
breaking changes. **Please read them before running the migration.**
|
breaking changes. **Please read them before running the migration.**
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
If you are migrating from a version <3.6, then you still have to follow
|
If you are migrating from a version <4.3, you need to install 4.3
|
||||||
the manual migration steps up to 3.6.
|
and migrate to 4.3 first. Then you can migrate to the current
|
||||||
|
version. It is strongly recommended to do a reimport instead.
|
||||||
|
|
||||||
|
## 5.1.0 -> 5.2.0
|
||||||
|
|
||||||
|
### Lua import style: required extratags removed
|
||||||
|
|
||||||
|
Tags that are required by Nominatim as extratags are now always included
|
||||||
|
independent of what is defined in the style. The line
|
||||||
|
|
||||||
|
flex.add_for_extratags('required')
|
||||||
|
|
||||||
|
is no longer required in custom styles and will throw an error. Simply
|
||||||
|
remove the line from your style.
|
||||||
|
|
||||||
|
## 4.5.0 -> 5.0.0
|
||||||
|
|
||||||
|
### PHP frontend removed
|
||||||
|
|
||||||
|
The PHP frontend has been completely removed. Please switch to the Python
|
||||||
|
frontend.
|
||||||
|
|
||||||
|
Without the PHP code, the `nominatim refresh --website` command is no longer
|
||||||
|
needed. It currently omits a warning and does otherwise nothing. It will be
|
||||||
|
removed in later versions of Nominatim. So make sure you remove it from your
|
||||||
|
scripts.
|
||||||
|
|
||||||
|
### CMake building removed
|
||||||
|
|
||||||
|
Nominatim can now only be installed via pip. Please follow the installation
|
||||||
|
instructions for the current version to change to pip.
|
||||||
|
|
||||||
|
### osm2pgsql no longer vendored in
|
||||||
|
|
||||||
|
Nominatim no longer ships its own version of osm2pgsql. Please install a
|
||||||
|
stock version of osm2pgsql from your distribution. See the
|
||||||
|
[installation instruction for osm2pgsql](https://osm2pgsql.org/doc/install.html)
|
||||||
|
for details. A minimum version of 1.8 is required. The current stable versions
|
||||||
|
of Ubuntu and Debian already ship with an appropriate versions. For older
|
||||||
|
installation, you may have to compile a newer osm2pgsql yourself.
|
||||||
|
|
||||||
|
### Legacy tokenizer removed
|
||||||
|
|
||||||
|
The `legacy` tokenizer is no longer enabled. This tokenizer has been superseded
|
||||||
|
by the `ICU` tokenizer a long time ago. In the unlikely case that your database
|
||||||
|
still uses the `legacy` tokenizer, you must reimport your database.
|
||||||
|
|
||||||
|
### osm2pgsql style overhauled
|
||||||
|
|
||||||
|
There are some fundamental changes to how customized osm2pgsql styles should
|
||||||
|
be written. The changes are mostly backwards compatible, i.e. custom styles
|
||||||
|
should still work with the new implementation. The only exception is a
|
||||||
|
customization of the `process_tags()` function. This function is no longer
|
||||||
|
considered public and neither are the helper functions used in it.
|
||||||
|
They currently still work but will be removed at some point. If you have
|
||||||
|
been making changes to `process_tags`, please review your style and try
|
||||||
|
to switch to the new convenience functions.
|
||||||
|
|
||||||
|
For more information on the changes, see the
|
||||||
|
[pull request](https://github.com/osm-search/Nominatim/pull/3615)
|
||||||
|
and read the new
|
||||||
|
[customization documentation](https://nominatim.org/release-docs/latest/customize/Import-Styles/).
|
||||||
|
|
||||||
## 4.4.0 -> 4.5.0
|
## 4.4.0 -> 4.5.0
|
||||||
|
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ The website is now available at `http://localhost:8765`.
|
|||||||
## Forwarding searches to nominatim-ui
|
## Forwarding searches to nominatim-ui
|
||||||
|
|
||||||
Nominatim used to provide the search interface directly by itself when
|
Nominatim used to provide the search interface directly by itself when
|
||||||
`format=html` was requested. For all endpoints except for `/reverse` and
|
`format=html` was requested. For the `/search` endpoint this even used
|
||||||
`/lookup` this even used to be the default.
|
to be the default.
|
||||||
|
|
||||||
The following section describes how to set up Apache or nginx, so that your
|
The following section describes how to set up Apache or nginx, so that your
|
||||||
users are forwarded to nominatim-ui when they go to URL that formerly presented
|
users are forwarded to nominatim-ui when they go to a URL that formerly presented
|
||||||
the UI.
|
the UI.
|
||||||
|
|
||||||
### Setting up forwarding in Nginx
|
### Setting up forwarding in Nginx
|
||||||
@@ -73,41 +73,28 @@ map $args $format {
|
|||||||
|
|
||||||
# Determine from the URI and the format parameter above if forwarding is needed.
|
# Determine from the URI and the format parameter above if forwarding is needed.
|
||||||
map $uri/$format $forward_to_ui {
|
map $uri/$format $forward_to_ui {
|
||||||
default 1; # The default is to forward.
|
default 0; # no forwarding by default
|
||||||
~^/ui 0; # If the URI point to the UI already, we are done.
|
~/search.*/default 1; # Use this line only, if search should go to UI by default.
|
||||||
~/other$ 0; # An explicit non-html format parameter. No forwarding.
|
~/reverse.*/html 1; # Forward API calls that UI supports, when
|
||||||
~/reverse.*/default 0; # Reverse and lookup assume xml format when
|
~/status.*/html 1; # format=html is explicitly requested.
|
||||||
~/lookup.*/default 0; # no format parameter is given. No forwarding.
|
~/search.*/html 1;
|
||||||
|
~/details.*/html 1;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `$forward_to_ui` parameter can now be used to conditionally forward the
|
The `$forward_to_ui` parameter can now be used to conditionally forward the
|
||||||
calls:
|
calls:
|
||||||
|
|
||||||
```
|
``` nginx
|
||||||
# When no endpoint is given, default to search.
|
location / {
|
||||||
# Need to add a rewrite so that the rewrite rules below catch it correctly.
|
|
||||||
rewrite ^/$ /search;
|
|
||||||
|
|
||||||
location @php {
|
|
||||||
# fastcgi stuff..
|
|
||||||
if ($forward_to_ui) {
|
if ($forward_to_ui) {
|
||||||
rewrite ^(/[^/]*) https://yourserver.com/ui$1.html redirect;
|
rewrite ^(/[^/.]*) https://$http_host/ui$1.html redirect;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
location ~ [^/]\.php(/|$) {
|
# proxy_pass commands
|
||||||
# fastcgi stuff..
|
|
||||||
if ($forward_to_ui) {
|
|
||||||
rewrite (.*).php https://yourserver.com/ui$1.html redirect;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! warning
|
|
||||||
Be aware that the rewrite commands are slightly different for URIs with and
|
|
||||||
without the .php suffix.
|
|
||||||
|
|
||||||
Reload nginx and the UI should be available.
|
Reload nginx and the UI should be available.
|
||||||
|
|
||||||
### Setting up forwarding in Apache
|
### Setting up forwarding in Apache
|
||||||
@@ -159,18 +146,16 @@ directory like this:
|
|||||||
RewriteBase "/nominatim/"
|
RewriteBase "/nominatim/"
|
||||||
|
|
||||||
# If no endpoint is given, then use search.
|
# If no endpoint is given, then use search.
|
||||||
RewriteRule ^(/|$) "search.php"
|
RewriteRule ^(/|$) "search"
|
||||||
|
|
||||||
# If format-html is explicitly requested, forward to the UI.
|
# If format-html is explicitly requested, forward to the UI.
|
||||||
RewriteCond %{QUERY_STRING} "format=html"
|
RewriteCond %{QUERY_STRING} "format=html"
|
||||||
RewriteRule ^([^/]+)(.php)? ui/$1.html [R,END]
|
RewriteRule ^([^/.]+) ui/$1.html [R,END]
|
||||||
|
|
||||||
# If no format parameter is there then forward anything
|
# Optionally: if no format parameter is there then forward /search.
|
||||||
# but /reverse and /lookup to the UI.
|
|
||||||
RewriteCond %{QUERY_STRING} "!format="
|
RewriteCond %{QUERY_STRING} "!format="
|
||||||
RewriteCond %{REQUEST_URI} "!/lookup"
|
RewriteCond %{REQUEST_URI} "/search"
|
||||||
RewriteCond %{REQUEST_URI} "!/reverse"
|
RewriteRule ^([^/.]+) ui/$1.html [R,END]
|
||||||
RewriteRule ^([^/]+)(.php)? ui/$1.html [R,END]
|
|
||||||
</Directory>
|
</Directory>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -68,10 +68,10 @@ the update interval no new data has been published yet, it will go to sleep
|
|||||||
until the next expected update and only then attempt to download the next batch.
|
until the next expected update and only then attempt to download the next batch.
|
||||||
|
|
||||||
The one-time mode is particularly useful if you want to run updates continuously
|
The one-time mode is particularly useful if you want to run updates continuously
|
||||||
but need to schedule other work in between updates. For example, the main
|
but need to schedule other work in between updates. For example, you might
|
||||||
service at osm.org uses it, to regularly recompute postcodes -- a process that
|
want to regularly recompute postcodes -- a process that
|
||||||
must not be run while updates are in progress. Its update script
|
must not be run while updates are in progress. An update script refreshing
|
||||||
looks like this:
|
postcodes regularly might look like this:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
@@ -109,17 +109,19 @@ Unit=nominatim-updates.service
|
|||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
```
|
```
|
||||||
|
|
||||||
And then a similar service definition: `/etc/systemd/system/nominatim-updates.service`:
|
`OnUnitActiveSec` defines how often the individual update command is run.
|
||||||
|
|
||||||
|
Then add a service definition for the timer in `/etc/systemd/system/nominatim-updates.service`:
|
||||||
|
|
||||||
```
|
```
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Single updates of Nominatim
|
Description=Single updates of Nominatim
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
WorkingDirectory=/srv/nominatim
|
WorkingDirectory=/srv/nominatim-project
|
||||||
ExecStart=nominatim replication --once
|
ExecStart=/srv/nominatim-venv/bin/nominatim replication --once
|
||||||
StandardOutput=append:/var/log/nominatim-updates.log
|
StandardOutput=journald
|
||||||
StandardError=append:/var/log/nominatim-updates.error.log
|
StandardError=inherit
|
||||||
User=nominatim
|
User=nominatim
|
||||||
Group=nominatim
|
Group=nominatim
|
||||||
Type=simple
|
Type=simple
|
||||||
@@ -128,9 +130,9 @@ Type=simple
|
|||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
```
|
```
|
||||||
|
|
||||||
Replace the `WorkingDirectory` with your project directory. Also adapt user and
|
Replace the `WorkingDirectory` with your project directory. `ExecStart` points
|
||||||
group names as required. `OnUnitActiveSec` defines how often the individual
|
to the nominatim binary that was installed in your virtualenv earlier.
|
||||||
update command is run.
|
Finally, you might need to adapt user and group names as required.
|
||||||
|
|
||||||
Now activate the service and start the updates:
|
Now activate the service and start the updates:
|
||||||
|
|
||||||
@@ -140,12 +142,13 @@ sudo systemctl enable nominatim-updates.timer
|
|||||||
sudo systemctl start nominatim-updates.timer
|
sudo systemctl start nominatim-updates.timer
|
||||||
```
|
```
|
||||||
|
|
||||||
You can stop future data updates, while allowing any current, in-progress
|
You can stop future data updates while allowing any current, in-progress
|
||||||
update steps to finish, by running `sudo systemctl stop
|
update steps to finish, by running `sudo systemctl stop
|
||||||
nominatim-updates.timer` and waiting until `nominatim-updates.service` isn't
|
nominatim-updates.timer` and waiting until `nominatim-updates.service` isn't
|
||||||
running (`sudo systemctl is-active nominatim-updates.service`). Current output
|
running (`sudo systemctl is-active nominatim-updates.service`).
|
||||||
from the update can be seen like above (`systemctl status
|
|
||||||
nominatim-updates.service`).
|
To check the output from the update process, use journalctl: `journalctl -u
|
||||||
|
nominatim-updates.service`
|
||||||
|
|
||||||
|
|
||||||
#### Catch-up mode
|
#### Catch-up mode
|
||||||
@@ -155,13 +158,13 @@ all changes from the server until the database is up-to-date. The catch-up mode
|
|||||||
still respects the parameter `NOMINATIM_REPLICATION_MAX_DIFF`. It downloads and
|
still respects the parameter `NOMINATIM_REPLICATION_MAX_DIFF`. It downloads and
|
||||||
applies the changes in appropriate batches until all is done.
|
applies the changes in appropriate batches until all is done.
|
||||||
|
|
||||||
The catch-up mode is foremost useful to bring the database up to speed after the
|
The catch-up mode is foremost useful to bring the database up to date after the
|
||||||
initial import. Give that the service usually is not in production at this
|
initial import. Give that the service usually is not in production at this
|
||||||
point, you can temporarily be a bit more generous with the batch size and
|
point, you can temporarily be a bit more generous with the batch size and
|
||||||
number of threads you use for the updates by running catch-up like this:
|
number of threads you use for the updates by running catch-up like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
cd /srv/nominatim
|
cd /srv/nominatim-project
|
||||||
NOMINATIM_REPLICATION_MAX_DIFF=5000 nominatim replication --catch-up --threads 15
|
NOMINATIM_REPLICATION_MAX_DIFF=5000 nominatim replication --catch-up --threads 15
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -173,13 +176,13 @@ replication catch-up at whatever interval you desire.
|
|||||||
When running scheduled updates with catch-up, it is a good idea to choose
|
When running scheduled updates with catch-up, it is a good idea to choose
|
||||||
a replication source with an update frequency that is an order of magnitude
|
a replication source with an update frequency that is an order of magnitude
|
||||||
lower. For example, if you want to update once a day, use an hourly updated
|
lower. For example, if you want to update once a day, use an hourly updated
|
||||||
source. This makes sure that you don't miss an entire day of updates when
|
source. This ensures that you don't miss an entire day of updates when
|
||||||
the source is unexpectedly late to publish its update.
|
the source is unexpectedly late to publish its update.
|
||||||
|
|
||||||
If you want to use the source with the same update frequency (e.g. a daily
|
If you want to use the source with the same update frequency (e.g. a daily
|
||||||
updated source with daily updates), use the
|
updated source with daily updates), use the
|
||||||
continuous update mode. It ensures to re-request the newest update until it
|
once mode together with a frequently run systemd script as described above.
|
||||||
is published.
|
It ensures to re-request the newest update until they have been published.
|
||||||
|
|
||||||
|
|
||||||
#### Continuous updates
|
#### Continuous updates
|
||||||
@@ -197,36 +200,3 @@ parameters:
|
|||||||
|
|
||||||
The update application keeps running forever and retrieves and applies
|
The update application keeps running forever and retrieves and applies
|
||||||
new updates from the server as they are published.
|
new updates from the server as they are published.
|
||||||
|
|
||||||
You can run this command as a simple systemd service. Create a service
|
|
||||||
description like that in `/etc/systemd/system/nominatim-updates.service`:
|
|
||||||
|
|
||||||
```
|
|
||||||
[Unit]
|
|
||||||
Description=Continuous updates of Nominatim
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
WorkingDirectory=/srv/nominatim
|
|
||||||
ExecStart=nominatim replication
|
|
||||||
StandardOutput=append:/var/log/nominatim-updates.log
|
|
||||||
StandardError=append:/var/log/nominatim-updates.error.log
|
|
||||||
User=nominatim
|
|
||||||
Group=nominatim
|
|
||||||
Type=simple
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace the `WorkingDirectory` with your project directory. Also adapt user
|
|
||||||
and group names as required.
|
|
||||||
|
|
||||||
Now activate the service and start the updates:
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
sudo systemctl enable nominatim-updates
|
|
||||||
sudo systemctl start nominatim-updates
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -59,13 +59,6 @@ When set, then JSON output will be wrapped in a callback function with
|
|||||||
the given name. See [JSONP](https://en.wikipedia.org/wiki/JSONP) for more
|
the given name. See [JSONP](https://en.wikipedia.org/wiki/JSONP) for more
|
||||||
information.
|
information.
|
||||||
|
|
||||||
| Parameter | Value | Default |
|
|
||||||
|-----------| ----- | ------- |
|
|
||||||
| pretty | 0 or 1 | 0 |
|
|
||||||
|
|
||||||
`[PHP-only]` Add indentation to the output to make it more human-readable.
|
|
||||||
|
|
||||||
|
|
||||||
### Output details
|
### Output details
|
||||||
|
|
||||||
| Parameter | Value | Default |
|
| Parameter | Value | Default |
|
||||||
@@ -95,10 +88,8 @@ members.
|
|||||||
|-----------| ----- | ------- |
|
|-----------| ----- | ------- |
|
||||||
| hierarchy | 0 or 1 | 0 |
|
| hierarchy | 0 or 1 | 0 |
|
||||||
|
|
||||||
Include details of places lower in the address hierarchy.
|
Include details of POIs and address that depend on the place. Only POIs
|
||||||
|
that use this place to determine their address will be returned.
|
||||||
`[Python-only]` will only return properly parented places. These are address
|
|
||||||
or POI-like places that reuse the address of their parent street or place.
|
|
||||||
|
|
||||||
| Parameter | Value | Default |
|
| Parameter | Value | Default |
|
||||||
|-----------| ----- | ------- |
|
|-----------| ----- | ------- |
|
||||||
@@ -114,6 +105,13 @@ grouped by type.
|
|||||||
|
|
||||||
Include geometry of result.
|
Include geometry of result.
|
||||||
|
|
||||||
|
| Parameter | Value | Default |
|
||||||
|
|-----------| ----- | ------- |
|
||||||
|
| entrances | 0 or 1 | 0 |
|
||||||
|
|
||||||
|
When set to 1, include the tagged entrances in the result.
|
||||||
|
|
||||||
|
|
||||||
### Language of results
|
### Language of results
|
||||||
|
|
||||||
| Parameter | Value | Default |
|
| Parameter | Value | Default |
|
||||||
@@ -129,7 +127,7 @@ as the ["Accept-Language" HTTP header](https://developer.mozilla.org/en-US/docs/
|
|||||||
|
|
||||||
##### JSON
|
##### JSON
|
||||||
|
|
||||||
[https://nominatim.openstreetmap.org/details.php?osmtype=W&osmid=38210407&format=json](https://nominatim.openstreetmap.org/details.php?osmtype=W&osmid=38210407&format=json)
|
[https://nominatim.openstreetmap.org/details?osmtype=W&osmid=38210407&format=json](https://nominatim.openstreetmap.org/details?osmtype=W&osmid=38210407&format=json)
|
||||||
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Only has an effect for JSON output formats.
|
|||||||
|
|
||||||
| Parameter | Value | Default |
|
| Parameter | Value | Default |
|
||||||
|-----------| ----- | ------- |
|
|-----------| ----- | ------- |
|
||||||
| addressdetails | 0 or 1 | 0 |
|
| addressdetails | 0 or 1 | 1 |
|
||||||
|
|
||||||
When set to 1, include a breakdown of the address into elements.
|
When set to 1, include a breakdown of the address into elements.
|
||||||
The exact content of the address breakdown depends on the output format.
|
The exact content of the address breakdown depends on the output format.
|
||||||
@@ -77,6 +77,12 @@ that is available in the database, e.g. wikipedia link, opening hours.
|
|||||||
When set to 1, include a full list of names for the result. These may include
|
When set to 1, include a full list of names for the result. These may include
|
||||||
language variants, older names, references and brand.
|
language variants, older names, references and brand.
|
||||||
|
|
||||||
|
| Parameter | Value | Default |
|
||||||
|
|-----------| ----- | ------- |
|
||||||
|
| entrances | 0 or 1 | 0 |
|
||||||
|
|
||||||
|
When set to 1, include the tagged entrances in the result.
|
||||||
|
|
||||||
|
|
||||||
### Language of results
|
### Language of results
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ The possible fields are:
|
|||||||
* `namedetails` - dictionary with full list of available names including ref etc.
|
* `namedetails` - dictionary with full list of available names including ref etc.
|
||||||
* `geojson`, `svg`, `geotext`, `geokml` - full geometry
|
* `geojson`, `svg`, `geotext`, `geokml` - full geometry
|
||||||
(only with the appropriate `polygon_*` parameter)
|
(only with the appropriate `polygon_*` parameter)
|
||||||
|
* `entrances` - array of objects representing tagged entrances for the object, or
|
||||||
|
null if none are found (only with `entrances=1`)
|
||||||
|
|
||||||
## JSONv2
|
## JSONv2
|
||||||
|
|
||||||
@@ -87,6 +89,8 @@ The properties object has the following fields:
|
|||||||
* `extratags` - dictionary with additional useful tags like `website` or `maxspeed`
|
* `extratags` - dictionary with additional useful tags like `website` or `maxspeed`
|
||||||
(only with `extratags=1`)
|
(only with `extratags=1`)
|
||||||
* `namedetails` - dictionary with full list of available names including ref etc.
|
* `namedetails` - dictionary with full list of available names including ref etc.
|
||||||
|
* `entrances` - array of objects representing tagged entrances for the object, or
|
||||||
|
null if none are found (only with `entrances=1`)
|
||||||
|
|
||||||
Use `polygon_geojson` to output the full geometry of the object instead
|
Use `polygon_geojson` to output the full geometry of the object instead
|
||||||
of the centroid.
|
of the centroid.
|
||||||
@@ -106,8 +110,13 @@ The following feature attributes are implemented:
|
|||||||
* `name` - localised name of the place
|
* `name` - localised name of the place
|
||||||
* `housenumber`, `street`, `locality`, `district`, `postcode`, `city`,
|
* `housenumber`, `street`, `locality`, `district`, `postcode`, `city`,
|
||||||
`county`, `state`, `country` -
|
`county`, `state`, `country` -
|
||||||
provided when it can be determined from the address
|
provided when it can be determined from the address (only with `addressdetails=1`)
|
||||||
* `admin` - list of localised names of administrative boundaries (only with `addressdetails=1`)
|
* `admin` - list of localised names of administrative boundaries (only with `addressdetails=1`)
|
||||||
|
* `extra` - dictionary with additional useful tags like `website` or `maxspeed`
|
||||||
|
(only with `extratags=1`)
|
||||||
|
* `entrances` - array of objects representing tagged entrances for the object, or
|
||||||
|
null if none are found (only with `entrances=1`)
|
||||||
|
|
||||||
|
|
||||||
Use `polygon_geojson` to output the full geometry of the object instead
|
Use `polygon_geojson` to output the full geometry of the object instead
|
||||||
of the centroid.
|
of the centroid.
|
||||||
@@ -159,8 +168,8 @@ The place information can be found in the `result` element. The attributes of th
|
|||||||
The full address of the result can be found in the content of the
|
The full address of the result can be found in the content of the
|
||||||
`result` element as a comma-separated list.
|
`result` element as a comma-separated list.
|
||||||
|
|
||||||
Additional information requested with `addressdetails=1`, `extratags=1` and
|
Additional information requested with `addressdetails=1`, `extratags=1`,
|
||||||
`namedetails=1` can be found in extra elements.
|
`namedetails=1`, and `entrances=1` can be found in extra elements.
|
||||||
|
|
||||||
### Search and Lookup
|
### Search and Lookup
|
||||||
|
|
||||||
@@ -168,7 +177,7 @@ Additional information requested with `addressdetails=1`, `extratags=1` and
|
|||||||
<searchresults timestamp="Sat, 11 Aug 18 11:55:35 +0000"
|
<searchresults timestamp="Sat, 11 Aug 18 11:55:35 +0000"
|
||||||
attribution="Data © OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright"
|
attribution="Data © OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright"
|
||||||
querystring="london" polygon="false" exclude_place_ids="100149"
|
querystring="london" polygon="false" exclude_place_ids="100149"
|
||||||
more_url="https://nominatim.openstreetmap.org/search.php?q=london&addressdetails=1&extratags=1&exclude_place_ids=100149&format=xml&accept-language=en-US%2Cen%3Bq%3D0.7%2Cde%3Bq%3D0.3">
|
more_url="https://nominatim.openstreetmap.org/search?q=london&addressdetails=1&extratags=1&exclude_place_ids=100149&format=xml&accept-language=en-US%2Cen%3Bq%3D0.7%2Cde%3Bq%3D0.3">
|
||||||
<place place_id="100149" osm_type="node" osm_id="107775" place_rank="15" address_rank="15"
|
<place place_id="100149" osm_type="node" osm_id="107775" place_rank="15" address_rank="15"
|
||||||
boundingbox="51.3473219,51.6673219,-0.2876474,0.0323526" lat="51.5073219" lon="-0.1276474"
|
boundingbox="51.3473219,51.6673219,-0.2876474,0.0323526" lat="51.5073219" lon="-0.1276474"
|
||||||
display_name="London, Greater London, England, SW1A 2DU, United Kingdom"
|
display_name="London, Greater London, England, SW1A 2DU, United Kingdom"
|
||||||
@@ -221,9 +230,9 @@ be more than one. The attributes of that element contain:
|
|||||||
When `addressdetails=1` is requested, the localised address parts appear
|
When `addressdetails=1` is requested, the localised address parts appear
|
||||||
as subelements with the type of the address part.
|
as subelements with the type of the address part.
|
||||||
|
|
||||||
Additional information requested with `extratags=1` and `namedetails=1` can
|
Additional information requested with `extratags=1`, `namedetails=1`, and
|
||||||
be found in extra elements as sub-element of `extratags` and `namedetails`
|
`entrances=1` can be found in extra elements as sub-element of `extratags`,
|
||||||
respectively.
|
`namedetails`, and `entrances` respectively.
|
||||||
|
|
||||||
|
|
||||||
## Notes on field values
|
## Notes on field values
|
||||||
@@ -300,3 +309,78 @@ with a designation label. Per default the following labels may appear:
|
|||||||
|
|
||||||
They roughly correspond to the classification of the OpenStreetMap data
|
They roughly correspond to the classification of the OpenStreetMap data
|
||||||
according to either the `place` tag or the main key of the object.
|
according to either the `place` tag or the main key of the object.
|
||||||
|
|
||||||
|
### entrances
|
||||||
|
|
||||||
|
Entrance details in the xml and json formats return the latitude and longitude
|
||||||
|
of the entrance, the osm node ID, the [type of
|
||||||
|
entrance](https://wiki.openstreetmap.org/wiki/Key:entrance), and any extra tags
|
||||||
|
associated with the entrance node.
|
||||||
|
|
||||||
|
* osm_id
|
||||||
|
* type
|
||||||
|
* lat
|
||||||
|
* lon
|
||||||
|
* extratags
|
||||||
|
|
||||||
|
They roughly correspond to the classification of the OpenStreetMap data
|
||||||
|
according to either the `place` tag or the main key of the object.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
##### JSON
|
||||||
|
|
||||||
|
[https://nominatim.openstreetmap.org/details?osmtype=W&osmid=32619803&entrances=1&format=json](https://nominatim.openstreetmap.org/details?osmtype=W&osmid=32619803&entrances=1&format=json)
|
||||||
|
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"place_id": 124325848,
|
||||||
|
"parent_place_id": 123936289,
|
||||||
|
"osm_type": "W",
|
||||||
|
"osm_id": 32619803,
|
||||||
|
"category": "shop",
|
||||||
|
"type": "supermarket",
|
||||||
|
"admin_level": 15,
|
||||||
|
"localname": "PENNY",
|
||||||
|
...
|
||||||
|
"entrances": [
|
||||||
|
{
|
||||||
|
"osm_id": 1733488238,
|
||||||
|
"type": "yes",
|
||||||
|
"lat": "51.0466704",
|
||||||
|
"lon": "12.8077106",
|
||||||
|
"extratags": {
|
||||||
|
"foot": "yes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"osm_id": 1733488256,
|
||||||
|
"type": "main",
|
||||||
|
"lat": "51.0467197",
|
||||||
|
"lon": "12.8078448",
|
||||||
|
"extratags": {
|
||||||
|
"foot": "yes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"osm_id": 1733498087,
|
||||||
|
"type": "exit",
|
||||||
|
"lat": "51.0467081",
|
||||||
|
"lon": "12.8078131",
|
||||||
|
"extratags": {
|
||||||
|
"foot": "yes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"osm_id": 7914950851,
|
||||||
|
"type": "service",
|
||||||
|
"lat": "51.0468487",
|
||||||
|
"lon": "12.8075876",
|
||||||
|
"extratags": {
|
||||||
|
"access": "delivery"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
!!! Attention
|
|
||||||
The current version of Nominatim implements two different search frontends:
|
|
||||||
the old PHP frontend and the new Python frontend. They have a very similar
|
|
||||||
API but differ in some implementation details. These are marked in the
|
|
||||||
documentation as `[Python-only]` or `[PHP-only]`.
|
|
||||||
|
|
||||||
`https://nominatim.openstreetmap.org` implements the **Python frontend**.
|
|
||||||
So users should refer to the **`[Python-only]`** comments.
|
|
||||||
|
|
||||||
This section describes the API V1 of the Nominatim web service. The
|
This section describes the API V1 of the Nominatim web service. The
|
||||||
service offers the following endpoints:
|
service offers the following endpoints:
|
||||||
|
|
||||||
|
|||||||
@@ -32,10 +32,9 @@ projection. The API returns exactly one result or an error when the coordinate
|
|||||||
is in an area with no OSM data coverage.
|
is in an area with no OSM data coverage.
|
||||||
|
|
||||||
|
|
||||||
!!! danger "Deprecation warning"
|
!!! tip
|
||||||
The reverse API used to allow address lookup for a single OSM object by
|
The reverse API allows a lookup of object by coordinate. If you want
|
||||||
its OSM id for `[PHP-only]`. The use is considered deprecated.
|
to look up an object by ID, use the [Address Lookup API](Lookup.md) instead.
|
||||||
Use the [Address Lookup API](Lookup.md) instead.
|
|
||||||
|
|
||||||
!!! danger "Deprecation warning"
|
!!! danger "Deprecation warning"
|
||||||
The API can also be used with the URL
|
The API can also be used with the URL
|
||||||
@@ -99,6 +98,12 @@ that is available in the database, e.g. wikipedia link, opening hours.
|
|||||||
When set to 1, include a full list of names for the result. These may include
|
When set to 1, include a full list of names for the result. These may include
|
||||||
language variants, older names, references and brand.
|
language variants, older names, references and brand.
|
||||||
|
|
||||||
|
| Parameter | Value | Default |
|
||||||
|
|-----------| ----- | ------- |
|
||||||
|
| entrances | 0 or 1 | 0 |
|
||||||
|
|
||||||
|
When set to 1, include the tagged entrances in the result.
|
||||||
|
|
||||||
|
|
||||||
### Language of results
|
### Language of results
|
||||||
|
|
||||||
@@ -147,9 +152,7 @@ In terms of address details the zoom levels are as follows:
|
|||||||
|
|
||||||
| Parameter | Value | Default |
|
| Parameter | Value | Default |
|
||||||
|-----------| ----- | ------- |
|
|-----------| ----- | ------- |
|
||||||
| layer | comma-separated list of: `address`, `poi`, `railway`, `natural`, `manmade` | _unset_ (no restriction) |
|
| layer | comma-separated list of: `address`, `poi`, `railway`, `natural`, `manmade` | `address,poi` |
|
||||||
|
|
||||||
**`[Python-only]`**
|
|
||||||
|
|
||||||
The layer filter allows to select places by themes.
|
The layer filter allows to select places by themes.
|
||||||
|
|
||||||
@@ -215,7 +218,7 @@ This overrides the specified machine readable format.
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
* [https://nominatim.openstreetmap.org/reverse?format=xml&lat=52.5487429714954&lon=-1.81602098644987&zoom=18&addressdetails=1](https://nominatim.openstreetmap.org/reverse?format=xml&lat=52.5487429714954&lon=-1.81602098644987&zoom=18&addressdetails=1)
|
* [https://nominatim.openstreetmap.org/reverse?format=xml&lat=52.5487429714954&lon=-1.81602098644987&zoom=18&addressdetails=1&layer=address](https://nominatim.openstreetmap.org/reverse?format=xml&lat=52.5487429714954&lon=-1.81602098644987&zoom=18&addressdetails=1&layer=address)
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<reversegeocode timestamp="Fri, 06 Nov 09 16:33:54 +0000" querystring="...">
|
<reversegeocode timestamp="Fri, 06 Nov 09 16:33:54 +0000" querystring="...">
|
||||||
@@ -238,7 +241,7 @@ This overrides the specified machine readable format.
|
|||||||
|
|
||||||
##### Example with `format=jsonv2`
|
##### Example with `format=jsonv2`
|
||||||
|
|
||||||
* [https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=-34.44076&lon=-58.70521](https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=-34.44076&lon=-58.70521)
|
* [https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=-34.44076&lon=-58.70521&layer=address](https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=-34.44076&lon=-58.70521&layer=address)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -270,7 +273,7 @@ This overrides the specified machine readable format.
|
|||||||
|
|
||||||
##### Example with `format=geojson`
|
##### Example with `format=geojson`
|
||||||
|
|
||||||
* [https://nominatim.openstreetmap.org/reverse?format=geojson&lat=44.50155&lon=11.33989](https://nominatim.openstreetmap.org/reverse?format=geojson&lat=44.50155&lon=11.33989)
|
* [https://nominatim.openstreetmap.org/reverse?format=geojson&lat=44.50155&lon=11.33989&layer=address](https://nominatim.openstreetmap.org/reverse?format=geojson&lat=44.50155&lon=11.33989&layer=address)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -322,7 +325,7 @@ This overrides the specified machine readable format.
|
|||||||
|
|
||||||
##### Example with `format=geocodejson`
|
##### Example with `format=geocodejson`
|
||||||
|
|
||||||
[https://nominatim.openstreetmap.org/reverse?format=geocodejson&lat=60.2299&lon=11.1663](https://nominatim.openstreetmap.org/reverse?format=geocodejson&lat=60.2299&lon=11.1663)
|
[https://nominatim.openstreetmap.org/reverse?format=geocodejson&lat=60.2299&lon=11.1663&layer=address](https://nominatim.openstreetmap.org/reverse?format=geocodejson&lat=60.2299&lon=11.1663&layer=address)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -136,6 +136,12 @@ that is available in the database, e.g. wikipedia link, opening hours.
|
|||||||
When set to 1, include a full list of names for the result. These may include
|
When set to 1, include a full list of names for the result. These may include
|
||||||
language variants, older names, references and brand.
|
language variants, older names, references and brand.
|
||||||
|
|
||||||
|
| Parameter | Value | Default |
|
||||||
|
|-----------| ----- | ------- |
|
||||||
|
| entrances | 0 or 1 | 0 |
|
||||||
|
|
||||||
|
When set to 1, include the tagged entrances in the result.
|
||||||
|
|
||||||
|
|
||||||
### Language of results
|
### Language of results
|
||||||
|
|
||||||
@@ -187,8 +193,6 @@ also excluded when the filter is set.
|
|||||||
|-----------| ----- | ------- |
|
|-----------| ----- | ------- |
|
||||||
| layer | comma-separated list of: `address`, `poi`, `railway`, `natural`, `manmade` | _unset_ (no restriction) |
|
| layer | comma-separated list of: `address`, `poi`, `railway`, `natural`, `manmade` | _unset_ (no restriction) |
|
||||||
|
|
||||||
**`[Python-only]`**
|
|
||||||
|
|
||||||
The layer filter allows to select places by themes.
|
The layer filter allows to select places by themes.
|
||||||
|
|
||||||
The `address` layer contains all places that make up an address:
|
The `address` layer contains all places that make up an address:
|
||||||
@@ -214,7 +218,7 @@ other layers.
|
|||||||
The featureType allows to have a more fine-grained selection for places
|
The featureType allows to have a more fine-grained selection for places
|
||||||
from the address layer. Results can be restricted to places that make up
|
from the address layer. Results can be restricted to places that make up
|
||||||
the 'state', 'country' or 'city' part of an address. A featureType of
|
the 'state', 'country' or 'city' part of an address. A featureType of
|
||||||
settlement selects any human inhabited feature from 'state' down to
|
`settlement` selects any human inhabited feature from 'state' down to
|
||||||
'neighbourhood'.
|
'neighbourhood'.
|
||||||
|
|
||||||
When featureType is set, then results are automatically restricted
|
When featureType is set, then results are automatically restricted
|
||||||
|
|||||||
@@ -1,95 +1,136 @@
|
|||||||
## Configuring the Import
|
# Configuring the Import of OSM data
|
||||||
|
|
||||||
In the very first step of a Nominatim import, OSM data is loaded into the
|
In the very first step of a Nominatim import, OSM data is loaded into the
|
||||||
database. Nominatim uses [osm2pgsql](https://osm2pgsql.org) for this task.
|
database. Nominatim uses [osm2pgsql](https://osm2pgsql.org) for this task.
|
||||||
It comes with a [flex style](https://osm2pgsql.org/doc/manual.html#the-flex-output)
|
It comes with a [flex style](https://osm2pgsql.org/doc/manual.html#the-flex-output)
|
||||||
specifically tailored to filter and convert OSM data into Nominatim's
|
specifically tailored to filter and convert OSM data into Nominatim's
|
||||||
internal data representation.
|
internal data representation. Nominatim ships with a few preset
|
||||||
|
configurations for this import, each results in a geocoding database of
|
||||||
There are a number of default configurations for the flex style which
|
different detail. The
|
||||||
result in geocoding databases of different detail. The
|
|
||||||
[Import section](../admin/Import.md#filtering-imported-data) explains
|
[Import section](../admin/Import.md#filtering-imported-data) explains
|
||||||
these default configurations in detail.
|
these default configurations in detail.
|
||||||
|
|
||||||
You can also create your own custom style. Put the style file into your
|
If you want to have more control over which OSM data is added to the database,
|
||||||
project directory and then set `NOMINATIM_IMPORT_STYLE` to the name of the file.
|
you can also create your own custom style. Create a new lua style file, put it
|
||||||
It is always recommended to start with one of the standard styles and customize
|
into your project directory and then set `NOMINATIM_IMPORT_STYLE` to the name
|
||||||
those. You find the standard styles under the name `import-<stylename>.lua`
|
of the file. Custom style files can be used to modify the existing preset
|
||||||
in the standard Nominatim configuration path (usually `/etc/nominatim` or
|
configurations or to implement your own configuration from scratch.
|
||||||
`/usr/local/etc/nominatim`).
|
|
||||||
|
|
||||||
The remainder of the page describes how the flex style works and how to
|
The remainder of the page describes how the flex style works and how to
|
||||||
customize it.
|
customize it.
|
||||||
|
|
||||||
### The `flex-base.lua` module
|
## The `flex-base` lua module
|
||||||
|
|
||||||
The core of Nominatim's flex import configuration is the `flex-base` module.
|
The core of Nominatim's flex import configuration is the `flex-base` module.
|
||||||
It defines the table layout used by Nominatim and provides standard
|
It defines the table layout used by Nominatim and provides standard
|
||||||
implementations for the import callbacks that make it easy to customize
|
implementations for the import callbacks that help with customizing
|
||||||
how OSM tags are used by Nominatim.
|
how OSM tags are used by Nominatim.
|
||||||
|
|
||||||
Every custom style should include this module to make sure that the correct
|
Every custom style must include this module to make sure that the correct
|
||||||
tables are created. Thus start your custom style as follows:
|
tables are created. Thus start your custom style as follows:
|
||||||
|
|
||||||
``` lua
|
``` lua
|
||||||
local flex = require('flex-base')
|
local flex = require('flex-base')
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The following sections explain how the module can be customized.
|
### Using preset configurations
|
||||||
|
|
||||||
|
If you want to start with one of the existing presets, then you can import
|
||||||
|
its settings using the `load_topic()` function:
|
||||||
|
|
||||||
### Changing the recognized tags
|
``` lua
|
||||||
|
local flex = require('flex-base')
|
||||||
|
|
||||||
If you just want to change which OSM tags are recognized during import,
|
flex.load_topic('streets')
|
||||||
then there are a number of convenience functions to set the tag lists used
|
```
|
||||||
during the processing.
|
|
||||||
|
|
||||||
!!! warning
|
The `load_topic` function takes an optional second configuration
|
||||||
There are no built-in defaults for the tag lists, so all the functions
|
parameter. The available options are explained in the
|
||||||
need to be called from your style script to fully process the data.
|
[themepark section](#using-osm2pgsql-themepark).
|
||||||
Make sure you start from one of the default style and only modify
|
|
||||||
the data you are interested in. You can also derive your style from an
|
|
||||||
existing style by importing the appropriate module, e.g.
|
|
||||||
`local flex = require('import-street')`.
|
|
||||||
|
|
||||||
Many of the following functions take _key match lists_. These lists can
|
Available topics are: `admin`, `street`, `address`, `full`. These topic
|
||||||
|
correspond to the [import styles](../admin/Import.md#filtering-imported-data)
|
||||||
|
you can choose during import. To start with the 'extratags' style, use the
|
||||||
|
`full` topic with the appropriate config parameter:
|
||||||
|
|
||||||
|
``` lua
|
||||||
|
flex.load_topic('full', {with_extratags = true})
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
You can also directly import the preset style files, e.g.
|
||||||
|
`local flex = require('import-street')`. It is not possible to
|
||||||
|
set extra configuration this way.
|
||||||
|
|
||||||
|
### How processing works
|
||||||
|
|
||||||
|
When Nominatim processes an OSM object, it looks for four kinds of tags:
|
||||||
|
The _main tags_ classify what kind of place the OSM object represents. One
|
||||||
|
OSM object can have more than one main tag. In such case one database entry
|
||||||
|
is created for each main tag. _Name tags_ represent searchable names of the
|
||||||
|
place. _Address tags_ are used to compute the address information of the place.
|
||||||
|
Address tags are used for searching and for creating a display name of the place.
|
||||||
|
_Extra tags_ are any tags that are not directly related to search but
|
||||||
|
contain interesting additional information. These are just saved in the database
|
||||||
|
and may be returned with the result [on request](../api/Search.md#output-details).
|
||||||
|
|
||||||
|
!!! danger
|
||||||
|
Some tags in the extratags category are used by Nominatim to better
|
||||||
|
classify the place. These tags will always be added, independent of
|
||||||
|
any settings in the style.
|
||||||
|
|
||||||
|
Configuring the style means deciding which key and/or key/value is used
|
||||||
|
in which category.
|
||||||
|
|
||||||
|
## Changing the recognized tags
|
||||||
|
|
||||||
|
The flex style offers a number of functions to set the classification of
|
||||||
|
each OSM tag. Most of these functions can also take a preset string instead
|
||||||
|
of a tag description. These presets describe common configurations that
|
||||||
|
are also used in the definition of the predefined styles. This section
|
||||||
|
lists the configuration functions and the accepted presets.
|
||||||
|
|
||||||
|
#### Key match lists
|
||||||
|
|
||||||
|
Some of the following functions take _key match lists_. These lists can
|
||||||
contain three kinds of strings to match against tag keys:
|
contain three kinds of strings to match against tag keys:
|
||||||
A string that ends in an asterisk `*` is a prefix match and accordingly matches
|
A string that ends in an asterisk `*` is a prefix match and accordingly matches
|
||||||
against any key that starts with the given string (minus the `*`).
|
against any key that starts with the given string (minus the `*`).
|
||||||
A suffix match can be defined similarly with a string that starts with a `*`.
|
A suffix match can be defined similarly with a string that starts with a `*`.
|
||||||
Any other string is matched exactly against tag keys.
|
Any other string is matched exactly against tag keys.
|
||||||
|
|
||||||
|
### Main tags
|
||||||
|
|
||||||
#### `set_main_tags()` - principal tags
|
`set/modify_main_tags()` allow to define which tags are used as main tags. It
|
||||||
|
takes a lua table parameter which defines for keys and key/value
|
||||||
|
combinations, how they are classified.
|
||||||
|
|
||||||
If a principal or main tag is found on an OSM object, then the object
|
The following classifications are recognized:
|
||||||
is included in Nominatim's search index. A single object may also have
|
|
||||||
multiple main tags. In that case, the object will be included multiple
|
|
||||||
times in the index, once for each main tag.
|
|
||||||
|
|
||||||
The flex script distinguishes between four types of main tags:
|
| classification | meaning |
|
||||||
|
| :-------------- | :------ |
|
||||||
|
| always | Unconditionally use this tag as a main tag. |
|
||||||
|
| named | Consider as main tag, when the object has a primary name (see [names](#name-tags) below) |
|
||||||
|
| named_with_key | Consider as main tag, when the object has a primary name with a domain prefix. For example, if the main tag is `bridge=yes`, then it will only be added as an extra entry, if there is a tag `bridge:name[:XXX]` for the same object. If this property is set, all names that are not domain-specific are ignored. |
|
||||||
|
| fallback | Consider as main tag only when no other main tag was found. Fallback always implies `named`, i.e. fallbacks are only tried for objects with primary names. |
|
||||||
|
| postcode_area | Tag indicates a postcode area. Copy area into the table of postcodes but only when the object is a relation and has a postcode tagged. |
|
||||||
|
| delete | Completely ignore the tag in any further processing |
|
||||||
|
| extra | Move the tag to extratags and then ignore it for further processing |
|
||||||
|
| `<function>`| Advanced handling, see [below](#advanced-main-tag-handling) |
|
||||||
|
|
||||||
* __always__: a main tag that is used unconditionally
|
Each key in the table parameter defines an OSM tag key. The value may
|
||||||
* __named__: consider this main tag only, if the object has a proper name
|
be directly a classification as described above. Then the tag will
|
||||||
(a reference is not enough, see below).
|
be considered a main tag for any possible value that is not further defined.
|
||||||
* __named_with_key__: consider this main tag only, when the object has
|
To further restrict which values are acceptable, give a table with the
|
||||||
a proper name with a domain prefix. For example, if the main tag is
|
permitted values and their kind of main tag. If the table contains a simple
|
||||||
`bridge=yes`, then it will only be added as an extra row, if there is
|
value without key, then this is used as default for values that are not listed.
|
||||||
a tag `bridge:name[:XXX]` for the same object. If this property is set,
|
|
||||||
all other names that are not domain-specific are ignored.
|
|
||||||
* __fallback__: use this main tag only, if there is no other main tag.
|
|
||||||
Fallback always implied `named`, i.e. fallbacks are only tried for
|
|
||||||
named objects.
|
|
||||||
|
|
||||||
The `set_main_tags()` function takes exactly one table parameter which
|
`set_main_tags()` will completely replace the current main tag configuration
|
||||||
defines the keys and key/value combinations to include and the kind of
|
with the new configuration. `modify_main_tags()` will merge the new
|
||||||
main tag. Each lua table key defines an OSM tag key. The value may
|
configuration with the existing one. Merging is done at value level.
|
||||||
be a string defining the kind of main key as described above. Then the tag will
|
For example, when the current setting is `highway = {'always', primary = 'named'}`,
|
||||||
be considered a main tag for any possible value. To further restrict
|
then `set_main_tags{highway = 'delete'}` will result in a rule
|
||||||
which values are acceptable, give a table with the permitted values
|
`highway = {'delete', primary = 'named'}`.
|
||||||
and their kind of main tag. If the table contains a simple value without
|
|
||||||
key, then this is used as default for values that are not listed.
|
|
||||||
|
|
||||||
!!! example
|
!!! example
|
||||||
``` lua
|
``` lua
|
||||||
@@ -97,28 +138,145 @@ key, then this is used as default for values that are not listed.
|
|||||||
|
|
||||||
flex.set_main_tags{
|
flex.set_main_tags{
|
||||||
boundary = {administrative = 'named'},
|
boundary = {administrative = 'named'},
|
||||||
highway = {'always', street_lamp = 'named'},
|
highway = {'always', street_lamp = 'named', no = 'delete'},
|
||||||
landuse = 'fallback'
|
landuse = 'fallback'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example an object with a `boundary` tag will only be included
|
In this example an object with a `boundary` tag will only be included
|
||||||
when it has a value of `administrative`. Objects with `highway` tags are
|
when it has a value of `administrative`. Objects with `highway` tags are
|
||||||
always included. However when the value is `street_lamp` then the object
|
always included with two exceptions: the troll tag `highway=no` is
|
||||||
must have a name, too. With any other value, the object is included
|
deleted on the spot. And when the value is `street_lamp` then the object
|
||||||
independently of the name. Finally, if a `landuse` tag is present then
|
must also have a name, to be included. Finally, if a `landuse` tag is
|
||||||
it will be used independely of the concrete value if neither boundary
|
present then it will be used independently of the concrete value when
|
||||||
nor highway tags were found and the object is named.
|
neither boundary nor highway tags were found and the object is named.
|
||||||
|
|
||||||
|
##### Presets
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
| :----- | :---------- |
|
||||||
|
| admin | Basic tag set collecting places and administrative boundaries. This set is needed also to ensure proper address computation and should therefore always be present. You can disable selected place types like `place=locality` after adding this set, if they are not relevant for your use case. |
|
||||||
|
| all_boundaries | Extends the set of recognized boundaries and places to all available ones. |
|
||||||
|
| natural | Tags for natural features like rivers and mountain peaks. |
|
||||||
|
| street/default | Tags for streets. Major streets are always included, minor ones only when they have a name. |
|
||||||
|
| street/car | Tags for all streets that can be used by a motor vehicle. |
|
||||||
|
| street/all | Includes all highway features named and unnamed. |
|
||||||
|
| poi/delete | Adds most POI features with and without name. Some frequent but very domain-specific values are excluded by deleting them. |
|
||||||
|
| poi/extra | Like 'poi/delete' but excluded values are moved to extratags. |
|
||||||
|
|
||||||
|
|
||||||
#### `set_prefilters()` - ignoring tags
|
##### Advanced main tag handling
|
||||||
|
|
||||||
Pre-filtering of tags allows to ignore them for any further processing.
|
The groups described above are in fact only a preset for a filtering function
|
||||||
Thus pre-filtering takes precedence over any other tag processing. This is
|
that is used to make the final decision how a pre-selected main tag is entered
|
||||||
useful when some specific key/value combinations need to be excluded from
|
into Nominatim's internal table. To further customize handling you may also
|
||||||
processing. When tags are filtered, they may either be deleted completely
|
supply your own filtering function.
|
||||||
or moved to `extratags`. Extra tags are saved with the object and returned
|
|
||||||
to the user when requested, but are not used otherwise.
|
The function takes up to three parameters: a Place object of the object
|
||||||
|
being processed, the key of the main tag and the value of the main tag.
|
||||||
|
The function may return one of three values:
|
||||||
|
|
||||||
|
* `nil` or `false` causes the entry to be ignored
|
||||||
|
* the Place object causes the place to be added as is
|
||||||
|
* `Place.copy(names=..., address=..., extratags=...) causes the
|
||||||
|
place to be enter into the database but with name/address/extratags
|
||||||
|
set to the given different values.
|
||||||
|
|
||||||
|
The Place object has some read-only values that can be used to determine
|
||||||
|
the handling:
|
||||||
|
|
||||||
|
* **object** is the original OSM object data handed in by osm2pgsql
|
||||||
|
* **admin_level** is the content of the admin_level tag, parsed into an integer and normalized to a value between 0 and 15
|
||||||
|
* **has_name** is a boolean indicating if the object has a primary name tag
|
||||||
|
* **names** is a table with the collected list of name tags
|
||||||
|
* **address** is a table with the collected list of address tags
|
||||||
|
* **extratags** is a table with the collected list of additional tags to save
|
||||||
|
|
||||||
|
!!! example
|
||||||
|
``` lua
|
||||||
|
local flex = require('flex-base')
|
||||||
|
|
||||||
|
flex.add_topic('street')
|
||||||
|
|
||||||
|
local function no_sidewalks(place, k, v)
|
||||||
|
if place.object.tags.footway == 'sidewalk' then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- default behaviour is to have all footways
|
||||||
|
return place
|
||||||
|
end
|
||||||
|
|
||||||
|
flex.modify_main_tags(highway = {'footway' = no_sidewalks}
|
||||||
|
```
|
||||||
|
This script adds a custom handler for `highway=footway`. It only includes
|
||||||
|
them in the database, when the object doesn't have a tag `footway=sidewalk`
|
||||||
|
indicating that it is just part of a larger street which should already
|
||||||
|
be indexed. Note that it is not necessary to check the key and value
|
||||||
|
of the main tag because the function is only used for the specific
|
||||||
|
main tag.
|
||||||
|
|
||||||
|
|
||||||
|
### Ignored tags
|
||||||
|
|
||||||
|
The function `ignore_keys()` sets the `delete` classification for keys.
|
||||||
|
This function takes a _key match list_ so that it is possible to exclude
|
||||||
|
groups of keys.
|
||||||
|
|
||||||
|
Note that full matches always take precedence over suffix matches, which
|
||||||
|
in turn take precedence over prefix matches.
|
||||||
|
|
||||||
|
!!! example
|
||||||
|
``` lua
|
||||||
|
local flex = require('flex-base')
|
||||||
|
|
||||||
|
flex.add_topic('admin')
|
||||||
|
flex.ignore_keys{'old_name', 'old_name:*'}
|
||||||
|
```
|
||||||
|
|
||||||
|
This example uses the `admin` preset with the exception that names
|
||||||
|
that are no longer are in current use, are ignored.
|
||||||
|
|
||||||
|
##### Presets
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
| :----- | :---------- |
|
||||||
|
| metatags | Tags with meta information about the OSM tag like source, notes and import sources. |
|
||||||
|
| name | Non-names that actually describe properties or name parts. These names can throw off search and should always be removed. |
|
||||||
|
| address | Extra `addr:*` tags that are not useful for Nominatim. |
|
||||||
|
|
||||||
|
|
||||||
|
### Tags for `extratags`
|
||||||
|
|
||||||
|
The function `add_for_extratags()` sets the `extra` classification for keys.
|
||||||
|
This function takes a
|
||||||
|
_key match list_ so that it is possible to move groups of keys to extratags.
|
||||||
|
|
||||||
|
Note that full matches always take precedence over suffix matches, which
|
||||||
|
in turn take precedence over prefix matches.
|
||||||
|
|
||||||
|
!!! example
|
||||||
|
``` lua
|
||||||
|
local flex = require('flex-base')
|
||||||
|
|
||||||
|
flex.add_topic('street')
|
||||||
|
flex.add_for_extratags{'surface', 'access', 'vehicle', 'maxspeed'}
|
||||||
|
```
|
||||||
|
|
||||||
|
This example uses the `street` preset but adds a couple of tags that
|
||||||
|
are of interest about the condition of the street.
|
||||||
|
|
||||||
|
##### Presets
|
||||||
|
|
||||||
|
Accepts all [presets from ignored tags](#presets_1).
|
||||||
|
|
||||||
|
### General pre-filtering
|
||||||
|
|
||||||
|
_(deprecated)_ `set_prefilters()` allows to set the `delete` and `extra`
|
||||||
|
classification for main tags.
|
||||||
|
|
||||||
|
This function removes all previously set main tags with `delete` and `extra`
|
||||||
|
classification and then adds the newly defined tags.
|
||||||
|
|
||||||
`set_prefilters()` takes a table with four optional fields:
|
`set_prefilters()` takes a table with four optional fields:
|
||||||
|
|
||||||
@@ -130,47 +288,34 @@ to the user when requested, but are not used otherwise.
|
|||||||
* __extra_tags__ contains a table of tag keys pointing to a list of tag
|
* __extra_tags__ contains a table of tag keys pointing to a list of tag
|
||||||
values. Tags with matching key/value pairs are moved to extratags.
|
values. Tags with matching key/value pairs are moved to extratags.
|
||||||
|
|
||||||
Key list may contain three kinds of strings:
|
!!! danger "Deprecation warning"
|
||||||
A string that ends in an asterisk `*` is a prefix match and accordingly matches
|
Use of this function should be replaced with `modify_main_tags()` to
|
||||||
against any key that starts with the given string (minus the `*`).
|
set the data from `delete_tags` and `extra_tags`, with `ignore_keys()`
|
||||||
A suffix match can be defined similarly with a string that starts with a `*`.
|
for the `delete_keys` parameter and with `add_for_extratags()` for the
|
||||||
Any other string is matched exactly against tag keys.
|
`extra_keys` parameter.
|
||||||
|
|
||||||
!!! example
|
### Name tags
|
||||||
``` lua
|
|
||||||
local flex = require('import-full')
|
|
||||||
|
|
||||||
flex.set_prefilters{
|
`set/modify_name_tags()` allow to define the tags used for naming places. Name tags
|
||||||
delete_keys = {'source', 'source:*'},
|
can only be selected by their keys. The import script distinguishes
|
||||||
extra_tags = {amenity = {'yes', 'no'}}
|
between primary and auxiliary names. A primary name is the given name of
|
||||||
}
|
a place. Having a primary name makes a place _named_. This is important
|
||||||
flex.set_main_tags{
|
for main tags that are only included when a name is present. Auxiliary names
|
||||||
amenity = 'always'
|
are identifiers like references. They may be searched for but should not
|
||||||
}
|
be included on their own.
|
||||||
```
|
|
||||||
|
|
||||||
In this example any tags `source` and tags that begin with `source:` are
|
The functions take a table with two optional fields `main` and `extra`.
|
||||||
deleted before any other processing is done. Getting rid of frequent tags
|
They take _key match lists_ for primary and auxiliary names respectively.
|
||||||
this way can speed up the import.
|
A third field `house` can contain tags for names that appear in place of
|
||||||
|
house numbers in addresses. This field can only contain complete key names.
|
||||||
|
'house tags' are special in that they cause the OSM object to be added to
|
||||||
|
the database independently of the presence of other main tags.
|
||||||
|
|
||||||
Tags with `amenity=yes` or `amenity=no` are moved to extratags. Later
|
`set_name_tags()` overwrites the current configuration, while
|
||||||
all tags with an `amenity` key are made a main tag. This effectively means
|
`modify_name_tags()` replaces the fields that are given. (Be aware that
|
||||||
that Nominatim will use all amenity tags except for those with value
|
the fields are replaced as a whole. `main = {'foo_name'}` will cause
|
||||||
yes and no.
|
`foo_name` to become the only recognized primary name. Any previously
|
||||||
|
defined primary names are forgotten.)
|
||||||
#### `set_name_tags()` - defining names
|
|
||||||
|
|
||||||
The flex script distinguishes between two kinds of names:
|
|
||||||
|
|
||||||
* __main__: the primary names make an object fully searchable.
|
|
||||||
Main tags of type _named_ will only cause the object to be included when
|
|
||||||
such a primary name is present. Primary names are usually those found
|
|
||||||
in the `name` tag and its variants.
|
|
||||||
* __extra__: extra names are still added to the search index but they are
|
|
||||||
alone not sufficient to make an object named.
|
|
||||||
|
|
||||||
`set_name_tags()` takes a table with two optional fields `main` and `extra`.
|
|
||||||
They take _key match lists_ for main and extra names respectively.
|
|
||||||
|
|
||||||
!!! example
|
!!! example
|
||||||
``` lua
|
``` lua
|
||||||
@@ -186,29 +331,33 @@ They take _key match lists_ for main and extra names respectively.
|
|||||||
only include those that have a common name and not those which just
|
only include those that have a common name and not those which just
|
||||||
have some reference ID from the city.
|
have some reference ID from the city.
|
||||||
|
|
||||||
#### `set_address_tags()` - defining address parts
|
##### Presets
|
||||||
|
|
||||||
Address tags will be used to build up the address of an object.
|
| Name | Description |
|
||||||
|
| :----- | :---------- |
|
||||||
|
| core | Basic set of recognized names for all places. |
|
||||||
|
| address | Additional names useful when indexing full addresses. |
|
||||||
|
| poi | Extended set of recognized names for pois. Use on top of the core set. |
|
||||||
|
|
||||||
`set_address_tags()` takes a table with arbitrary fields pointing to
|
### Address tags
|
||||||
_key match lists_. Two fields have a special meaning:
|
|
||||||
|
|
||||||
* __main__: defines
|
`set/modify_address_tags()` defines the tags that will be used to build
|
||||||
the tags that make a full address object out of the OSM object. This
|
up the address of an object. Address tags can only be chosen by their key.
|
||||||
is usually the housenumber or variants thereof. If a main address tag
|
|
||||||
appears, then the object will always be included, if necessary with a
|
|
||||||
fallback of `place=house`. If the key has a prefix of `addr:` or `is_in:`
|
|
||||||
this will be stripped.
|
|
||||||
|
|
||||||
* __extra__: defines all supplementary tags for addresses, tags like `addr:street`, `addr:city` etc. If the key has a prefix of `addr:` or `is_in:` this will be stripped.
|
The functions take a table with arbitrary fields, each defining
|
||||||
|
a key list or _key match list_. Some fields have a special meaning:
|
||||||
|
|
||||||
All other fields will be handled as summary fields. If a key matches the
|
| Field | Type | Description |
|
||||||
key match list, then its value will be added to the address tags with the
|
| :---------| :-------- | :-----------|
|
||||||
name of the field as key. If multiple tags match, then an arbitrary one
|
| main | key list | Tags that make a full address object out of the OSM object. This is usually the house number or variants thereof. If a main address tag appears, then the object will always be included, if necessary with a fallback of `place=house`. If the key has a prefix of `addr:` or `is_in:` this will be stripped. |
|
||||||
wins.
|
| extra | key match list | Supplementary tags for addresses, tags like `addr:street`, `addr:city` etc. If the key has a prefix of `addr:` or `is_in:` this will be stripped. |
|
||||||
|
| interpolation | key list | Tags that identify address interpolation lines. |
|
||||||
|
| country | key match list | Tags that may contain the country the place is in. The first found value with a two-letter code will be accepted, all other values are discarded. |
|
||||||
|
| _other_ | key match list | Summary field. If a key matches the key match list, then its value will be added to the address tags with the name of the field as key. If multiple tags match, then an arbitrary one wins. |
|
||||||
|
|
||||||
Country tags are handled slightly special. Only tags with a two-letter code
|
`set_address_tags()` overwrites the current configuration, while
|
||||||
are accepted, all other values are discarded.
|
`modify_address_tags()` replaces the fields that are given. (Be aware that
|
||||||
|
the fields are replaced as a whole.)
|
||||||
|
|
||||||
!!! example
|
!!! example
|
||||||
``` lua
|
``` lua
|
||||||
@@ -232,21 +381,33 @@ are accepted, all other values are discarded.
|
|||||||
to postcodes, they will always be saved under the key `postcode` thus
|
to postcodes, they will always be saved under the key `postcode` thus
|
||||||
normalizing the multitude of keys that are used in the OSM database.
|
normalizing the multitude of keys that are used in the OSM database.
|
||||||
|
|
||||||
|
##### Presets
|
||||||
|
|
||||||
#### `set_unused_handling()` - processing remaining tags
|
| Name | Description |
|
||||||
|
| :----- | :---------- |
|
||||||
|
| core | Basic set of tags needed to recognize address relationship for any place. Always include this. |
|
||||||
|
| houses | Additional set of tags needed to recognize proper addresses |
|
||||||
|
|
||||||
This function defines what to do with tags that remain after all tags
|
### Handling of unclassified tags
|
||||||
|
|
||||||
|
`set_unused_handling()` defines what to do with tags that remain after all tags
|
||||||
have been classified using the functions above. There are two ways in
|
have been classified using the functions above. There are two ways in
|
||||||
which the function can be used:
|
which the function can be used:
|
||||||
|
|
||||||
`set_unused_handling(delete_keys = ..., delete_tags = ...)` deletes all
|
`set_unused_handling(delete_keys = ..., delete_tags = ...)` deletes all
|
||||||
keys that match the descriptions in the parameters and moves all remaining
|
keys that match the descriptions in the parameters and moves all remaining
|
||||||
tags into the extratags list.
|
tags into the extratags list.
|
||||||
|
|
||||||
`set_unused_handling(extra_keys = ..., extra_tags = ...)` moves all tags
|
`set_unused_handling(extra_keys = ..., extra_tags = ...)` moves all tags
|
||||||
matching the parameters into the extratags list and then deletes the remaining
|
matching the parameters into the extratags list and then deletes the remaining
|
||||||
tags. For the format of the parameters see the description in `set_prefilters()`
|
tags. For the format of the parameters see the description in `set_prefilters()`
|
||||||
above.
|
above.
|
||||||
|
|
||||||
|
When no special handling is set, then unused tags will be discarded with one
|
||||||
|
exception: place tags are kept in extratags for administrative boundaries.
|
||||||
|
When using a custom setting, you should also make sure that the place tag
|
||||||
|
is added for extratags.
|
||||||
|
|
||||||
!!! example
|
!!! example
|
||||||
``` lua
|
``` lua
|
||||||
local flex = require('import-full')
|
local flex = require('import-full')
|
||||||
@@ -263,17 +424,73 @@ above.
|
|||||||
already delete the tiger tags with `set_prefilters()` because that
|
already delete the tiger tags with `set_prefilters()` because that
|
||||||
would remove tiger:county before the address tags are processed.
|
would remove tiger:county before the address tags are processed.
|
||||||
|
|
||||||
### Customizing osm2pgsql callbacks
|
## Filling additional tables
|
||||||
|
|
||||||
|
Most of the OSM objects are saved in the main `place` table for further
|
||||||
|
processing. In addition to that, there are some smaller tables that save
|
||||||
|
specialised information. The content of these tables can be customized as
|
||||||
|
well.
|
||||||
|
|
||||||
|
### Entrance table
|
||||||
|
|
||||||
|
The table `place_entrance` saves information about OSM nodes that represent
|
||||||
|
an entrance. This data is later mingled with buildings and other areas and
|
||||||
|
can be returned [on request](../api/Search.md#output-details). The table
|
||||||
|
saves the type of entrance as well as a set of custom extra tags.
|
||||||
|
|
||||||
|
The function `set_entrance_filter()` can be used to customize the table's
|
||||||
|
content.
|
||||||
|
|
||||||
|
When called without any parameter, then filling the entrance table will be
|
||||||
|
disabled. When called with a preset name, the appropriate preset will be
|
||||||
|
applied.
|
||||||
|
|
||||||
|
To create a custom configuration, call the function
|
||||||
|
with a table with the following fields:
|
||||||
|
|
||||||
|
* __main_tags__ is a list of tags that mark an entrance node. The value of the
|
||||||
|
first tag found in the list will be used as the entrance type.
|
||||||
|
* __extra_include__ is an optional list of tags to be added to the extratags
|
||||||
|
for this entrance. When left out, all tags except for the ones defined
|
||||||
|
in 'main_tags' will be included. To disable saving of extra tags, set
|
||||||
|
this to the empty list.
|
||||||
|
* __extra_exclude__ defines an optional list of tags to drop before including
|
||||||
|
the remaining tags as extratags. Note that the tags defined in 'main_tags'
|
||||||
|
will always be excluded, independently of this setting.
|
||||||
|
|
||||||
|
To have even more fine-grained control over the output, you can also hand
|
||||||
|
in a callback for processing entrance information. The callback function
|
||||||
|
receives a single parameter, the
|
||||||
|
[osm2pgsql object](https://osm2pgsql.org/doc/manual.html#processing-callbacks).
|
||||||
|
This object itself must not be modified. The callback should return either
|
||||||
|
`nil` when the object is not an entrance. Or it returns a table with a
|
||||||
|
mandatory `entrance` field containing a string with the type of entrance
|
||||||
|
and an optional `extratags` field with a simple key-value table of extra
|
||||||
|
information.
|
||||||
|
|
||||||
|
##### Presets
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
| :----- | :---------- |
|
||||||
|
| default | Standard configuration used with `full` and `extratags` styles. |
|
||||||
|
|
||||||
|
## Customizing osm2pgsql callbacks
|
||||||
|
|
||||||
osm2pgsql expects the flex style to implement three callbacks, one process
|
osm2pgsql expects the flex style to implement three callbacks, one process
|
||||||
function per OSM type. If you want to implement special handling for
|
function per OSM type. If you want to implement special handling for
|
||||||
certain OSM types, you can override the default implementations provided
|
certain OSM types, you can override the default implementations provided
|
||||||
by the flex-base module.
|
by the flex-base module.
|
||||||
|
|
||||||
#### Changing the relation types to be handled
|
### Enabling additional relation types
|
||||||
|
|
||||||
The default scripts only allows relations of type `multipolygon`, `boundary`
|
OSM relations can represent very diverse
|
||||||
and `waterway`. To add other types relations, set `RELATION_TYPES` for
|
[types of real-world objects](https://wiki.openstreetmap.org/wiki/Key:type). To
|
||||||
|
be able to process them correctly, Nominatim needs to understand how to
|
||||||
|
create a geometry for each type. By default, the script knows how to
|
||||||
|
process relations of type `multipolygon`, `boundary` and `waterway`. All
|
||||||
|
other relation types are ignored.
|
||||||
|
|
||||||
|
To add other types relations, set `RELATION_TYPES` for
|
||||||
the type to the kind of geometry that should be created. The following
|
the type to the kind of geometry that should be created. The following
|
||||||
kinds of geometries can be used:
|
kinds of geometries can be used:
|
||||||
|
|
||||||
@@ -297,7 +514,7 @@ kinds of geometries can be used:
|
|||||||
geometry.
|
geometry.
|
||||||
|
|
||||||
|
|
||||||
#### Adding additional logic to processing functions
|
### Adding additional logic to processing functions
|
||||||
|
|
||||||
The default processing functions are also exported by the flex-base module
|
The default processing functions are also exported by the flex-base module
|
||||||
as `process_node`, `process_way` and `process_relation`. These can be used
|
as `process_node`, `process_way` and `process_relation`. These can be used
|
||||||
@@ -322,122 +539,85 @@ logic.
|
|||||||
|
|
||||||
### Customizing the main processing function
|
### Customizing the main processing function
|
||||||
|
|
||||||
The main processing function of the flex style can be found in the function
|
!!! danger "Deprecation Warning"
|
||||||
`process_tags`. This function is called for all OSM object kinds and is
|
The style used to allow overwriting the internal processing function
|
||||||
responsible for filtering the tags and writing out the rows into Postgresql.
|
`process_tags()`. While this is currently still possible, it is no longer
|
||||||
|
encouraged and may stop working in future versions. The internal
|
||||||
|
`Place` class should now be considered read-only.
|
||||||
|
|
||||||
|
|
||||||
|
## Using osm2pgsql-themepark
|
||||||
|
|
||||||
|
The Nominatim osm2pgsql style is designed so that it can also be used as
|
||||||
|
a theme for [osm2pgsql-themepark](https://osm2pgsql.org/themepark/). This
|
||||||
|
makes it easy to combine Nominatim with other projects like
|
||||||
|
[openstreetmap-carto](https://github.com/gravitystorm/openstreetmap-carto)
|
||||||
|
in the same database.
|
||||||
|
|
||||||
|
To set up one of the preset styles, simply include a topic with the same name:
|
||||||
|
|
||||||
|
```
|
||||||
|
local themepark = require('themepark')
|
||||||
|
themepark:add_topic('nominatim/address')
|
||||||
|
```
|
||||||
|
|
||||||
|
Themepark topics offer two configuration options:
|
||||||
|
|
||||||
|
* **street_theme** allows to choose one of the sub topics for streets:
|
||||||
|
* _default_ - include all major streets and named minor paths
|
||||||
|
* _car_ - include all streets physically usable by cars
|
||||||
|
* _all_ - include all major streets and minor paths
|
||||||
|
* **with_extratags**, when set to a truthy value, then tags that are
|
||||||
|
not specifically used for address or naming are added to the
|
||||||
|
extratags column
|
||||||
|
|
||||||
|
The customization functions described in the
|
||||||
|
[Changing recognized tags](#changing-the-recognized-tags) section
|
||||||
|
are available from the theme. To access the theme you need to explicitly initialize it.
|
||||||
|
|
||||||
!!! Example
|
!!! Example
|
||||||
``` lua
|
``` lua
|
||||||
local flex = require('import-full')
|
local themepark = require('themepark')
|
||||||
|
|
||||||
local original_process_tags = flex.process_tags
|
themepark:add_topic('nominatim/full', {with_extratags = true})
|
||||||
|
|
||||||
function flex.process_tags(o)
|
local flex = themepark:init_theme('nominatim')
|
||||||
if o.object.tags.highway ~= nil and o.object.tags.access == 'no' then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
original_process_tags(o)
|
flex.modify_main_tags{'amenity' = {
|
||||||
end
|
'waste_basket' = 'delete'}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
This example uses the full Nominatim configuration but disables
|
||||||
|
importing waste baskets.
|
||||||
|
|
||||||
This example shows the most simple customization of the process_tags function.
|
You may also write a new configuration from scratch. Simply omit including
|
||||||
It simply adds some additional processing before running the original code.
|
a Nominatim topic and only call the required customization functions.
|
||||||
To do that, first save the original function and then overwrite process_tags
|
|
||||||
from the module. In this example all highways which are not accessible
|
|
||||||
by anyone will be ignored.
|
|
||||||
|
|
||||||
|
Customizing the osm2pgsql processing functions as explained
|
||||||
|
[above](#adding-additional-logic-to-processing-functions) is not possible
|
||||||
|
when running under themepark. Instead include other topics that make the
|
||||||
|
necessary modifications or add an additional processor before including
|
||||||
|
the Nominatim topic.
|
||||||
|
|
||||||
#### The `Place` class
|
!!! Example
|
||||||
|
``` lua
|
||||||
|
local themepark = require('themepark')
|
||||||
|
|
||||||
The `process_tags` function receives a Lua object of `Place` type which comes
|
local function discard_country_boundaries(object)
|
||||||
with some handy functions to collect the data necessary for geocoding and
|
if object.tags.boundary == 'administrative' and object.tags.admin_level == '2' then
|
||||||
writing it into the place table. Always use this object to fill the table.
|
return 'stop'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
The Place class has some attributes which you may access read-only:
|
themepark:add_proc('relation', discard_country_boundaries)
|
||||||
|
-- Order matters here. The topic needs to be added after the custom callback.
|
||||||
|
themepark:add_topic('nominatim/full', {with_extratags = true})
|
||||||
|
```
|
||||||
|
Discarding country-level boundaries when running under themepark.
|
||||||
|
|
||||||
* __object__ is the original OSM object data handed in by osm2pgsql
|
## Changing the style of existing databases
|
||||||
* __admin_level__ is the content of the admin_level tag, parsed into an
|
|
||||||
integer and normalized to a value between 0 and 15
|
|
||||||
* __has_name__ is a boolean indicating if the object has a full name
|
|
||||||
* __names__ is a table with the collected list of name tags
|
|
||||||
* __address__ is a table with the collected list of address tags
|
|
||||||
* __extratags__ is a table with the collected list of additional tags to save
|
|
||||||
|
|
||||||
There are a number of functions to fill these fields. All functions expect
|
There is usually no issue changing the style of a database that is already
|
||||||
a table parameter with fields as indicated in the description.
|
|
||||||
Many of these functions expect match functions which are described in detail
|
|
||||||
further below.
|
|
||||||
|
|
||||||
* __delete{match=...}__ removes all tags that match the match function given
|
|
||||||
in _match_.
|
|
||||||
* __grab_extratags{match=...}__ moves all tags that match the match function
|
|
||||||
given in _match_ into extratags. Returns the number of tags moved.
|
|
||||||
* __clean{delete=..., extra=...}__ deletes all tags that match _delete_ and
|
|
||||||
moves the ones that match _extra_ into extratags
|
|
||||||
* __grab_address_parts{groups=...}__ moves matching tags into the address table.
|
|
||||||
_groups_ must be a group match function. Tags of the group `main` and
|
|
||||||
`extra` are added to the address table as is but with `addr:` and `is_in:`
|
|
||||||
prefixes removed from the tag key. All other groups are added with the
|
|
||||||
group name as key and the value from the tag. Multiple values of the same
|
|
||||||
group overwrite each other. The function returns the number of tags saved
|
|
||||||
from the main group.
|
|
||||||
* __grab_main_parts{groups=...}__ moves matching tags into the name table.
|
|
||||||
_groups_ must be a group match function. If a tags of the group `main` is
|
|
||||||
present, the object will be marked as having a name. Tags of group `house`
|
|
||||||
produce a fallback to `place=house`. This fallback is return by the function
|
|
||||||
if present.
|
|
||||||
|
|
||||||
There are two functions to write a row into the place table. Both functions
|
|
||||||
expect the main tag (key and value) for the row and then use the collected
|
|
||||||
information from the name, address, extratags etc. fields to complete the row.
|
|
||||||
They also have a boolean parameter `save_extra_mains` which defines how any
|
|
||||||
unprocessed tags are handled: when True, the tags will be saved as extratags,
|
|
||||||
when False, they will be simply discarded.
|
|
||||||
|
|
||||||
* __write_row(key, value, save_extra_mains)__ creates a new table row from
|
|
||||||
the current state of the Place object.
|
|
||||||
* __write_place(key, value, mtype, save_extra_mains)__ creates a new row
|
|
||||||
conditionally. When value is nil, the function will attempt to look up the
|
|
||||||
value in the object tags. If value is still nil or mtype is nil, the row
|
|
||||||
is ignored. An mtype of `always` will then always write out the row,
|
|
||||||
a mtype of `named` only, when the object has a full name. When mtype
|
|
||||||
is `named_with_key`, the function checks for a domain name, i.e. a name
|
|
||||||
tag prefixed with the name of the main key. Only if at least one is found,
|
|
||||||
the row will be written. The names are replaced with the domain names found.
|
|
||||||
|
|
||||||
#### Match functions
|
|
||||||
|
|
||||||
The Place functions usually expect either a _match function_ or a
|
|
||||||
_group match function_ to find the tags to apply their function to.
|
|
||||||
|
|
||||||
The __match function__ is a Lua function which takes two parameters,
|
|
||||||
key and value, and returns a boolean to indicate that a tag matches. The
|
|
||||||
flex-base module has a convenience function `tag_match()` to create such a
|
|
||||||
function. It takes a table with two optional fields: `keys` takes a key match
|
|
||||||
list (see above), `tags` takes a table with keys that point to a list of
|
|
||||||
possible values, thus defining key/value matches.
|
|
||||||
|
|
||||||
The __group match function__ is a Lua function which also takes two parameters,
|
|
||||||
key and value, and returns a string indicating to which group or type they
|
|
||||||
belong to. The `tag_group()` can be used to create such a function. It expects
|
|
||||||
a table where the group names are the keys and the values are a key match list.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Using the gazetteer output of osm2pgsql
|
|
||||||
|
|
||||||
Nominatim still allows you to configure the gazetteer output to remain
|
|
||||||
backwards compatible with older imports. It will be automatically used
|
|
||||||
when the style file name ends in `.style`. For documentation of the
|
|
||||||
old import style, please refer to the documentation of older releases
|
|
||||||
of Nominatim. Do not use the gazetteer output for new imports. There is no
|
|
||||||
guarantee that new versions of Nominatim are fully compatible with the
|
|
||||||
gazetteer output.
|
|
||||||
|
|
||||||
### Changing the Style of Existing Databases
|
|
||||||
|
|
||||||
There is normally no issue changing the style of a database that is already
|
|
||||||
imported and now kept up-to-date with change files. Just be aware that any
|
imported and now kept up-to-date with change files. Just be aware that any
|
||||||
change in the style applies to updates only. If you want to change the data
|
change in the style applies to updates only. If you want to change the data
|
||||||
that is already in the database, then a reimport is necessary.
|
that is already in the database, then a reimport is necessary.
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ _None._
|
|||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
|-----------------|-------------|
|
|-----------------|-------------|
|
||||||
| locales | [Locale](../library/Result-Handling.md#locale) object for the requested language(s) |
|
| locales | [Locales](../library/Result-Handling.md#locale) object for the requested language(s) |
|
||||||
| group_hierarchy | Setting of [group_hierarchy](../api/Details.md#output-details) parameter |
|
| group_hierarchy | Setting of [group_hierarchy](../api/Details.md#output-details) parameter |
|
||||||
| icon_base_url | (optional) URL pointing to icons as set in [NOMINATIM_MAPICON_URL](Settings.md#nominatim_mapicon_url) |
|
| icon_base_url | (optional) URL pointing to icons as set in [NOMINATIM_MAPICON_URL](Settings.md#nominatim_mapicon_url) |
|
||||||
|
|
||||||
|
|||||||
@@ -57,34 +57,13 @@ parameter that is understood by libpq. See the [Postgres documentation](https://
|
|||||||
| **After Changes:** | cannot be changed after import |
|
| **After Changes:** | cannot be changed after import |
|
||||||
|
|
||||||
Defines the name of the database user that will run search queries. Usually
|
Defines the name of the database user that will run search queries. Usually
|
||||||
this is the user under which the webserver is executed. When running Nominatim
|
this is the user under which the webserver is executed. The Postgres user
|
||||||
via php-fpm, you can also define a separate query user. The Postgres user
|
|
||||||
needs to be set up before starting the import.
|
needs to be set up before starting the import.
|
||||||
|
|
||||||
Nominatim grants minimal rights to this user to all tables that are needed
|
Nominatim grants minimal rights to this user to all tables that are needed
|
||||||
for running geocoding queries.
|
for running geocoding queries.
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_DATABASE_MODULE_PATH
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Directory where to find the PostgreSQL server module |
|
|
||||||
| **Format:** | path |
|
|
||||||
| **Default:** | _empty_ (use `<project_directory>/module`) |
|
|
||||||
| **After Changes:** | run `nominatim refresh --functions` |
|
|
||||||
| **Comment:** | Legacy tokenizer only |
|
|
||||||
|
|
||||||
Defines the directory in which the PostgreSQL server module `nominatim.so`
|
|
||||||
is stored. The directory and module must be accessible by the PostgreSQL
|
|
||||||
server.
|
|
||||||
|
|
||||||
For information on how to use this setting when working with external databases,
|
|
||||||
see [Advanced Installations](../admin/Advanced-Installations.md).
|
|
||||||
|
|
||||||
The option is only used by the Legacy tokenizer and ignored otherwise.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_TOKENIZER
|
#### NOMINATIM_TOKENIZER
|
||||||
|
|
||||||
| Summary | |
|
| Summary | |
|
||||||
@@ -115,20 +94,6 @@ on the file format.
|
|||||||
If a relative path is given, then the file is searched first relative to the
|
If a relative path is given, then the file is searched first relative to the
|
||||||
project directory and then in the global settings directory.
|
project directory and then in the global settings directory.
|
||||||
|
|
||||||
#### NOMINATIM_MAX_WORD_FREQUENCY
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Number of occurrences before a word is considered frequent |
|
|
||||||
| **Format:** | int |
|
|
||||||
| **Default:** | 50000 |
|
|
||||||
| **After Changes:** | cannot be changed after import |
|
|
||||||
| **Comment:** | Legacy tokenizer only |
|
|
||||||
|
|
||||||
The word frequency count is used by the Legacy tokenizer to automatically
|
|
||||||
identify _stop words_. Any partial term that occurs more often then what
|
|
||||||
is defined in this setting, is effectively ignored during search.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_LIMIT_REINDEXING
|
#### NOMINATIM_LIMIT_REINDEXING
|
||||||
|
|
||||||
@@ -163,25 +128,6 @@ codes, to restrict import to a subset of languages.
|
|||||||
Currently only affects the initial import of country names and special phrases.
|
Currently only affects the initial import of country names and special phrases.
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_TERM_NORMALIZATION
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Rules for normalizing terms for comparisons |
|
|
||||||
| **Format:** | string: semicolon-separated list of ICU rules |
|
|
||||||
| **Default:** | :: NFD (); [[:Nonspacing Mark:] [:Cf:]] >; :: lower (); [[:Punctuation:][:Space:]]+ > ' '; :: NFC (); |
|
|
||||||
| **Comment:** | Legacy tokenizer only |
|
|
||||||
|
|
||||||
[Special phrases](Special-Phrases.md) have stricter matching requirements than
|
|
||||||
normal search terms. They must appear exactly in the query after this term
|
|
||||||
normalization has been applied.
|
|
||||||
|
|
||||||
Only has an effect on the Legacy tokenizer. For the ICU tokenizer the rules
|
|
||||||
defined in the
|
|
||||||
[normalization section](Tokenizers.md#normalization-and-transliteration)
|
|
||||||
will be used.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_USE_US_TIGER_DATA
|
#### NOMINATIM_USE_US_TIGER_DATA
|
||||||
|
|
||||||
| Summary | |
|
| Summary | |
|
||||||
@@ -390,7 +336,7 @@ NOMINATIM_TABLESPACE_SEARCH_INDEX
|
|||||||
NOMINATIM_TABLESPACE_OSM_DATA
|
NOMINATIM_TABLESPACE_OSM_DATA
|
||||||
: Raw OSM data cache used for import and updates.
|
: Raw OSM data cache used for import and updates.
|
||||||
|
|
||||||
NOMINATIM_TABLESPACE_OSM_DATA
|
NOMINATIM_TABLESPACE_OSM_INDEX
|
||||||
: Indexes on the raw OSM data cache.
|
: Indexes on the raw OSM data cache.
|
||||||
|
|
||||||
NOMINATIM_TABLESPACE_PLACE_DATA
|
NOMINATIM_TABLESPACE_PLACE_DATA
|
||||||
@@ -544,38 +490,6 @@ the local languages (in OSM: the name tag without any language suffix) is
|
|||||||
used.
|
used.
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_SEARCH_BATCH_MODE
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Enable a special batch query mode |
|
|
||||||
| **Format:** | boolean |
|
|
||||||
| **Default:** | no |
|
|
||||||
| **After Changes:** | run `nominatim refresh --website` |
|
|
||||||
| **Comment:** | PHP frontend only |
|
|
||||||
|
|
||||||
|
|
||||||
This feature is currently undocumented and potentially broken.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_SEARCH_NAME_ONLY_THRESHOLD
|
|
||||||
|
|
||||||
| Summary | |
|
|
||||||
| -------------- | --------------------------------------------------- |
|
|
||||||
| **Description:** | Threshold for switching the search index lookup strategy |
|
|
||||||
| **Format:** | integer |
|
|
||||||
| **Default:** | 500 |
|
|
||||||
| **After Changes:** | run `nominatim refresh --website` |
|
|
||||||
| **Comment:** | PHP frontend only |
|
|
||||||
|
|
||||||
This setting defines the threshold over which a name is no longer considered
|
|
||||||
as rare. When searching for places with rare names, only the name is used
|
|
||||||
for place lookups. Otherwise the name and any address information is used.
|
|
||||||
|
|
||||||
This setting only has an effect after `nominatim refresh --word-counts` has
|
|
||||||
been called to compute the word frequencies.
|
|
||||||
|
|
||||||
|
|
||||||
#### NOMINATIM_LOOKUP_MAX_COUNT
|
#### NOMINATIM_LOOKUP_MAX_COUNT
|
||||||
|
|
||||||
| Summary | |
|
| Summary | |
|
||||||
@@ -616,7 +530,6 @@ Setting this parameter to 0 disables polygon output completely.
|
|||||||
| **Format:** | boolean |
|
| **Format:** | boolean |
|
||||||
| **Default:** | no |
|
| **Default:** | no |
|
||||||
| **After Changes:** | run `nominatim refresh --website` |
|
| **After Changes:** | run `nominatim refresh --website` |
|
||||||
| **Comment:** | PHP frontend only |
|
|
||||||
|
|
||||||
Enable to search elements just within countries.
|
Enable to search elements just within countries.
|
||||||
|
|
||||||
@@ -689,25 +602,44 @@ results gathered so far.
|
|||||||
Note that under high load you may observe that users receive different results
|
Note that under high load you may observe that users receive different results
|
||||||
than usual without seeing an error. This may cause some confusion.
|
than usual without seeing an error. This may cause some confusion.
|
||||||
|
|
||||||
### Logging Settings
|
#### NOMINATIM_OUTPUT_NAMES
|
||||||
|
|
||||||
#### NOMINATIM_LOG_DB
|
|
||||||
|
|
||||||
| Summary | |
|
| Summary | |
|
||||||
| -------------- | --------------------------------------------------- |
|
| -------------- | --------------------------------------------------- |
|
||||||
| **Description:** | Log requests into the database |
|
| **Description:** | Specifies order of name tags |
|
||||||
| **Format:** | boolean |
|
| **Format:** | string: comma-separated list of tag names |
|
||||||
| **Default:** | no |
|
| **Default:** | name:XX,name,brand,official_name:XX,short_name:XX,official_name,short_name,ref |
|
||||||
| **After Changes:** | run `nominatim refresh --website` |
|
|
||||||
|
|
||||||
Enable logging requests into a database table with this setting. The logs
|
Specifies the order in which different name tags are used.
|
||||||
can be found in the table `new_query_log`.
|
The values in this list determine the preferred order of name variants,
|
||||||
|
including language-specific names (in OSM: the name tag with and without any language suffix).
|
||||||
|
|
||||||
When using this logging method, it is advisable to set up a job that
|
Comma-separated list, where :XX stands for language suffix
|
||||||
regularly clears out old logging information. Nominatim will not do that
|
(e.g. name:en) and no :XX stands for general tags (e.g. name).
|
||||||
on its own.
|
|
||||||
|
|
||||||
Can be used as the same time as NOMINATIM_LOG_FILE.
|
See also [NOMINATIM_DEFAULT_LANGUAGE](#nominatim_default_language).
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
If NOMINATIM_OUTPUT_NAMES = `name:XX,name,short_name:XX,short_name` the search follows
|
||||||
|
|
||||||
|
```
|
||||||
|
'name', 'short_name'
|
||||||
|
```
|
||||||
|
|
||||||
|
if we have no preferred language order for showing search results.
|
||||||
|
|
||||||
|
For languages ['en', 'es'] the search follows
|
||||||
|
|
||||||
|
```
|
||||||
|
'name:en', 'name:es',
|
||||||
|
'name',
|
||||||
|
'short_name:en', 'short_name:es',
|
||||||
|
'short_name'
|
||||||
|
```
|
||||||
|
|
||||||
|
For those familiar with the internal implementation, the `_place_*` expansion is added, but to simplify, it is not included in this example.
|
||||||
|
|
||||||
|
### Logging Settings
|
||||||
|
|
||||||
#### NOMINATIM_LOG_FILE
|
#### NOMINATIM_LOG_FILE
|
||||||
|
|
||||||
@@ -716,22 +648,53 @@ Can be used as the same time as NOMINATIM_LOG_FILE.
|
|||||||
| **Description:** | Log requests into a file |
|
| **Description:** | Log requests into a file |
|
||||||
| **Format:** | path |
|
| **Format:** | path |
|
||||||
| **Default:** | _empty_ (logging disabled) |
|
| **Default:** | _empty_ (logging disabled) |
|
||||||
| **After Changes:** | run `nominatim refresh --website` |
|
|
||||||
|
|
||||||
Enable logging of requests into a file with this setting by setting the log
|
Enable logging of requests into a file with this setting by setting the log
|
||||||
file where to log to. A relative file name is assumed to be relative to
|
file where to log to. A relative file name is assumed to be relative to
|
||||||
the project directory.
|
the project directory. The format of the log output can be set
|
||||||
|
with NOMINATIM_LOG_FORMAT.
|
||||||
|
|
||||||
|
#### NOMINATIM_LOG_FORMAT
|
||||||
|
|
||||||
The entries in the log file have the following format:
|
| Summary | |
|
||||||
|
| -------------- | --------------------------------------------------- |
|
||||||
|
| **Description:** | Log requests into a file |
|
||||||
|
| **Format:** | [Python String Format](https://docs.python.org/3/library/string.html#formatstrings) string |
|
||||||
|
| **Default:** | `[{start}] {total_time:.4f} {results_total} {endpoint} "{query_string}"` |
|
||||||
|
|
||||||
<request time> <execution time in s> <number of results> <type> "<query string>"
|
Describes the content of a log line for a single request. The format
|
||||||
|
must be readable by Python's format function. Nominatim provides a number
|
||||||
|
of metrics than can be logged. The default set of metrics is the following:
|
||||||
|
|
||||||
Request time is the time when the request was started. The execution time is
|
/// html | div.simple-table
|
||||||
given in seconds and corresponds to the time the query took executing in PHP.
|
| name | type | Description |
|
||||||
type contains the name of the endpoint used.
|
| --------------- | ------ | ------------|
|
||||||
|
| start | time | Point in time when the request arrived. |
|
||||||
|
| end | time | Point in time when the request was done. |
|
||||||
|
| query_start | time | Point in time when processing started. |
|
||||||
|
| total_time | float | Total time in seconds to handle the request. |
|
||||||
|
| wait_time | float | Time in seconds the request waited for a database connection to be available. |
|
||||||
|
| query_time | float | Total time in seconds to process the request once a connection was available. |
|
||||||
|
| results_total | int | Number of results found. |
|
||||||
|
| endpoint | string | API endpoint used. |
|
||||||
|
| query_string | string | Raw query string received. |
|
||||||
|
///
|
||||||
|
|
||||||
|
Variables of type 'time' contain a UTC timestamp string in ISO format.
|
||||||
|
|
||||||
|
Nominatim also exposes additional metrics to help with development. These
|
||||||
|
are subject to change between versions:
|
||||||
|
|
||||||
|
/// html | div.simple-table
|
||||||
|
| name | type | Description |
|
||||||
|
| ------------------------- | ------ | ------------|
|
||||||
|
| search_rounds | int | Total number of searches executed for the request. |
|
||||||
|
| search_min_penalty | float | Minimal possible penalty for the request. |
|
||||||
|
| search_first_result_round | int | Number of first search to yield any result. |
|
||||||
|
| search_min_result_penalty | float | Minimal penalty by a result found. |
|
||||||
|
| search_best_penalty_round | int | Search round that yielded the best penalty result. |
|
||||||
|
///
|
||||||
|
|
||||||
Can be used as the same time as NOMINATIM_LOG_DB.
|
|
||||||
|
|
||||||
#### NOMINATIM_DEBUG_SQL
|
#### NOMINATIM_DEBUG_SQL
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ columns:
|
|||||||
|
|
||||||
* **phrase**: the keyword to look for
|
* **phrase**: the keyword to look for
|
||||||
* **class**: key of the main tag of the place to find
|
* **class**: key of the main tag of the place to find
|
||||||
(see [principal tags in import style](Import-Styles.md#set_main_tags-principal-tags)
|
(see [Import styles](Import-Styles.md#how-processing-works)
|
||||||
* **type**: value of the main tag
|
* **type**: value of the main tag
|
||||||
* **operator**: type of special phrase, may be one of:
|
* **operator**: type of special phrase, may be one of:
|
||||||
* *in*: place is within the place defined by the search term (e.g. "_Hotels in_ Berlin")
|
* *in*: place is within the place defined by the search term (e.g. "_Hotels in_ Berlin")
|
||||||
|
|||||||
@@ -4,64 +4,16 @@ The tokenizer module in Nominatim is responsible for analysing the names given
|
|||||||
to OSM objects and the terms of an incoming query in order to make sure, they
|
to OSM objects and the terms of an incoming query in order to make sure, they
|
||||||
can be matched appropriately.
|
can be matched appropriately.
|
||||||
|
|
||||||
Nominatim offers different tokenizer modules, which behave differently and have
|
Nominatim currently offers only one tokenizer module, the ICU tokenizer. This section
|
||||||
different configuration options. This sections describes the tokenizers and how
|
describes the tokenizer and how it can be configured.
|
||||||
they can be configured.
|
|
||||||
|
|
||||||
!!! important
|
!!! important
|
||||||
The use of a tokenizer is tied to a database installation. You need to choose
|
The selection of tokenizer is tied to a database installation. You need to choose
|
||||||
and configure the tokenizer before starting the initial import. Once the import
|
and configure the tokenizer before starting the initial import. Once the import
|
||||||
is done, you cannot switch to another tokenizer anymore. Reconfiguring the
|
is done, you cannot switch to another tokenizer anymore. Reconfiguring the
|
||||||
chosen tokenizer is very limited as well. See the comments in each tokenizer
|
chosen tokenizer is very limited as well. See the comments in each tokenizer
|
||||||
section.
|
section.
|
||||||
|
|
||||||
## Legacy tokenizer
|
|
||||||
|
|
||||||
!!! danger
|
|
||||||
The Legacy tokenizer is deprecated and will be removed in Nominatim 5.0.
|
|
||||||
If you still use a database with the legacy tokenizer, you must reimport
|
|
||||||
it using the ICU tokenizer below.
|
|
||||||
|
|
||||||
The legacy tokenizer implements the analysis algorithms of older Nominatim
|
|
||||||
versions. It uses a special Postgresql module to normalize names and queries.
|
|
||||||
This tokenizer is automatically installed and used when upgrading an older
|
|
||||||
database. It should not be used for new installations anymore.
|
|
||||||
|
|
||||||
### Compiling the PostgreSQL module
|
|
||||||
|
|
||||||
The tokeinzer needs a special C module for PostgreSQL which is not compiled
|
|
||||||
by default. If you need the legacy tokenizer, compile Nominatim as follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake -DBUILD_MODULE=on
|
|
||||||
make
|
|
||||||
```
|
|
||||||
|
|
||||||
### Enabling the tokenizer
|
|
||||||
|
|
||||||
To enable the tokenizer add the following line to your project configuration:
|
|
||||||
|
|
||||||
```
|
|
||||||
NOMINATIM_TOKENIZER=legacy
|
|
||||||
```
|
|
||||||
|
|
||||||
The Postgresql module for the tokenizer is available in the `module` directory
|
|
||||||
and also installed with the remainder of the software under
|
|
||||||
`lib/nominatim/module/nominatim.so`. You can specify a custom location for
|
|
||||||
the module with
|
|
||||||
|
|
||||||
```
|
|
||||||
NOMINATIM_DATABASE_MODULE_PATH=<path to directory where nominatim.so resides>
|
|
||||||
```
|
|
||||||
|
|
||||||
This is in particular useful when the database runs on a different server.
|
|
||||||
See [Advanced installations](../admin/Advanced-Installations.md#using-an-external-postgresql-database) for details.
|
|
||||||
|
|
||||||
There are no other configuration options for the legacy tokenizer. All
|
|
||||||
normalization functions are hard-coded.
|
|
||||||
|
|
||||||
## ICU tokenizer
|
## ICU tokenizer
|
||||||
|
|
||||||
The ICU tokenizer uses the [ICU library](http://site.icu-project.org/) to
|
The ICU tokenizer uses the [ICU library](http://site.icu-project.org/) to
|
||||||
@@ -90,10 +42,19 @@ On import the tokenizer processes names in the following three stages:
|
|||||||
See the [Token analysis](#token-analysis) section below for more
|
See the [Token analysis](#token-analysis) section below for more
|
||||||
information.
|
information.
|
||||||
|
|
||||||
During query time, only normalization and transliteration are relevant.
|
During query time, the tokeinzer is responsible for processing incoming
|
||||||
An incoming query is first split into name chunks (this usually means splitting
|
queries. This happens in two stages:
|
||||||
the string at the commas) and the each part is normalised and transliterated.
|
|
||||||
The result is used to look up places in the search index.
|
1. During **query preprocessing** the incoming text is split into name
|
||||||
|
chunks and normalised. This usually means applying the same normalisation
|
||||||
|
as during the import process but may involve other processing like,
|
||||||
|
for example, word break detection.
|
||||||
|
2. The **token analysis** step breaks down the query parts into tokens,
|
||||||
|
looks them up in the database and assigns them possible functions and
|
||||||
|
probabilities.
|
||||||
|
|
||||||
|
Query processing can be further customized while the rest of the analysis
|
||||||
|
is hard-coded.
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
@@ -105,6 +66,14 @@ have no effect.
|
|||||||
Here is an example configuration file:
|
Here is an example configuration file:
|
||||||
|
|
||||||
``` yaml
|
``` yaml
|
||||||
|
query-preprocessing:
|
||||||
|
- step: split_japanese_phrases
|
||||||
|
- step: regex_replace
|
||||||
|
replacements:
|
||||||
|
- pattern: https?://[^\s]* # Filter URLs starting with http or https
|
||||||
|
replace: ''
|
||||||
|
- step: normalize
|
||||||
|
|
||||||
normalization:
|
normalization:
|
||||||
- ":: lower ()"
|
- ":: lower ()"
|
||||||
- "ß > 'ss'" # German szet is unambiguously equal to double ss
|
- "ß > 'ss'" # German szet is unambiguously equal to double ss
|
||||||
@@ -125,8 +94,37 @@ token-analysis:
|
|||||||
replacements: ['ä', 'ae']
|
replacements: ['ä', 'ae']
|
||||||
```
|
```
|
||||||
|
|
||||||
The configuration file contains four sections:
|
The configuration file contains five sections:
|
||||||
`normalization`, `transliteration`, `sanitizers` and `token-analysis`.
|
`query-preprocessing`, `normalization`, `transliteration`, `sanitizers` and `token-analysis`.
|
||||||
|
|
||||||
|
#### Query preprocessing
|
||||||
|
|
||||||
|
The section for `query-preprocessing` defines an ordered list of functions
|
||||||
|
that are applied to the query before the token analysis.
|
||||||
|
|
||||||
|
The following is a list of preprocessors that are shipped with Nominatim.
|
||||||
|
|
||||||
|
##### normalize
|
||||||
|
|
||||||
|
::: nominatim_api.query_preprocessing.normalize
|
||||||
|
options:
|
||||||
|
members: False
|
||||||
|
heading_level: 6
|
||||||
|
docstring_section_style: spacy
|
||||||
|
|
||||||
|
##### regex-replace
|
||||||
|
|
||||||
|
::: nominatim_api.query_preprocessing.regex_replace
|
||||||
|
options:
|
||||||
|
members: False
|
||||||
|
heading_level: 6
|
||||||
|
docstring_section_style: spacy
|
||||||
|
description:
|
||||||
|
This option runs any given regex pattern on the input and replaces values accordingly
|
||||||
|
replacements:
|
||||||
|
- pattern: regex pattern
|
||||||
|
replace: string to replace with
|
||||||
|
|
||||||
|
|
||||||
#### Normalization and Transliteration
|
#### Normalization and Transliteration
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
### Import tables
|
### Import tables
|
||||||
|
|
||||||
OSM data is initially imported using [osm2pgsql](https://osm2pgsql.org).
|
OSM data is initially imported using [osm2pgsql](https://osm2pgsql.org).
|
||||||
Nominatim uses its own data output style 'gazetteer', which differs from the
|
Nominatim uses a custom flex style to create the initial import tables.
|
||||||
output style created for map rendering.
|
|
||||||
|
|
||||||
The import process creates the following tables:
|
The import process creates the following tables:
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ The `planet_osm_*` tables are the usual backing tables for OSM data. Note
|
|||||||
that Nominatim uses them to look up special relations and to find nodes on
|
that Nominatim uses them to look up special relations and to find nodes on
|
||||||
ways.
|
ways.
|
||||||
|
|
||||||
The gazetteer style produces a single table `place` as output with the following
|
The osm2pgsql import produces a single table `place` as output with the following
|
||||||
columns:
|
columns:
|
||||||
|
|
||||||
* `osm_type` - kind of OSM object (**N** - node, **W** - way, **R** - relation)
|
* `osm_type` - kind of OSM object (**N** - node, **W** - way, **R** - relation)
|
||||||
@@ -80,7 +79,7 @@ the placex table. Only three columns are special:
|
|||||||
Address interpolations are always ways in OSM, which is why there is no column
|
Address interpolations are always ways in OSM, which is why there is no column
|
||||||
`osm_type`.
|
`osm_type`.
|
||||||
|
|
||||||
The **location_postcode** table holds computed centroids of all postcodes that
|
The **location_postcodes** table holds computed centroids of all postcodes that
|
||||||
can be found in the OSM data. The meaning of the columns is again the same
|
can be found in the OSM data. The meaning of the columns is again the same
|
||||||
as that of the placex table.
|
as that of the placex table.
|
||||||
|
|
||||||
|
|||||||
@@ -25,18 +25,15 @@ following packages should get you started:
|
|||||||
|
|
||||||
## Prerequisites for testing and documentation
|
## Prerequisites for testing and documentation
|
||||||
|
|
||||||
The Nominatim test suite consists of behavioural tests (using behave) and
|
The Nominatim test suite consists of behavioural tests (using pytest-bdd) and
|
||||||
unit tests (using PHPUnit for PHP code and pytest for Python code).
|
unit tests (using pytest). It has the following additional requirements:
|
||||||
It has the following additional requirements:
|
|
||||||
|
|
||||||
* [behave test framework](https://behave.readthedocs.io) >= 1.2.6
|
* [flake8](https://flake8.pycqa.org/en/stable/) (CI always runs the latest version from pip)
|
||||||
* [phpunit](https://phpunit.de) (9.5 is known to work)
|
|
||||||
* [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer)
|
|
||||||
* [Pylint](https://pylint.org/) (CI always runs the latest version from pip)
|
|
||||||
* [mypy](http://mypy-lang.org/) (plus typing information for external libs)
|
* [mypy](http://mypy-lang.org/) (plus typing information for external libs)
|
||||||
* [Python Typing Extensions](https://github.com/python/typing_extensions) (for Python < 3.9)
|
* [Python Typing Extensions](https://github.com/python/typing_extensions) (for Python < 3.9)
|
||||||
* [pytest](https://pytest.org)
|
* [pytest](https://pytest.org)
|
||||||
* [pytest-asyncio](https://pytest-asyncio.readthedocs.io)
|
* [pytest-asyncio](https://pytest-asyncio.readthedocs.io)
|
||||||
|
* [pytest-bdd](https://pytest-bdd.readthedocs.io)
|
||||||
|
|
||||||
For testing the Python search frontend, you need to install extra dependencies
|
For testing the Python search frontend, you need to install extra dependencies
|
||||||
depending on your choice of webserver framework:
|
depending on your choice of webserver framework:
|
||||||
@@ -51,19 +48,17 @@ The documentation is built with mkdocs:
|
|||||||
* [mkdocs-material](https://squidfunk.github.io/mkdocs-material/)
|
* [mkdocs-material](https://squidfunk.github.io/mkdocs-material/)
|
||||||
* [mkdocs-gen-files](https://oprypin.github.io/mkdocs-gen-files/)
|
* [mkdocs-gen-files](https://oprypin.github.io/mkdocs-gen-files/)
|
||||||
|
|
||||||
Please be aware that tests always run against the globally installed
|
|
||||||
osm2pgsql, so you need to have this set up. If you want to test against
|
|
||||||
the vendored version of osm2pgsql, you need to set the PATH accordingly.
|
|
||||||
|
|
||||||
### Installing prerequisites on Ubuntu/Debian
|
### Installing prerequisites on Ubuntu/Debian
|
||||||
|
|
||||||
The Python tools should always be run with the most recent version.
|
The Python tools should always be run with the most recent version.
|
||||||
In particular, pylint tends to have a lot of breaking changes between versions.
|
|
||||||
The easiest way, to handle these Python dependencies is to run your
|
The easiest way, to handle these Python dependencies is to run your
|
||||||
development from within a virtual environment.
|
development from within a virtual environment.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt install libsqlite3-mod-spatialite php-cli
|
sudo apt install libsqlite3-mod-spatialite osm2pgsql \
|
||||||
|
postgresql-postgis postgresql-postgis-scripts \
|
||||||
|
pkg-config libicu-dev virtualenv
|
||||||
```
|
```
|
||||||
|
|
||||||
To set up the virtual environment with all necessary packages run:
|
To set up the virtual environment with all necessary packages run:
|
||||||
@@ -71,13 +66,14 @@ To set up the virtual environment with all necessary packages run:
|
|||||||
```sh
|
```sh
|
||||||
virtualenv ~/nominatim-dev-venv
|
virtualenv ~/nominatim-dev-venv
|
||||||
~/nominatim-dev-venv/bin/pip install\
|
~/nominatim-dev-venv/bin/pip install\
|
||||||
psutil psycopg[binary] PyICU SQLAlchemy \
|
psutil 'psycopg[binary]' PyICU SQLAlchemy \
|
||||||
python-dotenv jinja2 pyYAML datrie behave \
|
python-dotenv jinja2 pyYAML \
|
||||||
mkdocs mkdocstrings mkdocs-gen-files pytest pytest-asyncio pylint \
|
mkdocs 'mkdocstrings[python]' mkdocs-gen-files \
|
||||||
|
pytest pytest-asyncio pytest-bdd flake8 \
|
||||||
types-jinja2 types-markupsafe types-psutil types-psycopg2 \
|
types-jinja2 types-markupsafe types-psutil types-psycopg2 \
|
||||||
types-pygments types-pyyaml types-requests types-ujson \
|
types-pygments types-pyyaml types-requests types-ujson \
|
||||||
types-urllib3 typing-extensions unicorn falcon starlette \
|
types-urllib3 typing-extensions unicorn falcon starlette \
|
||||||
uvicorn mypy osmium aiosqlite
|
uvicorn mypy osmium aiosqlite mwparserfromhell
|
||||||
```
|
```
|
||||||
|
|
||||||
Now enter the virtual environment whenever you want to develop:
|
Now enter the virtual environment whenever you want to develop:
|
||||||
@@ -86,28 +82,6 @@ Now enter the virtual environment whenever you want to develop:
|
|||||||
. ~/nominatim-dev-venv/bin/activate
|
. ~/nominatim-dev-venv/bin/activate
|
||||||
```
|
```
|
||||||
|
|
||||||
For installing the PHP development tools, run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo apt install php-cgi phpunit php-codesniffer
|
|
||||||
```
|
|
||||||
|
|
||||||
If your distribution does not have PHPUnit 7.3+, you can install it (as well
|
|
||||||
as CodeSniffer) via composer:
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo apt-get install composer
|
|
||||||
composer global require "squizlabs/php_codesniffer=*"
|
|
||||||
composer global require "phpunit/phpunit=8.*"
|
|
||||||
```
|
|
||||||
|
|
||||||
The binaries are found in `.config/composer/vendor/bin`. You need to add this
|
|
||||||
to your PATH:
|
|
||||||
|
|
||||||
```
|
|
||||||
echo 'export PATH=~/.config/composer/vendor/bin:$PATH' > ~/.profile
|
|
||||||
```
|
|
||||||
|
|
||||||
### Running Nominatim during development
|
### Running Nominatim during development
|
||||||
|
|
||||||
The source code for Nominatim can be found in the `src` directory and can
|
The source code for Nominatim can be found in the `src` directory and can
|
||||||
@@ -118,7 +92,7 @@ but executes against the code in the source tree. For example:
|
|||||||
```
|
```
|
||||||
me@machine:~$ cd Nominatim
|
me@machine:~$ cd Nominatim
|
||||||
me@machine:~Nominatim$ ./nominatim-cli.py --version
|
me@machine:~Nominatim$ ./nominatim-cli.py --version
|
||||||
Nominatim version 4.4.99-1
|
Nominatim version 5.1.0-0
|
||||||
```
|
```
|
||||||
|
|
||||||
Make sure you have activated the virtual environment holding all
|
Make sure you have activated the virtual environment holding all
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ of sanitizers and token analysis.
|
|||||||
implemented, it is not guaranteed to be stable at the moment.
|
implemented, it is not guaranteed to be stable at the moment.
|
||||||
|
|
||||||
|
|
||||||
## Using non-standard sanitizers and token analyzers
|
## Using non-standard modules
|
||||||
|
|
||||||
Sanitizer names (in the `step` property) and token analysis names (in the
|
Sanitizer names (in the `step` property), token analysis names (in the
|
||||||
`analyzer`) may refer to externally supplied modules. There are two ways
|
`analyzer`) and query preprocessor names (in the `step` property)
|
||||||
|
may refer to externally supplied modules. There are two ways
|
||||||
to include external modules: through a library or from the project directory.
|
to include external modules: through a library or from the project directory.
|
||||||
|
|
||||||
To include a module from a library, use the absolute import path as name and
|
To include a module from a library, use the absolute import path as name and
|
||||||
@@ -27,6 +28,53 @@ To use a custom module without creating a library, you can put the module
|
|||||||
somewhere in your project directory and then use the relative path to the
|
somewhere in your project directory and then use the relative path to the
|
||||||
file. Include the whole name of the file including the `.py` ending.
|
file. Include the whole name of the file including the `.py` ending.
|
||||||
|
|
||||||
|
## Custom query preprocessors
|
||||||
|
|
||||||
|
A query preprocessor must export a single factory function `create` with
|
||||||
|
the following signature:
|
||||||
|
|
||||||
|
``` python
|
||||||
|
create(self, config: QueryConfig) -> Callable[[list[Phrase]], list[Phrase]]
|
||||||
|
```
|
||||||
|
|
||||||
|
The function receives the custom configuration for the preprocessor and
|
||||||
|
returns a callable (function or class) with the actual preprocessing
|
||||||
|
code. When a query comes in, then the callable gets a list of phrases
|
||||||
|
and needs to return the transformed list of phrases. The list and phrases
|
||||||
|
may be changed in place or a completely new list may be generated.
|
||||||
|
|
||||||
|
The `QueryConfig` is a simple dictionary which contains all configuration
|
||||||
|
options given in the yaml configuration of the ICU tokenizer. It is up to
|
||||||
|
the function to interpret the values.
|
||||||
|
|
||||||
|
A `nominatim_api.search.Phrase` describes a part of the query that contains one or more independent
|
||||||
|
search terms. Breaking a query into phrases helps reducing the number of
|
||||||
|
possible tokens Nominatim has to take into account. However a phrase break
|
||||||
|
is definitive: a multi-term search word cannot go over a phrase break.
|
||||||
|
A Phrase object has two fields:
|
||||||
|
|
||||||
|
* `ptype` further refines the type of phrase (see list below)
|
||||||
|
* `text` contains the query text for the phrase
|
||||||
|
|
||||||
|
The order of phrases matters to Nominatim when doing further processing.
|
||||||
|
Thus, while you may split or join phrases, you should not reorder them
|
||||||
|
unless you really know what you are doing.
|
||||||
|
|
||||||
|
Phrase types can further help narrowing down how the tokens in the phrase
|
||||||
|
are interpreted. The following phrase types are known:
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
|----------------|-------------|
|
||||||
|
| PHRASE_ANY | No specific designation (i.e. source is free-form query) |
|
||||||
|
| PHRASE_AMENITY | Contains name or type of a POI |
|
||||||
|
| PHRASE_STREET | Contains a street name optionally with a housenumber |
|
||||||
|
| PHRASE_CITY | Contains the postal city |
|
||||||
|
| PHRASE_COUNTY | Contains the equivalent of a county |
|
||||||
|
| PHRASE_STATE | Contains a state or province |
|
||||||
|
| PHRASE_POSTCODE| Contains a postal code |
|
||||||
|
| PHRASE_COUNTRY | Contains the country name or code |
|
||||||
|
|
||||||
|
|
||||||
## Custom sanitizer modules
|
## Custom sanitizer modules
|
||||||
|
|
||||||
A sanitizer module must export a single factory function `create` with the
|
A sanitizer module must export a single factory function `create` with the
|
||||||
@@ -90,21 +138,22 @@ adding extra attributes) or completely replace the list with a different one.
|
|||||||
The following sanitizer removes the directional prefixes from street names
|
The following sanitizer removes the directional prefixes from street names
|
||||||
in the US:
|
in the US:
|
||||||
|
|
||||||
``` python
|
!!! example
|
||||||
import re
|
``` python
|
||||||
|
import re
|
||||||
|
|
||||||
def _filter_function(obj):
|
def _filter_function(obj):
|
||||||
if obj.place.country_code == 'us' \
|
if obj.place.country_code == 'us' \
|
||||||
and obj.place.rank_address >= 26 and obj.place.rank_address <= 27:
|
and obj.place.rank_address >= 26 and obj.place.rank_address <= 27:
|
||||||
for name in obj.names:
|
for name in obj.names:
|
||||||
name.name = re.sub(r'^(north|south|west|east) ',
|
name.name = re.sub(r'^(north|south|west|east) ',
|
||||||
'',
|
'',
|
||||||
name.name,
|
name.name,
|
||||||
flags=re.IGNORECASE)
|
flags=re.IGNORECASE)
|
||||||
|
|
||||||
def create(config):
|
def create(config):
|
||||||
return _filter_function
|
return _filter_function
|
||||||
```
|
```
|
||||||
|
|
||||||
This is the most simple form of a sanitizer module. If defines a single
|
This is the most simple form of a sanitizer module. If defines a single
|
||||||
filter function and implements the required `create()` function by returning
|
filter function and implements the required `create()` function by returning
|
||||||
@@ -128,13 +177,13 @@ sanitizers:
|
|||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
This example is just a simplified show case on how to create a sanitizer.
|
This example is just a simplified show case on how to create a sanitizer.
|
||||||
It is not really read for real-world use: while the sanitizer would
|
It is not really meant for real-world use: while the sanitizer would
|
||||||
correctly transform `West 5th Street` into `5th Street`. it would also
|
correctly transform `West 5th Street` into `5th Street`. it would also
|
||||||
shorten a simple `North Street` to `Street`.
|
shorten a simple `North Street` to `Street`.
|
||||||
|
|
||||||
For more sanitizer examples, have a look at the sanitizers provided by Nominatim.
|
For more sanitizer examples, have a look at the sanitizers provided by Nominatim.
|
||||||
They can be found in the directory
|
They can be found in the directory
|
||||||
[`nominatim/tokenizer/sanitizers`](https://github.com/osm-search/Nominatim/tree/master/nominatim/tokenizer/sanitizers).
|
[`src/nominatim_db/tokenizer/sanitizers`](https://github.com/osm-search/Nominatim/tree/master/src/nominatim_db/tokenizer/sanitizers).
|
||||||
|
|
||||||
|
|
||||||
## Custom token analysis module
|
## Custom token analysis module
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ the tests, see the [Development setup chapter](Development-Environment.md).
|
|||||||
|
|
||||||
There are two kind of tests in this test suite. There are functional tests
|
There are two kind of tests in this test suite. There are functional tests
|
||||||
which test the API interface using a BDD test framework and there are unit
|
which test the API interface using a BDD test framework and there are unit
|
||||||
tests for specific PHP functions.
|
tests for the Python code.
|
||||||
|
|
||||||
This test directory is structured as follows:
|
This test directory is structured as follows:
|
||||||
|
|
||||||
@@ -20,28 +20,11 @@ This test directory is structured as follows:
|
|||||||
| +- db Tests for internal data processing on import and update
|
| +- db Tests for internal data processing on import and update
|
||||||
| +- api Tests for API endpoints (search, reverse, etc.)
|
| +- api Tests for API endpoints (search, reverse, etc.)
|
||||||
|
|
|
|
||||||
+- php PHP unit tests
|
|
||||||
+- python Python unit tests
|
+- python Python unit tests
|
||||||
+- testdb Base data for generating API test database
|
+- testdb Base data for generating API test database
|
||||||
+- testdata Additional test data used by unit tests
|
+- testdata Additional test data used by unit tests
|
||||||
```
|
```
|
||||||
|
|
||||||
## PHP Unit Tests (`test/php`)
|
|
||||||
|
|
||||||
Unit tests for PHP code can be found in the `php/` directory. They test selected
|
|
||||||
PHP functions. Very low coverage.
|
|
||||||
|
|
||||||
To execute the test suite run
|
|
||||||
|
|
||||||
cd test/php
|
|
||||||
UNIT_TEST_DSN='pgsql:dbname=nominatim_unit_tests' phpunit ../
|
|
||||||
|
|
||||||
It will read phpunit.xml which points to the library, test path, bootstrap
|
|
||||||
strip and sets other parameters.
|
|
||||||
|
|
||||||
It will use (and destroy) a local database 'nominatim_unit_tests'. You can set
|
|
||||||
a different connection string with e.g. UNIT_TEST_DSN='pgsql:dbname=foo_unit_tests'.
|
|
||||||
|
|
||||||
## Python Unit Tests (`test/python`)
|
## Python Unit Tests (`test/python`)
|
||||||
|
|
||||||
Unit tests for Python code can be found in the `python/` directory. The goal is
|
Unit tests for Python code can be found in the `python/` directory. The goal is
|
||||||
@@ -60,55 +43,62 @@ The name of the pytest binary depends on your installation.
|
|||||||
## BDD Functional Tests (`test/bdd`)
|
## BDD Functional Tests (`test/bdd`)
|
||||||
|
|
||||||
Functional tests are written as BDD instructions. For more information on
|
Functional tests are written as BDD instructions. For more information on
|
||||||
the philosophy of BDD testing, see the
|
the philosophy of BDD testing, read the Wikipedia article on
|
||||||
[Behave manual](http://pythonhosted.org/behave/philosophy.html).
|
[Behaviour-driven development](https://en.wikipedia.org/wiki/Behavior-driven_development).
|
||||||
|
|
||||||
The following explanation assume that the reader is familiar with the BDD
|
|
||||||
notations of features, scenarios and steps.
|
|
||||||
|
|
||||||
All possible steps can be found in the `steps` directory and should ideally
|
|
||||||
be documented.
|
|
||||||
|
|
||||||
### General Usage
|
### General Usage
|
||||||
|
|
||||||
To run the functional tests, do
|
To run the functional tests, do
|
||||||
|
|
||||||
cd test/bdd
|
pytest test/bdd
|
||||||
behave
|
|
||||||
|
|
||||||
The tests can be configured with a set of environment variables (`behave -D key=val`):
|
You can run a single feature file using expression matching:
|
||||||
|
|
||||||
* `TEMPLATE_DB` - name of template database used as a skeleton for
|
pytest test/bdd -k osm2pgsql/import/entrances.feature
|
||||||
the test databases (db tests)
|
|
||||||
* `TEST_DB` - name of test database (db tests)
|
This even works for running single tests by adding the line number of the
|
||||||
* `API_TEST_DB` - name of the database containing the API test data (api tests)
|
scenario header like that:
|
||||||
* `API_TEST_FILE` - OSM file to be imported into the API test database (api tests)
|
|
||||||
* `API_ENGINE` - webframe to use for running search queries, same values as
|
pytest test/bdd -k 'osm2pgsql/import/entrances.feature and L4'
|
||||||
`nominatim serve --engine` parameter
|
|
||||||
* `DB_HOST` - (optional) hostname of database host
|
The BDD tests create databases for the tests. You can set name of the databases
|
||||||
* `DB_PORT` - (optional) port of database on host
|
through configuration variables in your `pytest.ini`:
|
||||||
* `DB_USER` - (optional) username of database login
|
|
||||||
* `DB_PASS` - (optional) password for database login
|
* `nominatim_test_db` defines the name of the temporary database created for
|
||||||
* `SERVER_MODULE_PATH` - (optional) path on the Postgres server to Nominatim
|
a single test (default: `test_nominatim`)
|
||||||
module shared library file (only needed for legacy tokenizer)
|
* `nominatim_api_test_db` defines the name of the database containing
|
||||||
* `REMOVE_TEMPLATE` - if true, the template and API database will not be reused
|
the API test data, see also below (default: `test_api_nominatim`)
|
||||||
during the next run. Reusing the base templates speeds
|
* `nominatim_template_db` defines the name of the template database used
|
||||||
up tests considerably but might lead to outdated errors
|
for creating the temporary test databases. It contains some static setup
|
||||||
for some changes in the database layout.
|
which usually doesn't change between imports of OSM data
|
||||||
* `KEEP_TEST_DB` - if true, the test database will not be dropped after a test
|
(default: `test_template_nominatim`)
|
||||||
is finished. Should only be used if one single scenario is
|
|
||||||
run, otherwise the result is undefined.
|
To change other connection parameters for the PostgreSQL database, use
|
||||||
|
the [libpq enivronment variables](https://www.postgresql.org/docs/current/libpq-envars.html).
|
||||||
|
Never set a password through these variables. Use a
|
||||||
|
[password file](https://www.postgresql.org/docs/current/libpq-pgpass.html) instead.
|
||||||
|
|
||||||
|
The API test database and the template database are only created once and then
|
||||||
|
left untouched. This is usually what you want because it speeds up subsequent
|
||||||
|
runs of BDD tests. If you do change code that has an influence on the content
|
||||||
|
of these databases, you can run pytest with the `--nominatim-purge` parameter
|
||||||
|
and the databases will be dropped and recreated from scratch.
|
||||||
|
|
||||||
|
When running the BDD tests with make (using `make tests` or `make bdd`), then
|
||||||
|
the databases will always be purged.
|
||||||
|
|
||||||
|
The temporary test database is usually dropped directly after the test, so
|
||||||
|
it does not take up unnecessary space. If you want to keep the database around,
|
||||||
|
for example while debugging a specific BDD test, use the parameter
|
||||||
|
`--nominatim-keep-db`.
|
||||||
|
|
||||||
Logging can be defined through command line parameters of behave itself. Check
|
|
||||||
out `behave --help` for details. Also have a look at the 'work-in-progress'
|
|
||||||
feature of behave which comes in handy when writing new tests.
|
|
||||||
|
|
||||||
### API Tests (`test/bdd/api`)
|
### API Tests (`test/bdd/api`)
|
||||||
|
|
||||||
These tests are meant to test the different API endpoints and their parameters.
|
These tests are meant to test the different API endpoints and their parameters.
|
||||||
They require to import several datasets into a test database. This is normally
|
They require to import several datasets into a test database. This is normally
|
||||||
done automatically during setup of the test. The API test database is then
|
done automatically during setup of the test. The API test database is then
|
||||||
kept around and reused in subsequent runs of behave. Use `behave -DREMOVE_TEMPLATE`
|
kept around and reused in subsequent runs of behave. Use `--nominatim-purge`
|
||||||
to force a reimport of the database.
|
to force a reimport of the database.
|
||||||
|
|
||||||
The official test dataset is saved in the file `test/testdb/apidb-test-data.pbf`
|
The official test dataset is saved in the file `test/testdb/apidb-test-data.pbf`
|
||||||
@@ -118,7 +108,7 @@ and compromises the following data:
|
|||||||
* extract of Autauga country, Alabama, US (for tests against Tiger data)
|
* extract of Autauga country, Alabama, US (for tests against Tiger data)
|
||||||
* additional data from `test/testdb/additional_api_test.data.osm`
|
* additional data from `test/testdb/additional_api_test.data.osm`
|
||||||
|
|
||||||
API tests should only be testing the functionality of the website PHP code.
|
API tests should only be testing the functionality of the website frontend code.
|
||||||
Most tests should be formulated as BDD DB creation tests (see below) instead.
|
Most tests should be formulated as BDD DB creation tests (see below) instead.
|
||||||
|
|
||||||
### DB Creation Tests (`test/bdd/db`)
|
### DB Creation Tests (`test/bdd/db`)
|
||||||
@@ -128,12 +118,12 @@ test the correctness of osm2pgsql. Each test will write some data into the `plac
|
|||||||
table (and optionally the `planet_osm_*` tables if required) and then run
|
table (and optionally the `planet_osm_*` tables if required) and then run
|
||||||
Nominatim's processing functions on that.
|
Nominatim's processing functions on that.
|
||||||
|
|
||||||
These tests need to create their own test databases. By default they will be
|
These tests use the template database and create temporary test databases for
|
||||||
called `test_template_nominatim` and `test_nominatim`. Names can be changed with
|
each test.
|
||||||
the environment variables `TEMPLATE_DB` and `TEST_DB`. The user running the tests
|
|
||||||
needs superuser rights for postgres.
|
|
||||||
|
|
||||||
### Import Tests (`test/bdd/osm2pgsql`)
|
### Import Tests (`test/bdd/osm2pgsql`)
|
||||||
|
|
||||||
These tests check that data is imported correctly into the place table. They
|
These tests check that data is imported correctly into the place table.
|
||||||
use the same template database as the DB Creation tests, so the same remarks apply.
|
|
||||||
|
These tests also use the template database and create temporary test databases
|
||||||
|
for each test.
|
||||||
|
|||||||
@@ -91,19 +91,19 @@ for a custom tokenizer implementation.
|
|||||||
|
|
||||||
### Directory Structure
|
### Directory Structure
|
||||||
|
|
||||||
Nominatim expects two files for a tokenizer:
|
Nominatim expects two files containing the Python part of the implementation:
|
||||||
|
|
||||||
* `nominatim/tokenizer/<NAME>_tokenizer.py` containing the Python part of the
|
* `src/nominatim_db/tokenizer/<NAME>_tokenizer.py` contains the tokenizer
|
||||||
implementation
|
code used during import and
|
||||||
* `lib-php/tokenizer/<NAME>_tokenizer.php` with the PHP part of the
|
* `src/nominatim_api/search/<NAME>_tokenizer.py` has the code used during
|
||||||
implementation
|
query time.
|
||||||
|
|
||||||
where `<NAME>` is a unique name for the tokenizer consisting of only lower-case
|
`<NAME>` is a unique name for the tokenizer consisting of only lower-case
|
||||||
letters, digits and underscore. A tokenizer also needs to install some SQL
|
letters, digits and underscore. A tokenizer also needs to install some SQL
|
||||||
functions. By convention, these should be placed in `lib-sql/tokenizer`.
|
functions. By convention, these should be placed in `lib-sql/tokenizer`.
|
||||||
|
|
||||||
If the tokenizer has a default configuration file, this should be saved in
|
If the tokenizer has a default configuration file, this should be saved in
|
||||||
the `settings/<NAME>_tokenizer.<SUFFIX>`.
|
`settings/<NAME>_tokenizer.<SUFFIX>`.
|
||||||
|
|
||||||
### Configuration and Persistence
|
### Configuration and Persistence
|
||||||
|
|
||||||
@@ -115,9 +115,11 @@ are tied to a database installation and must only be read during installation
|
|||||||
time. If they are needed for the runtime then they must be saved into the
|
time. If they are needed for the runtime then they must be saved into the
|
||||||
`nominatim_properties` table and later loaded from there.
|
`nominatim_properties` table and later loaded from there.
|
||||||
|
|
||||||
### The Python module
|
### The Python modules
|
||||||
|
|
||||||
The Python module is expect to export a single factory function:
|
#### `src/nominatim_db/tokenizer/`
|
||||||
|
|
||||||
|
The import Python module is expected to export a single factory function:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def create(dsn: str, data_dir: Path) -> AbstractTokenizer
|
def create(dsn: str, data_dir: Path) -> AbstractTokenizer
|
||||||
@@ -128,6 +130,20 @@ is a directory in the project directory that the tokenizer may use to save
|
|||||||
database-specific data. The function must return the instance of the tokenizer
|
database-specific data. The function must return the instance of the tokenizer
|
||||||
class as defined below.
|
class as defined below.
|
||||||
|
|
||||||
|
#### `src/nominatim_api/search/`
|
||||||
|
|
||||||
|
The query-time Python module must also export a factory function:
|
||||||
|
|
||||||
|
``` python
|
||||||
|
def create_query_analyzer(conn: SearchConnection) -> AbstractQueryAnalyzer
|
||||||
|
```
|
||||||
|
|
||||||
|
The `conn` parameter contains the current search connection. See the
|
||||||
|
[library documentation](../library/Low-Level-DB-Access.md#searchconnection-class)
|
||||||
|
for details on the class. The function must return the instance of the tokenizer
|
||||||
|
class as defined below.
|
||||||
|
|
||||||
|
|
||||||
### Python Tokenizer Class
|
### Python Tokenizer Class
|
||||||
|
|
||||||
All tokenizers must inherit from `nominatim_db.tokenizer.base.AbstractTokenizer`
|
All tokenizers must inherit from `nominatim_db.tokenizer.base.AbstractTokenizer`
|
||||||
@@ -143,6 +159,13 @@ and implement the abstract functions defined there.
|
|||||||
options:
|
options:
|
||||||
heading_level: 6
|
heading_level: 6
|
||||||
|
|
||||||
|
|
||||||
|
### Python Query Analyzer Class
|
||||||
|
|
||||||
|
::: nominatim_api.search.query_analyzer_factory.AbstractQueryAnalyzer
|
||||||
|
options:
|
||||||
|
heading_level: 6
|
||||||
|
|
||||||
### PL/pgSQL Functions
|
### PL/pgSQL Functions
|
||||||
|
|
||||||
The tokenizer must provide access functions for the `token_info` column
|
The tokenizer must provide access functions for the `token_info` column
|
||||||
@@ -282,73 +305,3 @@ permanently. The indexer calls this function when all processing is done and
|
|||||||
replaces the content of the `token_info` column with the returned value before
|
replaces the content of the `token_info` column with the returned value before
|
||||||
the trigger stores the information in the database. May return NULL if no
|
the trigger stores the information in the database. May return NULL if no
|
||||||
information should be stored permanently.
|
information should be stored permanently.
|
||||||
|
|
||||||
### PHP Tokenizer class
|
|
||||||
|
|
||||||
The PHP tokenizer class is instantiated once per request and responsible for
|
|
||||||
analyzing the incoming query. Multiple requests may be in flight in
|
|
||||||
parallel.
|
|
||||||
|
|
||||||
The class is expected to be found under the
|
|
||||||
name of `\Nominatim\Tokenizer`. To find the class the PHP code includes the file
|
|
||||||
`tokenizer/tokenizer.php` in the project directory. This file must be created
|
|
||||||
when the tokenizer is first set up on import. The file should initialize any
|
|
||||||
configuration variables by setting PHP constants and then require the file
|
|
||||||
with the actual implementation of the tokenizer.
|
|
||||||
|
|
||||||
The tokenizer class must implement the following functions:
|
|
||||||
|
|
||||||
```php
|
|
||||||
public function __construct(object &$oDB)
|
|
||||||
```
|
|
||||||
|
|
||||||
The constructor of the class receives a database connection that can be used
|
|
||||||
to query persistent data in the database.
|
|
||||||
|
|
||||||
```php
|
|
||||||
public function checkStatus()
|
|
||||||
```
|
|
||||||
|
|
||||||
Check that the tokenizer can access its persistent data structures. If there
|
|
||||||
is an issue, throw an `\Exception`.
|
|
||||||
|
|
||||||
```php
|
|
||||||
public function normalizeString(string $sTerm) : string
|
|
||||||
```
|
|
||||||
|
|
||||||
Normalize string to a form to be used for comparisons when reordering results.
|
|
||||||
Nominatim reweighs results how well the final display string matches the actual
|
|
||||||
query. Before comparing result and query, names and query are normalised against
|
|
||||||
this function. The tokenizer can thus remove all properties that should not be
|
|
||||||
taken into account for reweighing, e.g. special characters or case.
|
|
||||||
|
|
||||||
```php
|
|
||||||
public function tokensForSpecialTerm(string $sTerm) : array
|
|
||||||
```
|
|
||||||
|
|
||||||
Return the list of special term tokens that match the given term.
|
|
||||||
|
|
||||||
```php
|
|
||||||
public function extractTokensFromPhrases(array &$aPhrases) : TokenList
|
|
||||||
```
|
|
||||||
|
|
||||||
Parse the given phrases, splitting them into word lists and retrieve the
|
|
||||||
matching tokens.
|
|
||||||
|
|
||||||
The phrase array may take on two forms. In unstructured searches (using `q=`
|
|
||||||
parameter) the search query is split at the commas and the elements are
|
|
||||||
put into a sorted list. For structured searches the phrase array is an
|
|
||||||
associative array where the key designates the type of the term (street, city,
|
|
||||||
county etc.) The tokenizer may ignore the phrase type at this stage in parsing.
|
|
||||||
Matching phrase type and appropriate search token type will be done later
|
|
||||||
when the SearchDescription is built.
|
|
||||||
|
|
||||||
For each phrase in the list of phrases, the function must analyse the phrase
|
|
||||||
string and then call `setWordSets()` to communicate the result of the analysis.
|
|
||||||
A word set is a list of strings, where each string refers to a search token.
|
|
||||||
A phrase may have multiple interpretations. Therefore a list of word sets is
|
|
||||||
usually attached to the phrase. The search tokens themselves are returned
|
|
||||||
by the function in an associative array, where the key corresponds to the
|
|
||||||
strings given in the word sets. The value is a list of search tokens. Thus
|
|
||||||
a single string in the list of word sets may refer to multiple search tokens.
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ the address computation and the search frontend.
|
|||||||
The __data import__ stage reads the raw OSM data and extracts all information
|
The __data import__ stage reads the raw OSM data and extracts all information
|
||||||
that is useful for geocoding. This part is done by osm2pgsql, the same tool
|
that is useful for geocoding. This part is done by osm2pgsql, the same tool
|
||||||
that can also be used to import a rendering database. It uses the special
|
that can also be used to import a rendering database. It uses the special
|
||||||
gazetteer output plugin in `osm2pgsql/src/output-gazetter.[ch]pp`. The result of
|
flex output style defined in the directory `/lib-lua`. The result of
|
||||||
the import can be found in the database table `place`.
|
the import can be found in the database table `place`.
|
||||||
|
|
||||||
The __address computation__ or __indexing__ stage takes the data from `place`
|
The __address computation__ or __indexing__ stage takes the data from `place`
|
||||||
@@ -20,5 +20,5 @@ and can be found in the files in the `sql/functions/` directory.
|
|||||||
|
|
||||||
The __search frontend__ implements the actual API. It takes search
|
The __search frontend__ implements the actual API. It takes search
|
||||||
and reverse geocoding queries from the user, looks up the data and
|
and reverse geocoding queries from the user, looks up the data and
|
||||||
returns the results in the requested format. This part is written in PHP
|
returns the results in the requested format. This part is located in the
|
||||||
and can be found in the `lib/` and `website/` directories.
|
`nominatim-api` package. The source code can be found in `src/nominatim_api`.
|
||||||
|
|||||||
@@ -74,15 +74,16 @@ map place_addressline {
|
|||||||
isaddress => BOOLEAN
|
isaddress => BOOLEAN
|
||||||
}
|
}
|
||||||
|
|
||||||
map location_postcode {
|
map location_postcodes {
|
||||||
place_id => BIGINT
|
place_id => BIGINT
|
||||||
|
osm_id => BIGINT
|
||||||
postcode => TEXT
|
postcode => TEXT
|
||||||
parent_place_id => BIGINT
|
parent_place_id => BIGINT
|
||||||
rank_search => SMALLINT
|
rank_search => SMALLINT
|
||||||
rank_address => SMALLINT
|
|
||||||
indexed_status => SMALLINT
|
indexed_status => SMALLINT
|
||||||
indexed_date => TIMESTAMP
|
indexed_date => TIMESTAMP
|
||||||
geometry => GEOMETRY
|
geometry => GEOMETRY
|
||||||
|
centroid -> GEOMETRY
|
||||||
}
|
}
|
||||||
|
|
||||||
placex::place_id <-- search_name::place_id
|
placex::place_id <-- search_name::place_id
|
||||||
@@ -94,6 +95,6 @@ search_name::nameaddress_vector --> word::word_id
|
|||||||
|
|
||||||
place_addressline -[hidden]> location_property_osmline
|
place_addressline -[hidden]> location_property_osmline
|
||||||
search_name -[hidden]> place_addressline
|
search_name -[hidden]> place_addressline
|
||||||
location_property_osmline -[hidden]-> location_postcode
|
location_property_osmline -[hidden]-> location_postcodes
|
||||||
|
|
||||||
@enduml
|
@enduml
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
display: none!important
|
display: none!important
|
||||||
}
|
}
|
||||||
|
|
||||||
.wy-nav-content {
|
.md-content {
|
||||||
max-width: 900px!important
|
max-width: 800px
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
@@ -39,3 +39,9 @@ th {
|
|||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.simple-table table:not([class]) th,
|
||||||
|
.simple-table table:not([class]) td {
|
||||||
|
padding: 2px 4px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|||||||
@@ -248,19 +248,19 @@ of the result. To do that, you first need to decide in which language the
|
|||||||
results should be presented. As with the names in the result itself, the
|
results should be presented. As with the names in the result itself, the
|
||||||
places in `address_rows` contain all possible name translation for each row.
|
places in `address_rows` contain all possible name translation for each row.
|
||||||
|
|
||||||
The library has a helper class `Locale` which helps extracting a name of a
|
The library has a helper class `Locales` which helps extracting a name of a
|
||||||
place in the preferred language. It takes a single parameter with a list
|
place in the preferred language. It takes a single parameter with a list
|
||||||
of language codes in the order of preference. So
|
of language codes in the order of preference. So
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
locale = napi.Locale(['fr', 'en'])
|
locale = napi.Locales(['fr', 'en'])
|
||||||
```
|
```
|
||||||
|
|
||||||
creates a helper class that returns the name preferably in French. If that is
|
creates a helper class that returns the name preferably in French. If that is
|
||||||
not possible, it tries English and eventually falls back to the default `name`
|
not possible, it tries English and eventually falls back to the default `name`
|
||||||
or `ref`.
|
or `ref`.
|
||||||
|
|
||||||
The `Locale` object can be applied to a name dictionary to return the best-matching
|
The `Locales` object can be applied to a name dictionary to return the best-matching
|
||||||
name out of it:
|
name out of it:
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
@@ -268,13 +268,17 @@ name out of it:
|
|||||||
'Brugges'
|
'Brugges'
|
||||||
```
|
```
|
||||||
|
|
||||||
The `address_row` field has a helper function to apply the function to all
|
The `address_row` field has a helper function to compute the display name for each Address Line
|
||||||
its members and save the result in the `local_name` field. It also returns
|
component based on its `local_name` field. This is then utilized by the overall `result` object,
|
||||||
all the localized names as a convenient simple list. This list can be used
|
which has a helper function to apply the function to all its ‘address_row’ members and saves
|
||||||
to create a human-readable output:
|
the result in the `locale_name` field.
|
||||||
|
|
||||||
|
However, in order to set this `local_name` field in a preferred language, you must use the `Locales`
|
||||||
|
object which contains the function `localize_results`, which explicitly sets each `local_name field`.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
>>> address_parts = results[0].address_rows.localize(locale)
|
>>> Locales().localize_results(results)
|
||||||
|
>>> address_parts = results[0].address_rows
|
||||||
>>> print(', '.join(address_parts))
|
>>> print(', '.join(address_parts))
|
||||||
Bruges, Flandre-Occidentale, Flandre, Belgique
|
Bruges, Flandre-Occidentale, Flandre, Belgique
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -49,7 +49,11 @@ its address.
|
|||||||
|
|
||||||
## Localization
|
## Localization
|
||||||
|
|
||||||
Results are always returned with the full list of available names.
|
Results are always returned with the full list of available names. However, the
|
||||||
|
default `locale_name` must be explicitly set using the `localize` function within
|
||||||
|
`Locales`. This parses through the full list of available names to find the one
|
||||||
|
most preferred by the user. Once this is set, the user can simply use the
|
||||||
|
`display_name` field within a `Result` object to retrive the localized name.
|
||||||
|
|
||||||
### Locale
|
### Locale
|
||||||
|
|
||||||
|
|||||||
14
lib-lua/flex-base.lua
Normal file
14
lib-lua/flex-base.lua
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-- This is just an alias for the Nominatim themepark theme module
|
||||||
|
local flex = require('themes/nominatim/init')
|
||||||
|
|
||||||
|
function flex.load_topic(name, cfg)
|
||||||
|
local topic_file = debug.getinfo(1, "S").source:sub(2):match("(.*/)") .. 'themes/nominatim/topics/'.. name .. '.lua'
|
||||||
|
|
||||||
|
if topic_file == nil then
|
||||||
|
error('Cannot find topic: ' .. name)
|
||||||
|
end
|
||||||
|
|
||||||
|
loadfile(topic_file)(nil, flex, cfg or {})
|
||||||
|
end
|
||||||
|
|
||||||
|
return flex
|
||||||
6
lib-lua/import-address.lua
Normal file
6
lib-lua/import-address.lua
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
-- This is just an alias for the Nominatim themepark address topic
|
||||||
|
local flex = require('flex-base')
|
||||||
|
|
||||||
|
flex.load_topic('address')
|
||||||
|
|
||||||
|
return flex
|
||||||
6
lib-lua/import-admin.lua
Normal file
6
lib-lua/import-admin.lua
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
-- This is just an alias for the Nominatim themepark admin topic
|
||||||
|
local flex = require('flex-base')
|
||||||
|
|
||||||
|
flex.load_topic('admin')
|
||||||
|
|
||||||
|
return flex
|
||||||
6
lib-lua/import-extratags.lua
Normal file
6
lib-lua/import-extratags.lua
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
-- This is just an alias for the Nominatim themepark full topic
|
||||||
|
local flex = require('flex-base')
|
||||||
|
|
||||||
|
flex.load_topic('full', {with_extratags = true})
|
||||||
|
|
||||||
|
return flex
|
||||||
6
lib-lua/import-full.lua
Normal file
6
lib-lua/import-full.lua
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
-- This is just an alias for the Nominatim themepark full topic
|
||||||
|
local flex = require('flex-base')
|
||||||
|
|
||||||
|
flex.load_topic('full')
|
||||||
|
|
||||||
|
return flex
|
||||||
6
lib-lua/import-street.lua
Normal file
6
lib-lua/import-street.lua
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
-- This is just an alias for the Nominatim themepark street topic
|
||||||
|
local flex = require('flex-base')
|
||||||
|
|
||||||
|
flex.load_topic('street')
|
||||||
|
|
||||||
|
return flex
|
||||||
118
lib-lua/taginfo.lua
Normal file
118
lib-lua/taginfo.lua
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
-- Prints taginfo project description in the standard output
|
||||||
|
--
|
||||||
|
|
||||||
|
-- create fake "osm2pgsql" table for flex-base, originally created by the main C++ program
|
||||||
|
osm2pgsql = {}
|
||||||
|
function osm2pgsql.define_table(...) end
|
||||||
|
|
||||||
|
-- provide path to flex-style lua file
|
||||||
|
package.path = arg[0]:match("(.*/)") .. "?.lua;" .. package.path
|
||||||
|
local flex = require('import-' .. (arg[1] or 'extratags'))
|
||||||
|
local json = require ('dkjson')
|
||||||
|
|
||||||
|
local NAME_DESCRIPTIONS = {
|
||||||
|
'Searchable auxiliary name of the place',
|
||||||
|
main = 'Searchable primary name of the place',
|
||||||
|
house = 'House name part of an address, searchable'
|
||||||
|
}
|
||||||
|
local ADDRESS_DESCRIPTIONS = {
|
||||||
|
'Used to determine the address of a place',
|
||||||
|
main = 'Primary key for an address point',
|
||||||
|
postcode = 'Used to determine the postcode of a place',
|
||||||
|
country = 'Used to determine country of a place (only if written as two-letter code)',
|
||||||
|
interpolation = 'Primary key for an address interpolation line'
|
||||||
|
}
|
||||||
|
|
||||||
|
------------ helper functions ---------------------
|
||||||
|
-- Sets the key order for the resulting JSON table
|
||||||
|
local function set_keyorder(table, order)
|
||||||
|
setmetatable(table, {
|
||||||
|
__jsonorder = order
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_key_description(key, description)
|
||||||
|
local desc = {}
|
||||||
|
desc.key = key
|
||||||
|
desc.description = description
|
||||||
|
set_keyorder(desc, {'key', 'description'})
|
||||||
|
return desc
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_key_value_description(key, value, description)
|
||||||
|
local desc = {key = key, value = value, description = description}
|
||||||
|
set_keyorder(desc, {'key', 'value', 'description'})
|
||||||
|
return desc
|
||||||
|
end
|
||||||
|
|
||||||
|
local function group_table_to_keys(tags, data, descriptions)
|
||||||
|
for group, values in pairs(data) do
|
||||||
|
local desc = descriptions[group] or descriptions[1]
|
||||||
|
for _, key in pairs(values) do
|
||||||
|
if key:sub(1, 1) ~= '*' and key:sub(#key, #key) ~= '*' then
|
||||||
|
table.insert(tags, get_key_description(key, desc))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Prints the collected tags in the required format in JSON
|
||||||
|
local function print_taginfo()
|
||||||
|
local taginfo = flex.get_taginfo()
|
||||||
|
local tags = {}
|
||||||
|
|
||||||
|
for k, values in pairs(taginfo.main) do
|
||||||
|
if values[1] == nil or values[1] == 'delete' or values[1] == 'extra' then
|
||||||
|
for v, group in pairs(values) do
|
||||||
|
if type(v) == 'string' and group ~= 'delete' and group ~= 'extra' then
|
||||||
|
local text = 'POI/feature in the search database'
|
||||||
|
if type(group) ~= 'function' then
|
||||||
|
text = 'Fallback ' .. text
|
||||||
|
end
|
||||||
|
table.insert(tags, get_key_value_description(k, v, text))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif type(values[1]) == 'function' or values[1] == 'fallback' then
|
||||||
|
local desc = 'POI/feature in the search database'
|
||||||
|
if values[1] == 'fallback' then
|
||||||
|
desc = 'Fallback ' .. desc
|
||||||
|
end
|
||||||
|
local excp = {}
|
||||||
|
for v, group in pairs(values) do
|
||||||
|
if group == 'delete' or group == 'extra' then
|
||||||
|
table.insert(excp, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if next(excp) ~= nil then
|
||||||
|
desc = desc .. string.format(' (except for values: %s)',
|
||||||
|
table.concat(excp, ', '))
|
||||||
|
end
|
||||||
|
table.insert(tags, get_key_description(k, desc))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
group_table_to_keys(tags, taginfo.name, NAME_DESCRIPTIONS)
|
||||||
|
group_table_to_keys(tags, taginfo.address, ADDRESS_DESCRIPTIONS)
|
||||||
|
|
||||||
|
local format = {
|
||||||
|
data_format = 1,
|
||||||
|
data_url = 'https://nominatim.openstreetmap.org/taginfo.json',
|
||||||
|
project = {
|
||||||
|
name = 'Nominatim',
|
||||||
|
description = 'OSM search engine.',
|
||||||
|
project_url = 'https://nominatim.openstreetmap.org',
|
||||||
|
doc_url = 'https://nominatim.org/release-docs/develop/',
|
||||||
|
contact_name = 'Sarah Hoffmann',
|
||||||
|
contact_email = 'lonvia@denofr.de'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format.tags = tags
|
||||||
|
|
||||||
|
set_keyorder(format, {'data_format', 'data_url', 'project', 'tags'})
|
||||||
|
set_keyorder(format.project, {'name', 'description', 'project_url', 'doc_url',
|
||||||
|
'contact_name', 'contact_email'})
|
||||||
|
|
||||||
|
print(json.encode(format))
|
||||||
|
end
|
||||||
|
|
||||||
|
print_taginfo()
|
||||||
1087
lib-lua/themes/nominatim/init.lua
Normal file
1087
lib-lua/themes/nominatim/init.lua
Normal file
File diff suppressed because it is too large
Load Diff
389
lib-lua/themes/nominatim/presets.lua
Normal file
389
lib-lua/themes/nominatim/presets.lua
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
-- Defines defaults used in the topic definitions.
|
||||||
|
|
||||||
|
local module = {}
|
||||||
|
|
||||||
|
-- Helper functions
|
||||||
|
|
||||||
|
local function group_merge(group1, group2)
|
||||||
|
for name, values in pairs(group2) do
|
||||||
|
if group1[name] == nil then
|
||||||
|
group1[name] = values
|
||||||
|
else
|
||||||
|
for _, v in pairs(values) do
|
||||||
|
table.insert(group1[name], v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return group1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Customized main tag filter functions
|
||||||
|
|
||||||
|
local EXCLUDED_FOOTWAYS = { sidewalk = 1, crossing = 1, link = 1, traffic_aisle }
|
||||||
|
|
||||||
|
local function filter_footways(place)
|
||||||
|
if place.has_name then
|
||||||
|
local footway = place.object.tags.footway
|
||||||
|
if footway == nil or EXCLUDED_FOOTWAYS[footway] ~= 1 then
|
||||||
|
return place
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function include_when_tag_present(key, value, named)
|
||||||
|
if named then
|
||||||
|
return function(place)
|
||||||
|
if place.has_name and place.intags[key] == value then
|
||||||
|
return place
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return function(place)
|
||||||
|
if place.intags[key] == value then
|
||||||
|
return place
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function exclude_when_key_present(key, named)
|
||||||
|
if named then
|
||||||
|
return function(place)
|
||||||
|
if place.has_name and place.intags[key] == nil then
|
||||||
|
return place
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return function(place)
|
||||||
|
if place.intags[key] == nil then
|
||||||
|
return place
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function lock_transform(place)
|
||||||
|
if place.object.tags.waterway ~= nil then
|
||||||
|
local name = place.object.tags.lock_name
|
||||||
|
if name ~= nil then
|
||||||
|
return place:clone{names={name=name, ref=place.object.tags.lock_ref}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Main tag definition
|
||||||
|
|
||||||
|
module.MAIN_TAGS = {}
|
||||||
|
|
||||||
|
module.MAIN_TAGS.admin = {
|
||||||
|
boundary = {administrative = 'named'},
|
||||||
|
landuse = {residential = 'fallback',
|
||||||
|
farm = 'fallback',
|
||||||
|
farmyard = 'fallback',
|
||||||
|
industrial = 'fallback',
|
||||||
|
commercial = 'fallback',
|
||||||
|
allotments = 'fallback',
|
||||||
|
retail = 'fallback'},
|
||||||
|
place = {county = 'always',
|
||||||
|
district = 'always',
|
||||||
|
municipality = 'always',
|
||||||
|
city = 'always',
|
||||||
|
town = 'always',
|
||||||
|
borough = 'always',
|
||||||
|
village = 'always',
|
||||||
|
suburb = 'always',
|
||||||
|
hamlet = 'always',
|
||||||
|
croft = 'always',
|
||||||
|
subdivision = 'always',
|
||||||
|
allotments = 'always',
|
||||||
|
neighbourhood = 'always',
|
||||||
|
quarter = 'always',
|
||||||
|
isolated_dwelling = 'always',
|
||||||
|
farm = 'always',
|
||||||
|
city_block = 'always',
|
||||||
|
locality = 'always'}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.MAIN_TAGS.all_boundaries = {
|
||||||
|
boundary = {'named',
|
||||||
|
place = 'delete',
|
||||||
|
land_area = 'delete',
|
||||||
|
protected_area = 'fallback',
|
||||||
|
postal_code = 'postcode_area'},
|
||||||
|
landuse = 'fallback',
|
||||||
|
place = 'always'
|
||||||
|
}
|
||||||
|
|
||||||
|
module.MAIN_TAGS.natural = {
|
||||||
|
waterway = {'named',
|
||||||
|
riverbank = 'delete'},
|
||||||
|
natural = {'named',
|
||||||
|
yes = 'delete',
|
||||||
|
no = 'delete',
|
||||||
|
coastline = 'delete',
|
||||||
|
saddle = 'fallback',
|
||||||
|
water = exclude_when_key_present('water', true)},
|
||||||
|
mountain_pass = {'always',
|
||||||
|
no = 'delete'},
|
||||||
|
water = {include_when_tag_present('natural', 'water', true),
|
||||||
|
river = 'never',
|
||||||
|
stream = 'never',
|
||||||
|
canal = 'never',
|
||||||
|
ditch = 'never',
|
||||||
|
drain = 'never',
|
||||||
|
fish_pass = 'never',
|
||||||
|
yes = 'delete',
|
||||||
|
intermittent = 'delete',
|
||||||
|
tidal = 'delete'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.MAIN_TAGS_POIS = function (group)
|
||||||
|
group = group or 'delete'
|
||||||
|
return {
|
||||||
|
aerialway = {'always',
|
||||||
|
no = group,
|
||||||
|
pylon = group},
|
||||||
|
aeroway = {'always',
|
||||||
|
no = group},
|
||||||
|
amenity = {'always',
|
||||||
|
no = group,
|
||||||
|
parking_space = group,
|
||||||
|
parking_entrance = group,
|
||||||
|
waste_disposal = group,
|
||||||
|
hunting_stand = group},
|
||||||
|
building = {'fallback',
|
||||||
|
no = group},
|
||||||
|
bridge = {'named_with_key',
|
||||||
|
no = group},
|
||||||
|
club = {'always',
|
||||||
|
no = group},
|
||||||
|
craft = {'always',
|
||||||
|
no = group},
|
||||||
|
emergency = {'always',
|
||||||
|
no = group,
|
||||||
|
yes = group,
|
||||||
|
fire_hydrant = group},
|
||||||
|
healthcare = {'fallback',
|
||||||
|
yes = group,
|
||||||
|
no = group},
|
||||||
|
highway = {'always',
|
||||||
|
no = group,
|
||||||
|
turning_circle = group,
|
||||||
|
mini_roundabout = group,
|
||||||
|
noexit = group,
|
||||||
|
crossing = group,
|
||||||
|
give_way = group,
|
||||||
|
stop = group,
|
||||||
|
turning_loop = group,
|
||||||
|
passing_place = group,
|
||||||
|
street_lamp = 'named',
|
||||||
|
traffic_signals = 'named'},
|
||||||
|
historic = {'fallback',
|
||||||
|
yes = group,
|
||||||
|
no = group},
|
||||||
|
information = {include_when_tag_present('tourism', 'information'),
|
||||||
|
yes = 'delete',
|
||||||
|
route_marker = 'never',
|
||||||
|
trail_blaze = 'never'},
|
||||||
|
junction = {'fallback',
|
||||||
|
no = group},
|
||||||
|
landuse = {cemetery = 'always'},
|
||||||
|
leisure = {'always',
|
||||||
|
nature_reserve = 'named',
|
||||||
|
swimming_pool = 'named',
|
||||||
|
garden = 'named',
|
||||||
|
common = 'named',
|
||||||
|
no = group},
|
||||||
|
lock = {yes = lock_transform},
|
||||||
|
man_made = {pier = 'always',
|
||||||
|
tower = 'always',
|
||||||
|
bridge = 'always',
|
||||||
|
works = 'named',
|
||||||
|
water_tower = 'always',
|
||||||
|
dyke = 'named',
|
||||||
|
adit = 'named',
|
||||||
|
lighthouse = 'always',
|
||||||
|
watermill = 'always',
|
||||||
|
tunnel = 'always'},
|
||||||
|
military = {'always',
|
||||||
|
yes = group,
|
||||||
|
no = group},
|
||||||
|
office = {'always',
|
||||||
|
no = group},
|
||||||
|
railway = {'named',
|
||||||
|
rail = group,
|
||||||
|
no = group,
|
||||||
|
abandoned = group,
|
||||||
|
disused = group,
|
||||||
|
razed = group,
|
||||||
|
level_crossing = group,
|
||||||
|
switch = group,
|
||||||
|
signal = group,
|
||||||
|
buffer_stop = group},
|
||||||
|
shop = {'always',
|
||||||
|
no = group},
|
||||||
|
tourism = {'always',
|
||||||
|
attraction = 'fallback',
|
||||||
|
no = group,
|
||||||
|
yes = group,
|
||||||
|
information = exclude_when_key_present('information')},
|
||||||
|
tunnel = {'named_with_key',
|
||||||
|
no = group}
|
||||||
|
} end
|
||||||
|
|
||||||
|
module.MAIN_TAGS_STREETS = {}
|
||||||
|
|
||||||
|
module.MAIN_TAGS_STREETS.default = {
|
||||||
|
place = {square = 'always'},
|
||||||
|
highway = {motorway = 'always',
|
||||||
|
trunk = 'always',
|
||||||
|
primary = 'always',
|
||||||
|
secondary = 'always',
|
||||||
|
tertiary = 'always',
|
||||||
|
unclassified = 'always',
|
||||||
|
residential = 'always',
|
||||||
|
road = 'always',
|
||||||
|
living_street = 'always',
|
||||||
|
pedestrian = 'always',
|
||||||
|
service = 'named',
|
||||||
|
cycleway = 'named',
|
||||||
|
path = 'named',
|
||||||
|
footway = filter_footways,
|
||||||
|
steps = 'named',
|
||||||
|
bridleway = 'named',
|
||||||
|
track = 'named',
|
||||||
|
motorway_link = 'named',
|
||||||
|
trunk_link = 'named',
|
||||||
|
primary_link = 'named',
|
||||||
|
secondary_link = 'named',
|
||||||
|
tertiary_link = 'named'}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.MAIN_TAGS_STREETS.car = {
|
||||||
|
place = {square = 'always'},
|
||||||
|
highway = {motorway = 'always',
|
||||||
|
trunk = 'always',
|
||||||
|
primary = 'always',
|
||||||
|
secondary = 'always',
|
||||||
|
tertiary = 'always',
|
||||||
|
unclassified = 'always',
|
||||||
|
residential = 'always',
|
||||||
|
road = 'always',
|
||||||
|
living_street = 'always',
|
||||||
|
service = 'always',
|
||||||
|
track = 'always',
|
||||||
|
motorway_link = 'always',
|
||||||
|
trunk_link = 'always',
|
||||||
|
primary_link = 'always',
|
||||||
|
secondary_link = 'always',
|
||||||
|
tertiary_link = 'always'}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.MAIN_TAGS_STREETS.all = {
|
||||||
|
place = {square = 'always'},
|
||||||
|
highway = {motorway = 'always',
|
||||||
|
trunk = 'always',
|
||||||
|
primary = 'always',
|
||||||
|
secondary = 'always',
|
||||||
|
tertiary = 'always',
|
||||||
|
unclassified = 'always',
|
||||||
|
residential = 'always',
|
||||||
|
road = 'always',
|
||||||
|
living_street = 'always',
|
||||||
|
pedestrian = 'always',
|
||||||
|
service = 'always',
|
||||||
|
cycleway = 'always',
|
||||||
|
path = 'always',
|
||||||
|
footway = 'always',
|
||||||
|
steps = 'always',
|
||||||
|
bridleway = 'always',
|
||||||
|
track = 'always',
|
||||||
|
motorway_link = 'always',
|
||||||
|
trunk_link = 'always',
|
||||||
|
primary_link = 'always',
|
||||||
|
secondary_link = 'always',
|
||||||
|
tertiary_link = 'always'}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- name tags
|
||||||
|
|
||||||
|
module.NAME_TAGS = {}
|
||||||
|
|
||||||
|
module.NAME_TAGS.core = {main = {'name', 'name:*',
|
||||||
|
'int_name', 'int_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:*'},
|
||||||
|
extra = {'ref', 'int_ref', 'nat_ref', 'reg_ref',
|
||||||
|
'loc_ref', 'old_ref', 'ISO3166-2'}
|
||||||
|
}
|
||||||
|
module.NAME_TAGS.address = {house = {'addr:housename'}}
|
||||||
|
module.NAME_TAGS.poi = group_merge({main = {'brand'},
|
||||||
|
extra = {'iata', 'icao', 'faa'}},
|
||||||
|
module.NAME_TAGS.core)
|
||||||
|
|
||||||
|
-- Address tagging
|
||||||
|
|
||||||
|
module.ADDRESS_TAGS = {}
|
||||||
|
|
||||||
|
module.ADDRESS_TAGS.core = { extra = {'addr:*', 'is_in:*', 'tiger:county'},
|
||||||
|
postcode = {'postal_code', 'postcode', 'addr:postcode',
|
||||||
|
'tiger:zip_left', 'tiger:zip_right'},
|
||||||
|
country = {'country_code', 'ISO3166-1',
|
||||||
|
'addr:country_code', 'is_in:country_code',
|
||||||
|
'addr:country', 'is_in:country'}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.ADDRESS_TAGS.houses = { main = {'addr:housenumber',
|
||||||
|
'addr:conscriptionnumber',
|
||||||
|
'addr:streetnumber'},
|
||||||
|
interpolation = {'addr:interpolation'}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Ignored tags (prefiltered away)
|
||||||
|
|
||||||
|
module.IGNORE_KEYS = {}
|
||||||
|
|
||||||
|
module.IGNORE_KEYS.metatags = {'note', 'note:*', 'source', 'source:*', '*source',
|
||||||
|
'attribution', 'comment', 'fixme', 'created_by',
|
||||||
|
'tiger:cfcc', 'tiger:reviewed', 'nysgissam:*',
|
||||||
|
'NHD:*', 'nhd:*', 'gnis:*', 'geobase:*', 'yh:*',
|
||||||
|
'osak:*', 'naptan:*', 'CLC:*', 'import', 'it:fvg:*',
|
||||||
|
'lacounty:*', 'ref:linz:*', 'survey:*',
|
||||||
|
'ref:bygningsnr', 'ref:ruian:*', 'building:ruian:type',
|
||||||
|
'type',
|
||||||
|
'is_in:postcode'}
|
||||||
|
module.IGNORE_KEYS.name = {'*:prefix', '*:suffix', 'name:prefix:*', 'name:suffix:*',
|
||||||
|
'name:etymology', 'name:etymology:*',
|
||||||
|
'name:signed', 'name:botanical'}
|
||||||
|
module.IGNORE_KEYS.address = {'addr:street:*', 'addr:city:*', 'addr:district:*',
|
||||||
|
'addr:province:*', 'addr:subdistrict:*', 'addr:place:*',
|
||||||
|
'addr:TW:dataset'}
|
||||||
|
|
||||||
|
-- INTERNAL: Required extra tags
|
||||||
|
|
||||||
|
module.EXTRATAGS = {keys = {'wikipedia', 'wikipedia:*', 'wikidata', 'capital'}}
|
||||||
|
|
||||||
|
-- Defaults for the entrance table
|
||||||
|
|
||||||
|
module.ENTRANCE_TABLE = {}
|
||||||
|
|
||||||
|
module.ENTRANCE_TABLE.default = {main_tags = {'entrance', 'routing:entrance'},
|
||||||
|
extra_exclude = module.IGNORE_KEYS.metatags}
|
||||||
|
|
||||||
|
return module
|
||||||
22
lib-lua/themes/nominatim/topics/address.lua
Normal file
22
lib-lua/themes/nominatim/topics/address.lua
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
local _, flex, cfg = ...
|
||||||
|
|
||||||
|
flex.set_main_tags('admin')
|
||||||
|
flex.modify_main_tags('street/' .. (cfg.street_theme or 'default'))
|
||||||
|
flex.modify_main_tags{boundary = {postal_code = 'always'}}
|
||||||
|
|
||||||
|
flex.set_name_tags('core')
|
||||||
|
flex.modify_name_tags('address')
|
||||||
|
|
||||||
|
flex.set_address_tags('core')
|
||||||
|
flex.modify_address_tags('houses')
|
||||||
|
|
||||||
|
flex.ignore_keys('metatags')
|
||||||
|
|
||||||
|
if cfg.with_extratags then
|
||||||
|
flex.set_unused_handling{delete_keys = {'tiger:*'}}
|
||||||
|
flex.add_for_extratags('name')
|
||||||
|
flex.add_for_extratags('address')
|
||||||
|
else
|
||||||
|
flex.ignore_keys('name')
|
||||||
|
flex.ignore_keys('address')
|
||||||
|
end
|
||||||
19
lib-lua/themes/nominatim/topics/admin.lua
Normal file
19
lib-lua/themes/nominatim/topics/admin.lua
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
local _, flex, cfg = ...
|
||||||
|
|
||||||
|
flex.set_main_tags('admin')
|
||||||
|
|
||||||
|
flex.set_name_tags('core')
|
||||||
|
|
||||||
|
flex.set_address_tags('core')
|
||||||
|
flex.set_postcode_fallback(false)
|
||||||
|
|
||||||
|
flex.ignore_keys('metatags')
|
||||||
|
|
||||||
|
if cfg.with_extratags then
|
||||||
|
flex.set_unused_handling{delete_keys = {'tiger:*'}}
|
||||||
|
flex.add_for_extratags('name')
|
||||||
|
flex.add_for_extratags('address')
|
||||||
|
else
|
||||||
|
flex.ignore_keys('name')
|
||||||
|
flex.ignore_keys('address')
|
||||||
|
end
|
||||||
33
lib-lua/themes/nominatim/topics/full.lua
Normal file
33
lib-lua/themes/nominatim/topics/full.lua
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
local _, flex, cfg = ...
|
||||||
|
|
||||||
|
local group
|
||||||
|
if cfg.with_extratags then
|
||||||
|
group = 'extra'
|
||||||
|
else
|
||||||
|
group = 'delete'
|
||||||
|
end
|
||||||
|
|
||||||
|
flex.set_main_tags('all_boundaries')
|
||||||
|
flex.modify_main_tags('natural')
|
||||||
|
flex.modify_main_tags('street/' .. (cfg.street_theme or 'default'))
|
||||||
|
flex.modify_main_tags('poi/' .. group)
|
||||||
|
|
||||||
|
flex.set_name_tags('core')
|
||||||
|
flex.modify_name_tags('address')
|
||||||
|
flex.modify_name_tags('poi')
|
||||||
|
|
||||||
|
flex.set_address_tags('core')
|
||||||
|
flex.modify_address_tags('houses')
|
||||||
|
|
||||||
|
flex.ignore_keys('metatags')
|
||||||
|
|
||||||
|
if cfg.with_extratags then
|
||||||
|
flex.set_unused_handling{delete_keys = {'tiger:*'}}
|
||||||
|
flex.add_for_extratags('name')
|
||||||
|
flex.add_for_extratags('address')
|
||||||
|
else
|
||||||
|
flex.ignore_keys('name')
|
||||||
|
flex.ignore_keys('address')
|
||||||
|
end
|
||||||
|
|
||||||
|
flex.set_entrance_filter('default')
|
||||||
21
lib-lua/themes/nominatim/topics/street.lua
Normal file
21
lib-lua/themes/nominatim/topics/street.lua
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
local _, flex, cfg = ...
|
||||||
|
|
||||||
|
flex.set_main_tags('admin')
|
||||||
|
flex.modify_main_tags('street/' .. (cfg.street_theme or 'default'))
|
||||||
|
flex.modify_main_tags{boundary = {postal_code = 'always'}}
|
||||||
|
|
||||||
|
flex.set_name_tags('core')
|
||||||
|
|
||||||
|
flex.set_address_tags('core')
|
||||||
|
flex.set_postcode_fallback(false)
|
||||||
|
|
||||||
|
flex.ignore_keys('metatags')
|
||||||
|
|
||||||
|
if cfg.with_extratags then
|
||||||
|
flex.set_unused_handling{delete_keys = {'tiger:*'}}
|
||||||
|
flex.add_for_extratags('name')
|
||||||
|
flex.add_for_extratags('address')
|
||||||
|
else
|
||||||
|
flex.ignore_keys('name')
|
||||||
|
flex.ignore_keys('address')
|
||||||
|
end
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/ClassTypes.php');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detailed list of address parts for a single result
|
|
||||||
*/
|
|
||||||
class AddressDetails
|
|
||||||
{
|
|
||||||
private $iPlaceID;
|
|
||||||
private $aAddressLines;
|
|
||||||
|
|
||||||
public function __construct(&$oDB, $iPlaceID, $sHousenumber, $mLangPref)
|
|
||||||
{
|
|
||||||
$this->iPlaceID = $iPlaceID;
|
|
||||||
|
|
||||||
if (is_array($mLangPref)) {
|
|
||||||
$mLangPref = $oDB->getArraySQL($oDB->getDBQuotedList($mLangPref));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($sHousenumber)) {
|
|
||||||
$sHousenumber = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sSQL = 'SELECT *,';
|
|
||||||
$sSQL .= ' get_name_by_language(name,'.$mLangPref.') as localname';
|
|
||||||
$sSQL .= ' FROM get_addressdata('.$iPlaceID.','.$sHousenumber.')';
|
|
||||||
$sSQL .= ' ORDER BY rank_address DESC, isaddress DESC';
|
|
||||||
|
|
||||||
$this->aAddressLines = $oDB->getAll($sSQL);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function isAddress($aLine)
|
|
||||||
{
|
|
||||||
return $aLine['isaddress'] || $aLine['type'] == 'country_code';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAddressDetails($bAll = false)
|
|
||||||
{
|
|
||||||
if ($bAll) {
|
|
||||||
return $this->aAddressLines;
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_filter($this->aAddressLines, array(__CLASS__, 'isAddress'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLocaleAddress()
|
|
||||||
{
|
|
||||||
$aParts = array();
|
|
||||||
$sPrevResult = '';
|
|
||||||
|
|
||||||
foreach ($this->aAddressLines as $aLine) {
|
|
||||||
if ($aLine['isaddress'] && $sPrevResult != $aLine['localname']) {
|
|
||||||
$sPrevResult = $aLine['localname'];
|
|
||||||
$aParts[] = $sPrevResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return join(', ', $aParts);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAddressNames()
|
|
||||||
{
|
|
||||||
$aAddress = array();
|
|
||||||
|
|
||||||
foreach ($this->aAddressLines as $aLine) {
|
|
||||||
if (!self::isAddress($aLine)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sTypeLabel = ClassTypes\getLabelTag($aLine);
|
|
||||||
|
|
||||||
$sName = null;
|
|
||||||
if (isset($aLine['localname']) && $aLine['localname']!=='') {
|
|
||||||
$sName = $aLine['localname'];
|
|
||||||
} elseif (isset($aLine['housenumber']) && $aLine['housenumber']!=='') {
|
|
||||||
$sName = $aLine['housenumber'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($sName)
|
|
||||||
&& (!isset($aAddress[$sTypeLabel])
|
|
||||||
|| $aLine['class'] == 'place')
|
|
||||||
) {
|
|
||||||
$aAddress[$sTypeLabel] = $sName;
|
|
||||||
|
|
||||||
if (!empty($aLine['name'])) {
|
|
||||||
$this->addSubdivisionCode($aAddress, $aLine['admin_level'], $aLine['name']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotates the given json with geocodejson address information fields.
|
|
||||||
*
|
|
||||||
* @param array $aJson Json hash to add the fields to.
|
|
||||||
*
|
|
||||||
* Geocodejson has the following fields:
|
|
||||||
* street, locality, postcode, city, district,
|
|
||||||
* county, state, country
|
|
||||||
*
|
|
||||||
* Postcode and housenumber are added by type, district is not used.
|
|
||||||
* All other fields are set according to address rank.
|
|
||||||
*/
|
|
||||||
public function addGeocodeJsonAddressParts(&$aJson)
|
|
||||||
{
|
|
||||||
foreach (array_reverse($this->aAddressLines) as $aLine) {
|
|
||||||
if (!$aLine['isaddress']) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($aLine['localname']) || $aLine['localname'] == '') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($aLine['type'] == 'postcode' || $aLine['type'] == 'postal_code') {
|
|
||||||
$aJson['postcode'] = $aLine['localname'];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($aLine['type'] == 'house_number') {
|
|
||||||
$aJson['housenumber'] = $aLine['localname'];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->iPlaceID == $aLine['place_id']) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$iRank = (int)$aLine['rank_address'];
|
|
||||||
|
|
||||||
if ($iRank > 25 && $iRank < 28) {
|
|
||||||
$aJson['street'] = $aLine['localname'];
|
|
||||||
} elseif ($iRank >= 22 && $iRank <= 25) {
|
|
||||||
$aJson['locality'] = $aLine['localname'];
|
|
||||||
} elseif ($iRank >= 17 && $iRank <= 21) {
|
|
||||||
$aJson['district'] = $aLine['localname'];
|
|
||||||
} elseif ($iRank >= 13 && $iRank <= 16) {
|
|
||||||
$aJson['city'] = $aLine['localname'];
|
|
||||||
} elseif ($iRank >= 10 && $iRank <= 12) {
|
|
||||||
$aJson['county'] = $aLine['localname'];
|
|
||||||
} elseif ($iRank >= 5 && $iRank <= 9) {
|
|
||||||
$aJson['state'] = $aLine['localname'];
|
|
||||||
} elseif ($iRank == 4) {
|
|
||||||
$aJson['country'] = $aLine['localname'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAdminLevels()
|
|
||||||
{
|
|
||||||
$aAddress = array();
|
|
||||||
foreach (array_reverse($this->aAddressLines) as $aLine) {
|
|
||||||
if (self::isAddress($aLine)
|
|
||||||
&& isset($aLine['admin_level'])
|
|
||||||
&& $aLine['admin_level'] < 15
|
|
||||||
&& !isset($aAddress['level'.$aLine['admin_level']])
|
|
||||||
) {
|
|
||||||
$aAddress['level'.$aLine['admin_level']] = $aLine['localname'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $aAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return $this->aAddressLines;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addSubdivisionCode(&$aAddress, $iAdminLevel, $nameDetails)
|
|
||||||
{
|
|
||||||
if (is_string($nameDetails)) {
|
|
||||||
$nameDetails = json_decode('{' . str_replace('"=>"', '":"', $nameDetails) . '}', true);
|
|
||||||
}
|
|
||||||
if (!empty($nameDetails['ISO3166-2'])) {
|
|
||||||
$aAddress["ISO3166-2-lvl$iAdminLevel"] = $nameDetails['ISO3166-2'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,576 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim\ClassTypes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a label tag for the given place that can be used as an XML name.
|
|
||||||
*
|
|
||||||
* @param array[] $aPlace Information about the place to label.
|
|
||||||
*
|
|
||||||
* A label tag groups various object types together under a common
|
|
||||||
* label. The returned value is lower case and has no spaces
|
|
||||||
*/
|
|
||||||
function getLabelTag($aPlace, $sCountry = null)
|
|
||||||
{
|
|
||||||
$iRank = (int) ($aPlace['rank_address'] ?? 30);
|
|
||||||
$sLabel;
|
|
||||||
if (isset($aPlace['place_type'])) {
|
|
||||||
$sLabel = $aPlace['place_type'];
|
|
||||||
} elseif ($aPlace['class'] == 'boundary' && $aPlace['type'] == 'administrative') {
|
|
||||||
$sLabel = getBoundaryLabel($iRank/2, $sCountry);
|
|
||||||
} elseif ($aPlace['type'] == 'postal_code') {
|
|
||||||
$sLabel = 'postcode';
|
|
||||||
} elseif ($iRank < 26) {
|
|
||||||
$sLabel = $aPlace['type'];
|
|
||||||
} elseif ($iRank < 28) {
|
|
||||||
$sLabel = 'road';
|
|
||||||
} elseif ($aPlace['class'] == 'place'
|
|
||||||
&& ($aPlace['type'] == 'house_number' ||
|
|
||||||
$aPlace['type'] == 'house_name' ||
|
|
||||||
$aPlace['type'] == 'country_code')
|
|
||||||
) {
|
|
||||||
$sLabel = $aPlace['type'];
|
|
||||||
} else {
|
|
||||||
$sLabel = $aPlace['class'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return strtolower(str_replace(' ', '_', $sLabel));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a label for the given place.
|
|
||||||
*
|
|
||||||
* @param array[] $aPlace Information about the place to label.
|
|
||||||
*/
|
|
||||||
function getLabel($aPlace, $sCountry = null)
|
|
||||||
{
|
|
||||||
if (isset($aPlace['place_type'])) {
|
|
||||||
return ucwords(str_replace('_', ' ', $aPlace['place_type']));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($aPlace['class'] == 'boundary' && $aPlace['type'] == 'administrative') {
|
|
||||||
return getBoundaryLabel(($aPlace['rank_address'] ?? 30)/2, $sCountry ?? null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a label only for 'important' class/type combinations
|
|
||||||
if (getImportance($aPlace) !== null) {
|
|
||||||
return ucwords(str_replace('_', ' ', $aPlace['type']));
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a simple label for an administrative boundary for the given country.
|
|
||||||
*
|
|
||||||
* @param int $iAdminLevel Content of admin_level tag.
|
|
||||||
* @param string $sCountry Country code of the country where the object is
|
|
||||||
* in. May be null, in which case a world-wide
|
|
||||||
* fallback is used.
|
|
||||||
* @param string $sFallback String to return if no explicit string is listed.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
function getBoundaryLabel($iAdminLevel, $sCountry, $sFallback = 'Administrative')
|
|
||||||
{
|
|
||||||
static $aBoundaryList = array (
|
|
||||||
'default' => array (
|
|
||||||
1 => 'Continent',
|
|
||||||
2 => 'Country',
|
|
||||||
3 => 'Region',
|
|
||||||
4 => 'State',
|
|
||||||
5 => 'State District',
|
|
||||||
6 => 'County',
|
|
||||||
7 => 'Municipality',
|
|
||||||
8 => 'City',
|
|
||||||
9 => 'City District',
|
|
||||||
10 => 'Suburb',
|
|
||||||
11 => 'Neighbourhood',
|
|
||||||
12 => 'City Block'
|
|
||||||
),
|
|
||||||
'no' => array (
|
|
||||||
3 => 'State',
|
|
||||||
4 => 'County'
|
|
||||||
),
|
|
||||||
'se' => array (
|
|
||||||
3 => 'State',
|
|
||||||
4 => 'County'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isset($aBoundaryList[$sCountry])
|
|
||||||
&& isset($aBoundaryList[$sCountry][$iAdminLevel])
|
|
||||||
) {
|
|
||||||
return $aBoundaryList[$sCountry][$iAdminLevel];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aBoundaryList['default'][$iAdminLevel] ?? $sFallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an estimated radius of how far the object node extends.
|
|
||||||
*
|
|
||||||
* @param array[] $aPlace Information about the place. This must be a node
|
|
||||||
* feature.
|
|
||||||
*
|
|
||||||
* @return float The radius around the feature in degrees.
|
|
||||||
*/
|
|
||||||
function getDefRadius($aPlace)
|
|
||||||
{
|
|
||||||
$aSpecialRadius = array(
|
|
||||||
'place:continent' => 25,
|
|
||||||
'place:country' => 7,
|
|
||||||
'place:state' => 2.6,
|
|
||||||
'place:province' => 2.6,
|
|
||||||
'place:region' => 1.0,
|
|
||||||
'place:county' => 0.7,
|
|
||||||
'place:city' => 0.16,
|
|
||||||
'place:municipality' => 0.16,
|
|
||||||
'place:island' => 0.32,
|
|
||||||
'place:postcode' => 0.16,
|
|
||||||
'place:town' => 0.04,
|
|
||||||
'place:village' => 0.02,
|
|
||||||
'place:hamlet' => 0.02,
|
|
||||||
'place:district' => 0.02,
|
|
||||||
'place:borough' => 0.02,
|
|
||||||
'place:suburb' => 0.02,
|
|
||||||
'place:locality' => 0.01,
|
|
||||||
'place:neighbourhood'=> 0.01,
|
|
||||||
'place:quarter' => 0.01,
|
|
||||||
'place:city_block' => 0.01,
|
|
||||||
'landuse:farm' => 0.01,
|
|
||||||
'place:farm' => 0.01,
|
|
||||||
'place:airport' => 0.015,
|
|
||||||
'aeroway:aerodrome' => 0.015,
|
|
||||||
'railway:station' => 0.005
|
|
||||||
);
|
|
||||||
|
|
||||||
$sClassPlace = $aPlace['class'].':'.$aPlace['type'];
|
|
||||||
|
|
||||||
return $aSpecialRadius[$sClassPlace] ?? 0.00005;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the icon to use with the given object.
|
|
||||||
*/
|
|
||||||
function getIcon($aPlace)
|
|
||||||
{
|
|
||||||
$aIcons = array(
|
|
||||||
'boundary:administrative' => 'poi_boundary_administrative',
|
|
||||||
'place:city' => 'poi_place_city',
|
|
||||||
'place:town' => 'poi_place_town',
|
|
||||||
'place:village' => 'poi_place_village',
|
|
||||||
'place:hamlet' => 'poi_place_village',
|
|
||||||
'place:suburb' => 'poi_place_village',
|
|
||||||
'place:locality' => 'poi_place_village',
|
|
||||||
'place:airport' => 'transport_airport2',
|
|
||||||
'aeroway:aerodrome' => 'transport_airport2',
|
|
||||||
'railway:station' => 'transport_train_station2',
|
|
||||||
'amenity:place_of_worship' => 'place_of_worship_unknown3',
|
|
||||||
'amenity:pub' => 'food_pub',
|
|
||||||
'amenity:bar' => 'food_bar',
|
|
||||||
'amenity:university' => 'education_university',
|
|
||||||
'tourism:museum' => 'tourist_museum',
|
|
||||||
'amenity:arts_centre' => 'tourist_art_gallery2',
|
|
||||||
'tourism:zoo' => 'tourist_zoo',
|
|
||||||
'tourism:theme_park' => 'poi_point_of_interest',
|
|
||||||
'tourism:attraction' => 'poi_point_of_interest',
|
|
||||||
'leisure:golf_course' => 'sport_golf',
|
|
||||||
'historic:castle' => 'tourist_castle',
|
|
||||||
'amenity:hospital' => 'health_hospital',
|
|
||||||
'amenity:school' => 'education_school',
|
|
||||||
'amenity:theatre' => 'tourist_theatre',
|
|
||||||
'amenity:library' => 'amenity_library',
|
|
||||||
'amenity:fire_station' => 'amenity_firestation3',
|
|
||||||
'amenity:police' => 'amenity_police2',
|
|
||||||
'amenity:bank' => 'money_bank2',
|
|
||||||
'amenity:post_office' => 'amenity_post_office',
|
|
||||||
'tourism:hotel' => 'accommodation_hotel2',
|
|
||||||
'amenity:cinema' => 'tourist_cinema',
|
|
||||||
'tourism:artwork' => 'tourist_art_gallery2',
|
|
||||||
'historic:archaeological_site' => 'tourist_archaeological2',
|
|
||||||
'amenity:doctors' => 'health_doctors',
|
|
||||||
'leisure:sports_centre' => 'sport_leisure_centre',
|
|
||||||
'leisure:swimming_pool' => 'sport_swimming_outdoor',
|
|
||||||
'shop:supermarket' => 'shopping_supermarket',
|
|
||||||
'shop:convenience' => 'shopping_convenience',
|
|
||||||
'amenity:restaurant' => 'food_restaurant',
|
|
||||||
'amenity:fast_food' => 'food_fastfood',
|
|
||||||
'amenity:cafe' => 'food_cafe',
|
|
||||||
'tourism:guest_house' => 'accommodation_bed_and_breakfast',
|
|
||||||
'amenity:pharmacy' => 'health_pharmacy_dispensing',
|
|
||||||
'amenity:fuel' => 'transport_fuel',
|
|
||||||
'natural:peak' => 'poi_peak',
|
|
||||||
'natural:wood' => 'landuse_coniferous_and_deciduous',
|
|
||||||
'shop:bicycle' => 'shopping_bicycle',
|
|
||||||
'shop:clothes' => 'shopping_clothes',
|
|
||||||
'shop:hairdresser' => 'shopping_hairdresser',
|
|
||||||
'shop:doityourself' => 'shopping_diy',
|
|
||||||
'shop:estate_agent' => 'shopping_estateagent2',
|
|
||||||
'shop:car' => 'shopping_car',
|
|
||||||
'shop:garden_centre' => 'shopping_garden_centre',
|
|
||||||
'shop:car_repair' => 'shopping_car_repair',
|
|
||||||
'shop:bakery' => 'shopping_bakery',
|
|
||||||
'shop:butcher' => 'shopping_butcher',
|
|
||||||
'shop:apparel' => 'shopping_clothes',
|
|
||||||
'shop:laundry' => 'shopping_laundrette',
|
|
||||||
'shop:beverages' => 'shopping_alcohol',
|
|
||||||
'shop:alcohol' => 'shopping_alcohol',
|
|
||||||
'shop:optician' => 'health_opticians',
|
|
||||||
'shop:chemist' => 'health_pharmacy',
|
|
||||||
'shop:gallery' => 'tourist_art_gallery2',
|
|
||||||
'shop:jewelry' => 'shopping_jewelry',
|
|
||||||
'tourism:information' => 'amenity_information',
|
|
||||||
'historic:ruins' => 'tourist_ruin',
|
|
||||||
'amenity:college' => 'education_school',
|
|
||||||
'historic:monument' => 'tourist_monument',
|
|
||||||
'historic:memorial' => 'tourist_monument',
|
|
||||||
'historic:mine' => 'poi_mine',
|
|
||||||
'tourism:caravan_site' => 'accommodation_caravan_park',
|
|
||||||
'amenity:bus_station' => 'transport_bus_station',
|
|
||||||
'amenity:atm' => 'money_atm2',
|
|
||||||
'tourism:viewpoint' => 'tourist_view_point',
|
|
||||||
'tourism:guesthouse' => 'accommodation_bed_and_breakfast',
|
|
||||||
'railway:tram' => 'transport_tram_stop',
|
|
||||||
'amenity:courthouse' => 'amenity_court',
|
|
||||||
'amenity:recycling' => 'amenity_recycling',
|
|
||||||
'amenity:dentist' => 'health_dentist',
|
|
||||||
'natural:beach' => 'tourist_beach',
|
|
||||||
'railway:tram_stop' => 'transport_tram_stop',
|
|
||||||
'amenity:prison' => 'amenity_prison',
|
|
||||||
'highway:bus_stop' => 'transport_bus_stop2'
|
|
||||||
);
|
|
||||||
|
|
||||||
$sClassPlace = $aPlace['class'].':'.$aPlace['type'];
|
|
||||||
|
|
||||||
return $aIcons[$sClassPlace] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an icon for the given object with its full URL.
|
|
||||||
*/
|
|
||||||
function getIconFile($aPlace)
|
|
||||||
{
|
|
||||||
if (CONST_MapIcon_URL === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sIcon = getIcon($aPlace);
|
|
||||||
|
|
||||||
if (!isset($sIcon)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CONST_MapIcon_URL.'/'.$sIcon.'.p.20.png';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a class importance value for the given place.
|
|
||||||
*
|
|
||||||
* @param array[] $aPlace Information about the place.
|
|
||||||
*
|
|
||||||
* @return int An importance value. The lower the value, the more
|
|
||||||
* important the class.
|
|
||||||
*/
|
|
||||||
function getImportance($aPlace)
|
|
||||||
{
|
|
||||||
static $aWithImportance = null;
|
|
||||||
|
|
||||||
if ($aWithImportance === null) {
|
|
||||||
$aWithImportance = array_flip(array(
|
|
||||||
'boundary:administrative',
|
|
||||||
'place:country',
|
|
||||||
'place:state',
|
|
||||||
'place:province',
|
|
||||||
'place:county',
|
|
||||||
'place:city',
|
|
||||||
'place:region',
|
|
||||||
'place:island',
|
|
||||||
'place:town',
|
|
||||||
'place:village',
|
|
||||||
'place:hamlet',
|
|
||||||
'place:suburb',
|
|
||||||
'place:locality',
|
|
||||||
'landuse:farm',
|
|
||||||
'place:farm',
|
|
||||||
'highway:motorway_junction',
|
|
||||||
'highway:motorway',
|
|
||||||
'highway:trunk',
|
|
||||||
'highway:primary',
|
|
||||||
'highway:secondary',
|
|
||||||
'highway:tertiary',
|
|
||||||
'highway:residential',
|
|
||||||
'highway:unclassified',
|
|
||||||
'highway:living_street',
|
|
||||||
'highway:service',
|
|
||||||
'highway:track',
|
|
||||||
'highway:road',
|
|
||||||
'highway:byway',
|
|
||||||
'highway:bridleway',
|
|
||||||
'highway:cycleway',
|
|
||||||
'highway:pedestrian',
|
|
||||||
'highway:footway',
|
|
||||||
'highway:steps',
|
|
||||||
'highway:motorway_link',
|
|
||||||
'highway:trunk_link',
|
|
||||||
'highway:primary_link',
|
|
||||||
'landuse:industrial',
|
|
||||||
'landuse:residential',
|
|
||||||
'landuse:retail',
|
|
||||||
'landuse:commercial',
|
|
||||||
'place:airport',
|
|
||||||
'aeroway:aerodrome',
|
|
||||||
'railway:station',
|
|
||||||
'amenity:place_of_worship',
|
|
||||||
'amenity:pub',
|
|
||||||
'amenity:bar',
|
|
||||||
'amenity:university',
|
|
||||||
'tourism:museum',
|
|
||||||
'amenity:arts_centre',
|
|
||||||
'tourism:zoo',
|
|
||||||
'tourism:theme_park',
|
|
||||||
'tourism:attraction',
|
|
||||||
'leisure:golf_course',
|
|
||||||
'historic:castle',
|
|
||||||
'amenity:hospital',
|
|
||||||
'amenity:school',
|
|
||||||
'amenity:theatre',
|
|
||||||
'amenity:public_building',
|
|
||||||
'amenity:library',
|
|
||||||
'amenity:townhall',
|
|
||||||
'amenity:community_centre',
|
|
||||||
'amenity:fire_station',
|
|
||||||
'amenity:police',
|
|
||||||
'amenity:bank',
|
|
||||||
'amenity:post_office',
|
|
||||||
'leisure:park',
|
|
||||||
'amenity:park',
|
|
||||||
'landuse:park',
|
|
||||||
'landuse:recreation_ground',
|
|
||||||
'tourism:hotel',
|
|
||||||
'tourism:motel',
|
|
||||||
'amenity:cinema',
|
|
||||||
'tourism:artwork',
|
|
||||||
'historic:archaeological_site',
|
|
||||||
'amenity:doctors',
|
|
||||||
'leisure:sports_centre',
|
|
||||||
'leisure:swimming_pool',
|
|
||||||
'shop:supermarket',
|
|
||||||
'shop:convenience',
|
|
||||||
'amenity:restaurant',
|
|
||||||
'amenity:fast_food',
|
|
||||||
'amenity:cafe',
|
|
||||||
'tourism:guest_house',
|
|
||||||
'amenity:pharmacy',
|
|
||||||
'amenity:fuel',
|
|
||||||
'natural:peak',
|
|
||||||
'waterway:waterfall',
|
|
||||||
'natural:wood',
|
|
||||||
'natural:water',
|
|
||||||
'landuse:forest',
|
|
||||||
'landuse:cemetery',
|
|
||||||
'landuse:allotments',
|
|
||||||
'landuse:farmyard',
|
|
||||||
'railway:rail',
|
|
||||||
'waterway:canal',
|
|
||||||
'waterway:river',
|
|
||||||
'waterway:stream',
|
|
||||||
'shop:bicycle',
|
|
||||||
'shop:clothes',
|
|
||||||
'shop:hairdresser',
|
|
||||||
'shop:doityourself',
|
|
||||||
'shop:estate_agent',
|
|
||||||
'shop:car',
|
|
||||||
'shop:garden_centre',
|
|
||||||
'shop:car_repair',
|
|
||||||
'shop:newsagent',
|
|
||||||
'shop:bakery',
|
|
||||||
'shop:furniture',
|
|
||||||
'shop:butcher',
|
|
||||||
'shop:apparel',
|
|
||||||
'shop:electronics',
|
|
||||||
'shop:department_store',
|
|
||||||
'shop:books',
|
|
||||||
'shop:yes',
|
|
||||||
'shop:outdoor',
|
|
||||||
'shop:mall',
|
|
||||||
'shop:florist',
|
|
||||||
'shop:charity',
|
|
||||||
'shop:hardware',
|
|
||||||
'shop:laundry',
|
|
||||||
'shop:shoes',
|
|
||||||
'shop:beverages',
|
|
||||||
'shop:dry_cleaning',
|
|
||||||
'shop:carpet',
|
|
||||||
'shop:computer',
|
|
||||||
'shop:alcohol',
|
|
||||||
'shop:optician',
|
|
||||||
'shop:chemist',
|
|
||||||
'shop:gallery',
|
|
||||||
'shop:mobile_phone',
|
|
||||||
'shop:sports',
|
|
||||||
'shop:jewelry',
|
|
||||||
'shop:pet',
|
|
||||||
'shop:beauty',
|
|
||||||
'shop:stationery',
|
|
||||||
'shop:shopping_centre',
|
|
||||||
'shop:general',
|
|
||||||
'shop:electrical',
|
|
||||||
'shop:toys',
|
|
||||||
'shop:jeweller',
|
|
||||||
'shop:betting',
|
|
||||||
'shop:household',
|
|
||||||
'shop:travel_agency',
|
|
||||||
'shop:hifi',
|
|
||||||
'amenity:shop',
|
|
||||||
'tourism:information',
|
|
||||||
'place:house',
|
|
||||||
'place:house_name',
|
|
||||||
'place:house_number',
|
|
||||||
'place:country_code',
|
|
||||||
'leisure:pitch',
|
|
||||||
'highway:unsurfaced',
|
|
||||||
'historic:ruins',
|
|
||||||
'amenity:college',
|
|
||||||
'historic:monument',
|
|
||||||
'railway:subway',
|
|
||||||
'historic:memorial',
|
|
||||||
'leisure:nature_reserve',
|
|
||||||
'leisure:common',
|
|
||||||
'waterway:lock_gate',
|
|
||||||
'natural:fell',
|
|
||||||
'amenity:nightclub',
|
|
||||||
'highway:path',
|
|
||||||
'leisure:garden',
|
|
||||||
'landuse:reservoir',
|
|
||||||
'leisure:playground',
|
|
||||||
'leisure:stadium',
|
|
||||||
'historic:mine',
|
|
||||||
'natural:cliff',
|
|
||||||
'tourism:caravan_site',
|
|
||||||
'amenity:bus_station',
|
|
||||||
'amenity:kindergarten',
|
|
||||||
'highway:construction',
|
|
||||||
'amenity:atm',
|
|
||||||
'amenity:emergency_phone',
|
|
||||||
'waterway:lock',
|
|
||||||
'waterway:riverbank',
|
|
||||||
'natural:coastline',
|
|
||||||
'tourism:viewpoint',
|
|
||||||
'tourism:hostel',
|
|
||||||
'tourism:bed_and_breakfast',
|
|
||||||
'railway:halt',
|
|
||||||
'railway:platform',
|
|
||||||
'railway:tram',
|
|
||||||
'amenity:courthouse',
|
|
||||||
'amenity:recycling',
|
|
||||||
'amenity:dentist',
|
|
||||||
'natural:beach',
|
|
||||||
'place:moor',
|
|
||||||
'amenity:grave_yard',
|
|
||||||
'waterway:drain',
|
|
||||||
'landuse:grass',
|
|
||||||
'landuse:village_green',
|
|
||||||
'natural:bay',
|
|
||||||
'railway:tram_stop',
|
|
||||||
'leisure:marina',
|
|
||||||
'highway:stile',
|
|
||||||
'natural:moor',
|
|
||||||
'railway:light_rail',
|
|
||||||
'railway:narrow_gauge',
|
|
||||||
'natural:land',
|
|
||||||
'amenity:village_hall',
|
|
||||||
'waterway:dock',
|
|
||||||
'amenity:veterinary',
|
|
||||||
'landuse:brownfield',
|
|
||||||
'leisure:track',
|
|
||||||
'railway:historic_station',
|
|
||||||
'landuse:construction',
|
|
||||||
'amenity:prison',
|
|
||||||
'landuse:quarry',
|
|
||||||
'amenity:telephone',
|
|
||||||
'highway:traffic_signals',
|
|
||||||
'natural:heath',
|
|
||||||
'historic:house',
|
|
||||||
'amenity:social_club',
|
|
||||||
'landuse:military',
|
|
||||||
'amenity:health_centre',
|
|
||||||
'historic:building',
|
|
||||||
'amenity:clinic',
|
|
||||||
'highway:services',
|
|
||||||
'amenity:ferry_terminal',
|
|
||||||
'natural:marsh',
|
|
||||||
'natural:hill',
|
|
||||||
'highway:raceway',
|
|
||||||
'amenity:taxi',
|
|
||||||
'amenity:take_away',
|
|
||||||
'amenity:car_rental',
|
|
||||||
'place:islet',
|
|
||||||
'amenity:nursery',
|
|
||||||
'amenity:nursing_home',
|
|
||||||
'amenity:toilets',
|
|
||||||
'amenity:hall',
|
|
||||||
'waterway:boatyard',
|
|
||||||
'highway:mini_roundabout',
|
|
||||||
'historic:manor',
|
|
||||||
'tourism:chalet',
|
|
||||||
'amenity:bicycle_parking',
|
|
||||||
'amenity:hotel',
|
|
||||||
'waterway:weir',
|
|
||||||
'natural:wetland',
|
|
||||||
'natural:cave_entrance',
|
|
||||||
'amenity:crematorium',
|
|
||||||
'tourism:picnic_site',
|
|
||||||
'landuse:wood',
|
|
||||||
'landuse:basin',
|
|
||||||
'natural:tree',
|
|
||||||
'leisure:slipway',
|
|
||||||
'landuse:meadow',
|
|
||||||
'landuse:piste',
|
|
||||||
'amenity:care_home',
|
|
||||||
'amenity:club',
|
|
||||||
'amenity:medical_centre',
|
|
||||||
'historic:roman_road',
|
|
||||||
'historic:fort',
|
|
||||||
'railway:subway_entrance',
|
|
||||||
'historic:yes',
|
|
||||||
'highway:gate',
|
|
||||||
'leisure:fishing',
|
|
||||||
'historic:museum',
|
|
||||||
'amenity:car_wash',
|
|
||||||
'railway:level_crossing',
|
|
||||||
'leisure:bird_hide',
|
|
||||||
'natural:headland',
|
|
||||||
'tourism:apartments',
|
|
||||||
'amenity:shopping',
|
|
||||||
'natural:scrub',
|
|
||||||
'natural:fen',
|
|
||||||
'building:yes',
|
|
||||||
'mountain_pass:yes',
|
|
||||||
'amenity:parking',
|
|
||||||
'highway:bus_stop',
|
|
||||||
'place:postcode',
|
|
||||||
'amenity:post_box',
|
|
||||||
'place:houses',
|
|
||||||
'railway:preserved',
|
|
||||||
'waterway:derelict_canal',
|
|
||||||
'amenity:dead_pub',
|
|
||||||
'railway:disused_station',
|
|
||||||
'railway:abandoned',
|
|
||||||
'railway:disused'
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
$sClassPlace = $aPlace['class'].':'.$aPlace['type'];
|
|
||||||
|
|
||||||
return $aWithImportance[$sClassPlace] ?? null;
|
|
||||||
}
|
|
||||||
362
lib-php/DB.php
362
lib-php/DB.php
@@ -1,362 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/DatabaseError.php');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses PDO to access the database specified in the CONST_Database_DSN
|
|
||||||
* setting.
|
|
||||||
*/
|
|
||||||
class DB
|
|
||||||
{
|
|
||||||
protected $connection;
|
|
||||||
|
|
||||||
public function __construct($sDSN = null)
|
|
||||||
{
|
|
||||||
$this->sDSN = $sDSN ?? getSetting('DATABASE_DSN');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function connect($bNew = false, $bPersistent = true)
|
|
||||||
{
|
|
||||||
if (isset($this->connection) && !$bNew) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
$aConnOptions = array(
|
|
||||||
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
|
||||||
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
|
|
||||||
\PDO::ATTR_PERSISTENT => $bPersistent
|
|
||||||
);
|
|
||||||
|
|
||||||
// https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php
|
|
||||||
try {
|
|
||||||
$this->connection = new \PDO($this->sDSN, null, null, $aConnOptions);
|
|
||||||
} catch (\PDOException $e) {
|
|
||||||
$sMsg = 'Failed to establish database connection:' . $e->getMessage();
|
|
||||||
throw new \Nominatim\DatabaseError($sMsg, 500, null, $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->connection->exec("SET DateStyle TO 'sql,european'");
|
|
||||||
$this->connection->exec("SET client_encoding TO 'utf-8'");
|
|
||||||
// Disable JIT and parallel workers. They interfere badly with search SQL.
|
|
||||||
$this->connection->exec('SET max_parallel_workers_per_gather TO 0');
|
|
||||||
if ($this->getPostgresVersion() >= 11) {
|
|
||||||
$this->connection->exec('SET jit_above_cost TO -1');
|
|
||||||
}
|
|
||||||
|
|
||||||
$iMaxExecution = ini_get('max_execution_time');
|
|
||||||
if ($iMaxExecution > 0) {
|
|
||||||
$this->connection->setAttribute(\PDO::ATTR_TIMEOUT, $iMaxExecution); // seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns the number of rows that were modified or deleted by the SQL
|
|
||||||
// statement. If no rows were affected returns 0.
|
|
||||||
public function exec($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
|
|
||||||
{
|
|
||||||
$val = null;
|
|
||||||
try {
|
|
||||||
if (isset($aInputVars)) {
|
|
||||||
$stmt = $this->connection->prepare($sSQL);
|
|
||||||
$stmt->execute($aInputVars);
|
|
||||||
} else {
|
|
||||||
$val = $this->connection->exec($sSQL);
|
|
||||||
}
|
|
||||||
} catch (\PDOException $e) {
|
|
||||||
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
|
||||||
}
|
|
||||||
return $val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes query. Returns first row as array.
|
|
||||||
* Returns false if no result found.
|
|
||||||
*
|
|
||||||
* @param string $sSQL
|
|
||||||
*
|
|
||||||
* @return array[]
|
|
||||||
*/
|
|
||||||
public function getRow($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
|
|
||||||
$row = $stmt->fetch();
|
|
||||||
} catch (\PDOException $e) {
|
|
||||||
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
|
||||||
}
|
|
||||||
return $row;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes query. Returns first value of first result.
|
|
||||||
* Returns false if no results found.
|
|
||||||
*
|
|
||||||
* @param string $sSQL
|
|
||||||
*
|
|
||||||
* @return array[]
|
|
||||||
*/
|
|
||||||
public function getOne($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
|
|
||||||
$row = $stmt->fetch(\PDO::FETCH_NUM);
|
|
||||||
if ($row === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (\PDOException $e) {
|
|
||||||
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
|
||||||
}
|
|
||||||
return $row[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes query. Returns array of results (arrays).
|
|
||||||
* Returns empty array if no results found.
|
|
||||||
*
|
|
||||||
* @param string $sSQL
|
|
||||||
*
|
|
||||||
* @return array[]
|
|
||||||
*/
|
|
||||||
public function getAll($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
|
|
||||||
$rows = $stmt->fetchAll();
|
|
||||||
} catch (\PDOException $e) {
|
|
||||||
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
|
||||||
}
|
|
||||||
return $rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes query. Returns array of the first value of each result.
|
|
||||||
* Returns empty array if no results found.
|
|
||||||
*
|
|
||||||
* @param string $sSQL
|
|
||||||
*
|
|
||||||
* @return array[]
|
|
||||||
*/
|
|
||||||
public function getCol($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
|
|
||||||
{
|
|
||||||
$aVals = array();
|
|
||||||
try {
|
|
||||||
$stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
|
|
||||||
|
|
||||||
while (($val = $stmt->fetchColumn(0)) !== false) { // returns first column or false
|
|
||||||
$aVals[] = $val;
|
|
||||||
}
|
|
||||||
} catch (\PDOException $e) {
|
|
||||||
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
|
||||||
}
|
|
||||||
return $aVals;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes query. Returns associate array mapping first value to second value of each result.
|
|
||||||
* Returns empty array if no results found.
|
|
||||||
*
|
|
||||||
* @param string $sSQL
|
|
||||||
*
|
|
||||||
* @return array[]
|
|
||||||
*/
|
|
||||||
public function getAssoc($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
|
|
||||||
|
|
||||||
$aList = array();
|
|
||||||
while ($aRow = $stmt->fetch(\PDO::FETCH_NUM)) {
|
|
||||||
$aList[$aRow[0]] = $aRow[1];
|
|
||||||
}
|
|
||||||
} catch (\PDOException $e) {
|
|
||||||
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
|
||||||
}
|
|
||||||
return $aList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes query. Returns a PDO statement to iterate over.
|
|
||||||
*
|
|
||||||
* @param string $sSQL
|
|
||||||
*
|
|
||||||
* @return PDOStatement
|
|
||||||
*/
|
|
||||||
public function getQueryStatement($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if (isset($aInputVars)) {
|
|
||||||
$stmt = $this->connection->prepare($sSQL);
|
|
||||||
$stmt->execute($aInputVars);
|
|
||||||
} else {
|
|
||||||
$stmt = $this->connection->query($sSQL);
|
|
||||||
}
|
|
||||||
} catch (\PDOException $e) {
|
|
||||||
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
|
||||||
}
|
|
||||||
return $stmt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* St. John's Way => 'St. John\'s Way'
|
|
||||||
*
|
|
||||||
* @param string $sVal Text to be quoted.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getDBQuoted($sVal)
|
|
||||||
{
|
|
||||||
return $this->connection->quote($sVal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Like getDBQuoted, but takes an array.
|
|
||||||
*
|
|
||||||
* @param array $aVals List of text to be quoted.
|
|
||||||
*
|
|
||||||
* @return array[]
|
|
||||||
*/
|
|
||||||
public function getDBQuotedList($aVals)
|
|
||||||
{
|
|
||||||
return array_map(function ($sVal) {
|
|
||||||
return $this->getDBQuoted($sVal);
|
|
||||||
}, $aVals);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [1,2,'b'] => 'ARRAY[1,2,'b']''
|
|
||||||
*
|
|
||||||
* @param array $aVals List of text to be quoted.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getArraySQL($a)
|
|
||||||
{
|
|
||||||
return 'ARRAY['.join(',', $a).']';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a table exists in the database. Returns true if it does.
|
|
||||||
*
|
|
||||||
* @param string $sTableName
|
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public function tableExists($sTableName)
|
|
||||||
{
|
|
||||||
$sSQL = 'SELECT count(*) FROM pg_tables WHERE tablename = :tablename';
|
|
||||||
return ($this->getOne($sSQL, array(':tablename' => $sTableName)) == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a table. Returns true if deleted or didn't exist.
|
|
||||||
*
|
|
||||||
* @param string $sTableName
|
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public function deleteTable($sTableName)
|
|
||||||
{
|
|
||||||
return $this->exec('DROP TABLE IF EXISTS '.$sTableName.' CASCADE') == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to connect to the database but on failure doesn't throw an exception.
|
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public function checkConnection()
|
|
||||||
{
|
|
||||||
$bExists = true;
|
|
||||||
try {
|
|
||||||
$this->connect(true);
|
|
||||||
} catch (\Nominatim\DatabaseError $e) {
|
|
||||||
$bExists = false;
|
|
||||||
}
|
|
||||||
return $bExists;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* e.g. 9.6, 10, 11.2
|
|
||||||
*
|
|
||||||
* @return float
|
|
||||||
*/
|
|
||||||
public function getPostgresVersion()
|
|
||||||
{
|
|
||||||
$sVersionString = $this->getOne('SHOW server_version_num');
|
|
||||||
preg_match('#([0-9]?[0-9])([0-9][0-9])[0-9][0-9]#', $sVersionString, $aMatches);
|
|
||||||
return (float) ($aMatches[1].'.'.$aMatches[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* e.g. 2, 2.2
|
|
||||||
*
|
|
||||||
* @return float
|
|
||||||
*/
|
|
||||||
public function getPostgisVersion()
|
|
||||||
{
|
|
||||||
$sVersionString = $this->getOne('select postgis_lib_version()');
|
|
||||||
preg_match('#^([0-9]+)[.]([0-9]+)[.]#', $sVersionString, $aMatches);
|
|
||||||
return (float) ($aMatches[1].'.'.$aMatches[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an associate array of postgresql database connection settings. Keys can
|
|
||||||
* be 'database', 'hostspec', 'port', 'username', 'password'.
|
|
||||||
* Returns empty array on failure, thus check if at least 'database' is set.
|
|
||||||
*
|
|
||||||
* @return array[]
|
|
||||||
*/
|
|
||||||
public static function parseDSN($sDSN)
|
|
||||||
{
|
|
||||||
// https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php
|
|
||||||
$aInfo = array();
|
|
||||||
if (preg_match('/^pgsql:(.+)$/', $sDSN, $aMatches)) {
|
|
||||||
foreach (explode(';', $aMatches[1]) as $sKeyVal) {
|
|
||||||
list($sKey, $sVal) = explode('=', $sKeyVal, 2);
|
|
||||||
if ($sKey == 'host') {
|
|
||||||
$sKey = 'hostspec';
|
|
||||||
} elseif ($sKey == 'dbname') {
|
|
||||||
$sKey = 'database';
|
|
||||||
} elseif ($sKey == 'user') {
|
|
||||||
$sKey = 'username';
|
|
||||||
}
|
|
||||||
$aInfo[$sKey] = $sVal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $aInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes an array of settings and return the DNS string. Key names can be
|
|
||||||
* 'database', 'hostspec', 'port', 'username', 'password' but aliases
|
|
||||||
* 'dbname', 'host' and 'user' are also supported.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static function generateDSN($aInfo)
|
|
||||||
{
|
|
||||||
$sDSN = sprintf(
|
|
||||||
'pgsql:host=%s;port=%s;dbname=%s;user=%s;password=%s;',
|
|
||||||
$aInfo['host'] ?? $aInfo['hostspec'] ?? '',
|
|
||||||
$aInfo['port'] ?? '',
|
|
||||||
$aInfo['dbname'] ?? $aInfo['database'] ?? '',
|
|
||||||
$aInfo['user'] ?? '',
|
|
||||||
$aInfo['password'] ?? ''
|
|
||||||
);
|
|
||||||
$sDSN = preg_replace('/\b\w+=;/', '', $sDSN);
|
|
||||||
$sDSN = preg_replace('/;\Z/', '', $sDSN);
|
|
||||||
|
|
||||||
return $sDSN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
class DatabaseError extends \Exception
|
|
||||||
{
|
|
||||||
|
|
||||||
public function __construct($message, $code, $previous, $oPDOErr, $sSql = null)
|
|
||||||
{
|
|
||||||
parent::__construct($message, $code, $previous);
|
|
||||||
// https://secure.php.net/manual/en/class.pdoexception.php
|
|
||||||
$this->oPDOErr = $oPDOErr;
|
|
||||||
$this->sSql = $sSql;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSqlError()
|
|
||||||
{
|
|
||||||
return $this->oPDOErr->getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSqlDebugDump()
|
|
||||||
{
|
|
||||||
if (CONST_Debug) {
|
|
||||||
return var_export($this->oPDOErr, true);
|
|
||||||
} else {
|
|
||||||
return $this->sSql;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
class Debug
|
|
||||||
{
|
|
||||||
public static function newFunction($sHeading)
|
|
||||||
{
|
|
||||||
echo "<pre><h2>Debug output for $sHeading</h2></pre>\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function newSection($sHeading)
|
|
||||||
{
|
|
||||||
echo "<hr><pre><h3>$sHeading</h3></pre>\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function printVar($sHeading, $mVar)
|
|
||||||
{
|
|
||||||
echo '<pre><b>'.$sHeading. ':</b> ';
|
|
||||||
Debug::outputVar($mVar, str_repeat(' ', strlen($sHeading) + 3));
|
|
||||||
echo "</pre>\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function fmtArrayVals($aArr)
|
|
||||||
{
|
|
||||||
return array('__debug_format' => 'array_vals', 'data' => $aArr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function printDebugArray($sHeading, $oVar)
|
|
||||||
{
|
|
||||||
|
|
||||||
if ($oVar === null) {
|
|
||||||
Debug::printVar($sHeading, 'null');
|
|
||||||
} else {
|
|
||||||
Debug::printVar($sHeading, $oVar->debugInfo());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function printDebugTable($sHeading, $aVar)
|
|
||||||
{
|
|
||||||
echo '<b>'.$sHeading.":</b>\n";
|
|
||||||
echo "<table border='1'>\n";
|
|
||||||
if (!empty($aVar)) {
|
|
||||||
echo " <tr>\n";
|
|
||||||
$aKeys = array();
|
|
||||||
$aInfo = reset($aVar);
|
|
||||||
if (!is_array($aInfo)) {
|
|
||||||
$aInfo = $aInfo->debugInfo();
|
|
||||||
}
|
|
||||||
foreach ($aInfo as $sKey => $mVal) {
|
|
||||||
echo ' <th><small>'.$sKey.'</small></th>'."\n";
|
|
||||||
$aKeys[] = $sKey;
|
|
||||||
}
|
|
||||||
echo " </tr>\n";
|
|
||||||
foreach ($aVar as $oRow) {
|
|
||||||
$aInfo = $oRow;
|
|
||||||
if (!is_array($oRow)) {
|
|
||||||
$aInfo = $oRow->debugInfo();
|
|
||||||
}
|
|
||||||
echo " <tr>\n";
|
|
||||||
foreach ($aKeys as $sKey) {
|
|
||||||
echo ' <td><pre>';
|
|
||||||
if (isset($aInfo[$sKey])) {
|
|
||||||
Debug::outputVar($aInfo[$sKey], '');
|
|
||||||
}
|
|
||||||
echo '</pre></td>'."\n";
|
|
||||||
}
|
|
||||||
echo " </tr>\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
echo "</table>\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function printGroupedSearch($aSearches, $aWordsIDs)
|
|
||||||
{
|
|
||||||
echo '<table border="1">';
|
|
||||||
echo '<tr><th>rank</th><th>Name Tokens</th><th>Name Not</th>';
|
|
||||||
echo '<th>Address Tokens</th><th>Address Not</th>';
|
|
||||||
echo '<th>country</th><th>operator</th>';
|
|
||||||
echo '<th>class</th><th>type</th><th>postcode</th><th>housenumber</th></tr>';
|
|
||||||
foreach ($aSearches as $aRankedSet) {
|
|
||||||
foreach ($aRankedSet as $aRow) {
|
|
||||||
$aRow->dumpAsHtmlTableRow($aWordsIDs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
echo '</table>';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function printGroupTable($sHeading, $aVar)
|
|
||||||
{
|
|
||||||
echo '<b>'.$sHeading.":</b>\n";
|
|
||||||
echo "<table border='1'>\n";
|
|
||||||
if (!empty($aVar)) {
|
|
||||||
echo " <tr>\n";
|
|
||||||
echo ' <th><small>Group</small></th>'."\n";
|
|
||||||
$aKeys = array();
|
|
||||||
$aInfo = reset($aVar)[0];
|
|
||||||
if (!is_array($aInfo)) {
|
|
||||||
$aInfo = $aInfo->debugInfo();
|
|
||||||
}
|
|
||||||
foreach ($aInfo as $sKey => $mVal) {
|
|
||||||
echo ' <th><small>'.$sKey.'</small></th>'."\n";
|
|
||||||
$aKeys[] = $sKey;
|
|
||||||
}
|
|
||||||
echo " </tr>\n";
|
|
||||||
foreach ($aVar as $sGrpKey => $aGroup) {
|
|
||||||
foreach ($aGroup as $oRow) {
|
|
||||||
$aInfo = $oRow;
|
|
||||||
if (!is_array($oRow)) {
|
|
||||||
$aInfo = $oRow->debugInfo();
|
|
||||||
}
|
|
||||||
echo " <tr>\n";
|
|
||||||
echo ' <td><pre>'.$sGrpKey.'</pre></td>'."\n";
|
|
||||||
foreach ($aKeys as $sKey) {
|
|
||||||
echo ' <td><pre>';
|
|
||||||
if (!empty($aInfo[$sKey])) {
|
|
||||||
Debug::outputVar($aInfo[$sKey], '');
|
|
||||||
}
|
|
||||||
echo '</pre></td>'."\n";
|
|
||||||
}
|
|
||||||
echo " </tr>\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
echo "</table>\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function printSQL($sSQL)
|
|
||||||
{
|
|
||||||
echo '<p><tt><b>'.date('c').'</b> <font color="#aaa">'.htmlspecialchars($sSQL, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401).'</font></tt></p>'."\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function outputVar($mVar, $sPreNL)
|
|
||||||
{
|
|
||||||
if (is_array($mVar) && !isset($mVar['__debug_format'])) {
|
|
||||||
$sPre = '';
|
|
||||||
foreach ($mVar as $mKey => $aValue) {
|
|
||||||
echo $sPre;
|
|
||||||
$iKeyLen = Debug::outputSimpleVar($mKey);
|
|
||||||
echo ' => ';
|
|
||||||
Debug::outputVar(
|
|
||||||
$aValue,
|
|
||||||
$sPreNL.str_repeat(' ', $iKeyLen + 4)
|
|
||||||
);
|
|
||||||
$sPre = "\n".$sPreNL;
|
|
||||||
}
|
|
||||||
} elseif (is_array($mVar) && isset($mVar['__debug_format'])) {
|
|
||||||
if (!empty($mVar['data'])) {
|
|
||||||
$sPre = '';
|
|
||||||
foreach ($mVar['data'] as $mValue) {
|
|
||||||
echo $sPre;
|
|
||||||
Debug::outputSimpleVar($mValue);
|
|
||||||
$sPre = ', ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} elseif (is_object($mVar) && method_exists($mVar, 'debugInfo')) {
|
|
||||||
Debug::outputVar($mVar->debugInfo(), $sPreNL);
|
|
||||||
} elseif (is_a($mVar, 'stdClass')) {
|
|
||||||
Debug::outputVar(json_decode(json_encode($mVar), true), $sPreNL);
|
|
||||||
} else {
|
|
||||||
Debug::outputSimpleVar($mVar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function outputSimpleVar($mVar)
|
|
||||||
{
|
|
||||||
if (is_bool($mVar)) {
|
|
||||||
echo '<i>'.($mVar ? 'True' : 'False').'</i>';
|
|
||||||
return $mVar ? 4 : 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_string($mVar)) {
|
|
||||||
$sOut = "'$mVar'";
|
|
||||||
} else {
|
|
||||||
$sOut = (string)$mVar;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo htmlspecialchars($sOut, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401);
|
|
||||||
return strlen($sOut);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
class Debug
|
|
||||||
{
|
|
||||||
public static function __callStatic($name, $arguments)
|
|
||||||
{
|
|
||||||
// nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,938 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/PlaceLookup.php');
|
|
||||||
require_once(CONST_LibDir.'/Phrase.php');
|
|
||||||
require_once(CONST_LibDir.'/ReverseGeocode.php');
|
|
||||||
require_once(CONST_LibDir.'/SearchDescription.php');
|
|
||||||
require_once(CONST_LibDir.'/SearchContext.php');
|
|
||||||
require_once(CONST_LibDir.'/SearchPosition.php');
|
|
||||||
require_once(CONST_LibDir.'/TokenList.php');
|
|
||||||
require_once(CONST_TokenizerDir.'/tokenizer.php');
|
|
||||||
|
|
||||||
class Geocode
|
|
||||||
{
|
|
||||||
protected $oDB;
|
|
||||||
|
|
||||||
protected $oPlaceLookup;
|
|
||||||
protected $oTokenizer;
|
|
||||||
|
|
||||||
protected $aLangPrefOrder = array();
|
|
||||||
|
|
||||||
protected $aExcludePlaceIDs = array();
|
|
||||||
|
|
||||||
protected $iLimit = 20;
|
|
||||||
protected $iFinalLimit = 10;
|
|
||||||
protected $iOffset = 0;
|
|
||||||
protected $bFallback = false;
|
|
||||||
|
|
||||||
protected $aCountryCodes = false;
|
|
||||||
|
|
||||||
protected $bBoundedSearch = false;
|
|
||||||
protected $aViewBox = false;
|
|
||||||
protected $aRoutePoints = false;
|
|
||||||
protected $aRouteWidth = false;
|
|
||||||
|
|
||||||
protected $iMaxRank = 20;
|
|
||||||
protected $iMinAddressRank = 0;
|
|
||||||
protected $iMaxAddressRank = 30;
|
|
||||||
protected $aAddressRankList = array();
|
|
||||||
|
|
||||||
protected $sAllowedTypesSQLList = false;
|
|
||||||
|
|
||||||
protected $sQuery = false;
|
|
||||||
protected $aStructuredQuery = false;
|
|
||||||
|
|
||||||
|
|
||||||
public function __construct(&$oDB)
|
|
||||||
{
|
|
||||||
$this->oDB =& $oDB;
|
|
||||||
$this->oPlaceLookup = new PlaceLookup($this->oDB);
|
|
||||||
$this->oTokenizer = new \Nominatim\Tokenizer($this->oDB);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLanguagePreference($aLangPref)
|
|
||||||
{
|
|
||||||
$this->aLangPrefOrder = $aLangPref;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMoreUrlParams()
|
|
||||||
{
|
|
||||||
if ($this->aStructuredQuery) {
|
|
||||||
$aParams = $this->aStructuredQuery;
|
|
||||||
} else {
|
|
||||||
$aParams = array('q' => $this->sQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
$aParams = array_merge($aParams, $this->oPlaceLookup->getMoreUrlParams());
|
|
||||||
|
|
||||||
if ($this->aExcludePlaceIDs) {
|
|
||||||
$aParams['exclude_place_ids'] = implode(',', $this->aExcludePlaceIDs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->bBoundedSearch) {
|
|
||||||
$aParams['bounded'] = '1';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->aCountryCodes) {
|
|
||||||
$aParams['countrycodes'] = implode(',', $this->aCountryCodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->aViewBox) {
|
|
||||||
$aParams['viewbox'] = join(',', $this->aViewBox);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLimit($iLimit = 10)
|
|
||||||
{
|
|
||||||
if ($iLimit > 50) {
|
|
||||||
$iLimit = 50;
|
|
||||||
} elseif ($iLimit < 1) {
|
|
||||||
$iLimit = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->iFinalLimit = $iLimit;
|
|
||||||
$this->iLimit = $iLimit + max($iLimit, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setFeatureType($sFeatureType)
|
|
||||||
{
|
|
||||||
switch ($sFeatureType) {
|
|
||||||
case 'country':
|
|
||||||
$this->setRankRange(4, 4);
|
|
||||||
break;
|
|
||||||
case 'state':
|
|
||||||
$this->setRankRange(8, 8);
|
|
||||||
break;
|
|
||||||
case 'city':
|
|
||||||
$this->setRankRange(14, 16);
|
|
||||||
break;
|
|
||||||
case 'settlement':
|
|
||||||
$this->setRankRange(8, 20);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setRankRange($iMin, $iMax)
|
|
||||||
{
|
|
||||||
$this->iMinAddressRank = $iMin;
|
|
||||||
$this->iMaxAddressRank = $iMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setViewbox($aViewbox)
|
|
||||||
{
|
|
||||||
$aBox = array_map('floatval', $aViewbox);
|
|
||||||
|
|
||||||
$this->aViewBox[0] = max(-180.0, min($aBox[0], $aBox[2]));
|
|
||||||
$this->aViewBox[1] = max(-90.0, min($aBox[1], $aBox[3]));
|
|
||||||
$this->aViewBox[2] = min(180.0, max($aBox[0], $aBox[2]));
|
|
||||||
$this->aViewBox[3] = min(90.0, max($aBox[1], $aBox[3]));
|
|
||||||
|
|
||||||
if ($this->aViewBox[2] - $this->aViewBox[0] < 0.000000001
|
|
||||||
|| $this->aViewBox[3] - $this->aViewBox[1] < 0.000000001
|
|
||||||
) {
|
|
||||||
userError("Bad parameter 'viewbox'. Not a box.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function viewboxImportanceFactor($fX, $fY)
|
|
||||||
{
|
|
||||||
if (!$this->aViewBox) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fWidth = ($this->aViewBox[2] - $this->aViewBox[0])/2;
|
|
||||||
$fHeight = ($this->aViewBox[3] - $this->aViewBox[1])/2;
|
|
||||||
|
|
||||||
$fXDist = abs($fX - ($this->aViewBox[0] + $this->aViewBox[2])/2);
|
|
||||||
$fYDist = abs($fY - ($this->aViewBox[1] + $this->aViewBox[3])/2);
|
|
||||||
|
|
||||||
if ($fXDist <= $fWidth && $fYDist <= $fHeight) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($fXDist <= $fWidth * 3 && $fYDist <= 3 * $fHeight) {
|
|
||||||
return 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setQuery($sQueryString)
|
|
||||||
{
|
|
||||||
$this->sQuery = $sQueryString;
|
|
||||||
$this->aStructuredQuery = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getQueryString()
|
|
||||||
{
|
|
||||||
return $this->sQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function loadParamArray($oParams, $sForceGeometryType = null)
|
|
||||||
{
|
|
||||||
$this->bBoundedSearch = $oParams->getBool('bounded', $this->bBoundedSearch);
|
|
||||||
|
|
||||||
$this->setLimit($oParams->getInt('limit', $this->iFinalLimit));
|
|
||||||
$this->iOffset = $oParams->getInt('offset', $this->iOffset);
|
|
||||||
|
|
||||||
$this->bFallback = $oParams->getBool('fallback', $this->bFallback);
|
|
||||||
|
|
||||||
// List of excluded Place IDs - used for more accurate pageing
|
|
||||||
$sExcluded = $oParams->getStringList('exclude_place_ids');
|
|
||||||
if ($sExcluded) {
|
|
||||||
foreach ($sExcluded as $iExcludedPlaceID) {
|
|
||||||
$iExcludedPlaceID = (int)$iExcludedPlaceID;
|
|
||||||
if ($iExcludedPlaceID) {
|
|
||||||
$aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($aExcludePlaceIDs)) {
|
|
||||||
$this->aExcludePlaceIDs = $aExcludePlaceIDs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only certain ranks of feature
|
|
||||||
$sFeatureType = $oParams->getString('featureType');
|
|
||||||
if (!$sFeatureType) {
|
|
||||||
$sFeatureType = $oParams->getString('featuretype');
|
|
||||||
}
|
|
||||||
if ($sFeatureType) {
|
|
||||||
$this->setFeatureType($sFeatureType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Country code list
|
|
||||||
$sCountries = $oParams->getStringList('countrycodes');
|
|
||||||
if ($sCountries) {
|
|
||||||
foreach ($sCountries as $sCountryCode) {
|
|
||||||
if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode)) {
|
|
||||||
$aCountries[] = strtolower($sCountryCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isset($aCountries)) {
|
|
||||||
$this->aCountryCodes = $aCountries;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$aViewbox = $oParams->getStringList('viewboxlbrt');
|
|
||||||
if ($aViewbox) {
|
|
||||||
if (count($aViewbox) != 4) {
|
|
||||||
userError("Bad parameter 'viewboxlbrt'. Expected 4 coordinates.");
|
|
||||||
}
|
|
||||||
$this->setViewbox($aViewbox);
|
|
||||||
} else {
|
|
||||||
$aViewbox = $oParams->getStringList('viewbox');
|
|
||||||
if ($aViewbox) {
|
|
||||||
if (count($aViewbox) != 4) {
|
|
||||||
userError("Bad parameter 'viewbox'. Expected 4 coordinates.");
|
|
||||||
}
|
|
||||||
$this->setViewBox($aViewbox);
|
|
||||||
} else {
|
|
||||||
$aRoute = $oParams->getStringList('route');
|
|
||||||
$fRouteWidth = $oParams->getFloat('routewidth');
|
|
||||||
if ($aRoute && $fRouteWidth) {
|
|
||||||
$this->aRoutePoints = $aRoute;
|
|
||||||
$this->aRouteWidth = $fRouteWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->oPlaceLookup->loadParamArray($oParams, $sForceGeometryType);
|
|
||||||
$this->oPlaceLookup->setIncludeAddressDetails($oParams->getBool('addressdetails', false));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setQueryFromParams($oParams)
|
|
||||||
{
|
|
||||||
// Search query
|
|
||||||
$sQuery = $oParams->getString('q');
|
|
||||||
if (!$sQuery) {
|
|
||||||
$this->setStructuredQuery(
|
|
||||||
$oParams->getString('amenity'),
|
|
||||||
$oParams->getString('street'),
|
|
||||||
$oParams->getString('city'),
|
|
||||||
$oParams->getString('county'),
|
|
||||||
$oParams->getString('state'),
|
|
||||||
$oParams->getString('country'),
|
|
||||||
$oParams->getString('postalcode')
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$this->setQuery($sQuery);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function loadStructuredAddressElement($sValue, $sKey, $iNewMinAddressRank, $iNewMaxAddressRank, $aItemListValues)
|
|
||||||
{
|
|
||||||
$sValue = trim($sValue);
|
|
||||||
if (!$sValue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$this->aStructuredQuery[$sKey] = $sValue;
|
|
||||||
if ($this->iMinAddressRank == 0 && $this->iMaxAddressRank == 30) {
|
|
||||||
$this->iMinAddressRank = $iNewMinAddressRank;
|
|
||||||
$this->iMaxAddressRank = $iNewMaxAddressRank;
|
|
||||||
}
|
|
||||||
if ($aItemListValues) {
|
|
||||||
$this->aAddressRankList = array_merge($this->aAddressRankList, $aItemListValues);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setStructuredQuery($sAmenity = false, $sStreet = false, $sCity = false, $sCounty = false, $sState = false, $sCountry = false, $sPostalCode = false)
|
|
||||||
{
|
|
||||||
$this->sQuery = false;
|
|
||||||
|
|
||||||
// Reset
|
|
||||||
$this->iMinAddressRank = 0;
|
|
||||||
$this->iMaxAddressRank = 30;
|
|
||||||
$this->aAddressRankList = array();
|
|
||||||
|
|
||||||
$this->aStructuredQuery = array();
|
|
||||||
$this->sAllowedTypesSQLList = false;
|
|
||||||
|
|
||||||
$this->loadStructuredAddressElement($sAmenity, 'amenity', 26, 30, false);
|
|
||||||
$this->loadStructuredAddressElement($sStreet, 'street', 26, 30, false);
|
|
||||||
$this->loadStructuredAddressElement($sCity, 'city', 14, 24, false);
|
|
||||||
$this->loadStructuredAddressElement($sCounty, 'county', 9, 13, false);
|
|
||||||
$this->loadStructuredAddressElement($sState, 'state', 8, 8, false);
|
|
||||||
$this->loadStructuredAddressElement($sPostalCode, 'postalcode', 5, 11, array(5, 11));
|
|
||||||
$this->loadStructuredAddressElement($sCountry, 'country', 4, 4, false);
|
|
||||||
|
|
||||||
if (!empty($this->aStructuredQuery)) {
|
|
||||||
$this->sQuery = join(', ', $this->aStructuredQuery);
|
|
||||||
if ($this->iMaxAddressRank < 30) {
|
|
||||||
$this->sAllowedTypesSQLList = '(\'place\',\'boundary\')';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function fallbackStructuredQuery()
|
|
||||||
{
|
|
||||||
$aParams = $this->aStructuredQuery;
|
|
||||||
|
|
||||||
if (!$aParams || count($aParams) == 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$aOrderToFallback = array('postalcode', 'street', 'city', 'county', 'state');
|
|
||||||
|
|
||||||
foreach ($aOrderToFallback as $sType) {
|
|
||||||
if (isset($aParams[$sType])) {
|
|
||||||
unset($aParams[$sType]);
|
|
||||||
$this->setStructuredQuery(@$aParams['amenity'], @$aParams['street'], @$aParams['city'], @$aParams['county'], @$aParams['state'], @$aParams['country'], @$aParams['postalcode']);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getGroupedSearches($aSearches, $aPhrases, $oValidTokens)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
Calculate all searches using oValidTokens i.e.
|
|
||||||
'Wodsworth Road, Sheffield' =>
|
|
||||||
|
|
||||||
Phrase Wordset
|
|
||||||
0 0 (wodsworth road)
|
|
||||||
0 1 (wodsworth)(road)
|
|
||||||
1 0 (sheffield)
|
|
||||||
|
|
||||||
Score how good the search is so they can be ordered
|
|
||||||
*/
|
|
||||||
foreach ($aPhrases as $iPhrase => $oPhrase) {
|
|
||||||
$aNewPhraseSearches = array();
|
|
||||||
$oPosition = new SearchPosition(
|
|
||||||
$oPhrase->getPhraseType(),
|
|
||||||
$iPhrase,
|
|
||||||
count($aPhrases)
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach ($oPhrase->getWordSets() as $aWordset) {
|
|
||||||
$aWordsetSearches = $aSearches;
|
|
||||||
|
|
||||||
// Add all words from this wordset
|
|
||||||
foreach ($aWordset as $iToken => $sToken) {
|
|
||||||
$aNewWordsetSearches = array();
|
|
||||||
$oPosition->setTokenPosition($iToken, count($aWordset));
|
|
||||||
|
|
||||||
foreach ($aWordsetSearches as $oCurrentSearch) {
|
|
||||||
foreach ($oValidTokens->get($sToken) as $oSearchTerm) {
|
|
||||||
if ($oSearchTerm->isExtendable($oCurrentSearch, $oPosition)) {
|
|
||||||
$aNewSearches = $oSearchTerm->extendSearch(
|
|
||||||
$oCurrentSearch,
|
|
||||||
$oPosition
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach ($aNewSearches as $oSearch) {
|
|
||||||
if ($oSearch->getRank() < $this->iMaxRank) {
|
|
||||||
$aNewWordsetSearches[] = $oSearch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Sort and cut
|
|
||||||
usort($aNewWordsetSearches, array('Nominatim\SearchDescription', 'bySearchRank'));
|
|
||||||
$aWordsetSearches = array_slice($aNewWordsetSearches, 0, 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
$aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches);
|
|
||||||
usort($aNewPhraseSearches, array('Nominatim\SearchDescription', 'bySearchRank'));
|
|
||||||
|
|
||||||
$aSearchHash = array();
|
|
||||||
foreach ($aNewPhraseSearches as $iSearch => $aSearch) {
|
|
||||||
$sHash = serialize($aSearch);
|
|
||||||
if (isset($aSearchHash[$sHash])) {
|
|
||||||
unset($aNewPhraseSearches[$iSearch]);
|
|
||||||
} else {
|
|
||||||
$aSearchHash[$sHash] = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$aNewPhraseSearches = array_slice($aNewPhraseSearches, 0, 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-group the searches by their score, junk anything over 20 as just not worth trying
|
|
||||||
$aGroupedSearches = array();
|
|
||||||
foreach ($aNewPhraseSearches as $aSearch) {
|
|
||||||
$iRank = $aSearch->getRank();
|
|
||||||
if ($iRank < $this->iMaxRank) {
|
|
||||||
if (!isset($aGroupedSearches[$iRank])) {
|
|
||||||
$aGroupedSearches[$iRank] = array();
|
|
||||||
}
|
|
||||||
$aGroupedSearches[$iRank][] = $aSearch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ksort($aGroupedSearches);
|
|
||||||
|
|
||||||
$iSearchCount = 0;
|
|
||||||
$aSearches = array();
|
|
||||||
foreach ($aGroupedSearches as $aNewSearches) {
|
|
||||||
$iSearchCount += count($aNewSearches);
|
|
||||||
$aSearches = array_merge($aSearches, $aNewSearches);
|
|
||||||
if ($iSearchCount > 50) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Revisit searches, drop bad searches and give penalty to unlikely combinations.
|
|
||||||
$aGroupedSearches = array();
|
|
||||||
foreach ($aSearches as $oSearch) {
|
|
||||||
if (!$oSearch->isValidSearch()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$iRank = $oSearch->getRank();
|
|
||||||
if (!isset($aGroupedSearches[$iRank])) {
|
|
||||||
$aGroupedSearches[$iRank] = array();
|
|
||||||
}
|
|
||||||
$aGroupedSearches[$iRank][] = $oSearch;
|
|
||||||
}
|
|
||||||
ksort($aGroupedSearches);
|
|
||||||
|
|
||||||
return $aGroupedSearches;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Perform the actual query lookup.
|
|
||||||
|
|
||||||
Returns an ordered list of results, each with the following fields:
|
|
||||||
osm_type: type of corresponding OSM object
|
|
||||||
N - node
|
|
||||||
W - way
|
|
||||||
R - relation
|
|
||||||
P - postcode (internally computed)
|
|
||||||
osm_id: id of corresponding OSM object
|
|
||||||
class: general object class (corresponds to tag key of primary OSM tag)
|
|
||||||
type: subclass of object (corresponds to tag value of primary OSM tag)
|
|
||||||
admin_level: see https://wiki.openstreetmap.org/wiki/Admin_level
|
|
||||||
rank_search: rank in search hierarchy
|
|
||||||
(see also https://wiki.openstreetmap.org/wiki/Nominatim/Development_overview#Country_to_street_level)
|
|
||||||
rank_address: rank in address hierarchy (determines orer in address)
|
|
||||||
place_id: internal key (may differ between different instances)
|
|
||||||
country_code: ISO country code
|
|
||||||
langaddress: localized full address
|
|
||||||
placename: localized name of object
|
|
||||||
ref: content of ref tag (if available)
|
|
||||||
lon: longitude
|
|
||||||
lat: latitude
|
|
||||||
importance: importance of place based on Wikipedia link count
|
|
||||||
addressimportance: cumulated importance of address elements
|
|
||||||
extra_place: type of place (for admin boundaries, if there is a place tag)
|
|
||||||
aBoundingBox: bounding Box
|
|
||||||
label: short description of the object class/type (English only)
|
|
||||||
name: full name (currently the same as langaddress)
|
|
||||||
foundorder: secondary ordering for places with same importance
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
public function lookup()
|
|
||||||
{
|
|
||||||
Debug::newFunction('Geocode::lookup');
|
|
||||||
if (!$this->sQuery && !$this->aStructuredQuery) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::printDebugArray('Geocode', $this);
|
|
||||||
|
|
||||||
$oCtx = new SearchContext();
|
|
||||||
|
|
||||||
if ($this->aRoutePoints) {
|
|
||||||
$oCtx->setViewboxFromRoute(
|
|
||||||
$this->oDB,
|
|
||||||
$this->aRoutePoints,
|
|
||||||
$this->aRouteWidth,
|
|
||||||
$this->bBoundedSearch
|
|
||||||
);
|
|
||||||
} elseif ($this->aViewBox) {
|
|
||||||
$oCtx->setViewboxFromBox($this->aViewBox, $this->bBoundedSearch);
|
|
||||||
}
|
|
||||||
if ($this->aExcludePlaceIDs) {
|
|
||||||
$oCtx->setExcludeList($this->aExcludePlaceIDs);
|
|
||||||
}
|
|
||||||
if ($this->aCountryCodes) {
|
|
||||||
$oCtx->setCountryList($this->aCountryCodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::newSection('Query Preprocessing');
|
|
||||||
|
|
||||||
$sQuery = $this->sQuery;
|
|
||||||
if (!preg_match('//u', $sQuery)) {
|
|
||||||
userError('Query string is not UTF-8 encoded.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do we have anything that looks like a lat/lon pair?
|
|
||||||
$sQuery = $oCtx->setNearPointFromQuery($sQuery);
|
|
||||||
|
|
||||||
if ($sQuery || $this->aStructuredQuery) {
|
|
||||||
// Start with a single blank search
|
|
||||||
$aSearches = array(new SearchDescription($oCtx));
|
|
||||||
|
|
||||||
if ($sQuery) {
|
|
||||||
$sQuery = $aSearches[0]->extractKeyValuePairs($sQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
$sSpecialTerm = '';
|
|
||||||
if ($sQuery) {
|
|
||||||
preg_match_all(
|
|
||||||
'/\\[([\\w ]*)\\]/u',
|
|
||||||
$sQuery,
|
|
||||||
$aSpecialTermsRaw,
|
|
||||||
PREG_SET_ORDER
|
|
||||||
);
|
|
||||||
if (!empty($aSpecialTermsRaw)) {
|
|
||||||
Debug::printVar('Special terms', $aSpecialTermsRaw);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($aSpecialTermsRaw as $aSpecialTerm) {
|
|
||||||
$sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery);
|
|
||||||
if (!$sSpecialTerm) {
|
|
||||||
$sSpecialTerm = $aSpecialTerm[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!$sSpecialTerm && $this->aStructuredQuery
|
|
||||||
&& isset($this->aStructuredQuery['amenity'])) {
|
|
||||||
$sSpecialTerm = $this->aStructuredQuery['amenity'];
|
|
||||||
unset($this->aStructuredQuery['amenity']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($sSpecialTerm && !$aSearches[0]->hasOperator()) {
|
|
||||||
$aTokens = $this->oTokenizer->tokensForSpecialTerm($sSpecialTerm);
|
|
||||||
|
|
||||||
if (!empty($aTokens)) {
|
|
||||||
$aNewSearches = array();
|
|
||||||
$oPosition = new SearchPosition('', 0, 1);
|
|
||||||
$oPosition->setTokenPosition(0, 1);
|
|
||||||
|
|
||||||
foreach ($aSearches as $oSearch) {
|
|
||||||
foreach ($aTokens as $oToken) {
|
|
||||||
$aNewSearches = array_merge(
|
|
||||||
$aNewSearches,
|
|
||||||
$oToken->extendSearch($oSearch, $oPosition)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$aSearches = $aNewSearches;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split query into phrases
|
|
||||||
// Commas are used to reduce the search space by indicating where phrases split
|
|
||||||
$aPhrases = array();
|
|
||||||
if ($this->aStructuredQuery) {
|
|
||||||
foreach ($this->aStructuredQuery as $iPhrase => $sPhrase) {
|
|
||||||
$aPhrases[] = new Phrase($sPhrase, $iPhrase);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
foreach (explode(',', $sQuery) as $sPhrase) {
|
|
||||||
$aPhrases[] = new Phrase($sPhrase, '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::printDebugArray('Search context', $oCtx);
|
|
||||||
Debug::printDebugArray('Base search', empty($aSearches) ? null : $aSearches[0]);
|
|
||||||
|
|
||||||
Debug::newSection('Tokenization');
|
|
||||||
$oValidTokens = $this->oTokenizer->extractTokensFromPhrases($aPhrases);
|
|
||||||
|
|
||||||
if ($oValidTokens->count() > 0) {
|
|
||||||
$oCtx->setFullNameWords($oValidTokens->getFullWordIDs());
|
|
||||||
|
|
||||||
$aPhrases = array_filter($aPhrases, function ($oPhrase) {
|
|
||||||
return $oPhrase->getWordSets() !== null;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Any words that have failed completely?
|
|
||||||
// TODO: suggestions
|
|
||||||
|
|
||||||
Debug::printGroupTable('Valid Tokens', $oValidTokens->debugInfo());
|
|
||||||
Debug::printDebugTable('Phrases', $aPhrases);
|
|
||||||
|
|
||||||
Debug::newSection('Search candidates');
|
|
||||||
|
|
||||||
$aGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens);
|
|
||||||
|
|
||||||
if (!$this->aStructuredQuery) {
|
|
||||||
// Reverse phrase array and also reverse the order of the wordsets in
|
|
||||||
// the first and final phrase. Don't bother about phrases in the middle
|
|
||||||
// because order in the address doesn't matter.
|
|
||||||
$aPhrases = array_reverse($aPhrases);
|
|
||||||
$aPhrases[0]->invertWordSets();
|
|
||||||
if (count($aPhrases) > 1) {
|
|
||||||
$aPhrases[count($aPhrases)-1]->invertWordSets();
|
|
||||||
}
|
|
||||||
$aReverseGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens);
|
|
||||||
|
|
||||||
foreach ($aReverseGroupedSearches as $aSearches) {
|
|
||||||
foreach ($aSearches as $aSearch) {
|
|
||||||
if (!isset($aGroupedSearches[$aSearch->getRank()])) {
|
|
||||||
$aGroupedSearches[$aSearch->getRank()] = array();
|
|
||||||
}
|
|
||||||
$aGroupedSearches[$aSearch->getRank()][] = $aSearch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ksort($aGroupedSearches);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Re-group the searches by their score, junk anything over 20 as just not worth trying
|
|
||||||
$aGroupedSearches = array();
|
|
||||||
foreach ($aSearches as $aSearch) {
|
|
||||||
if ($aSearch->getRank() < $this->iMaxRank) {
|
|
||||||
if (!isset($aGroupedSearches[$aSearch->getRank()])) {
|
|
||||||
$aGroupedSearches[$aSearch->getRank()] = array();
|
|
||||||
}
|
|
||||||
$aGroupedSearches[$aSearch->getRank()][] = $aSearch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ksort($aGroupedSearches);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out duplicate searches
|
|
||||||
$aSearchHash = array();
|
|
||||||
foreach ($aGroupedSearches as $iGroup => $aSearches) {
|
|
||||||
foreach ($aSearches as $iSearch => $aSearch) {
|
|
||||||
$sHash = serialize($aSearch);
|
|
||||||
if (isset($aSearchHash[$sHash])) {
|
|
||||||
unset($aGroupedSearches[$iGroup][$iSearch]);
|
|
||||||
if (empty($aGroupedSearches[$iGroup])) {
|
|
||||||
unset($aGroupedSearches[$iGroup]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$aSearchHash[$sHash] = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::printGroupedSearch(
|
|
||||||
$aGroupedSearches,
|
|
||||||
$oValidTokens->debugTokenByWordIdList()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Start the search process
|
|
||||||
$iGroupLoop = 0;
|
|
||||||
$iQueryLoop = 0;
|
|
||||||
$aNextResults = array();
|
|
||||||
foreach ($aGroupedSearches as $iGroupedRank => $aSearches) {
|
|
||||||
$iGroupLoop++;
|
|
||||||
$aResults = $aNextResults;
|
|
||||||
foreach ($aSearches as $oSearch) {
|
|
||||||
$iQueryLoop++;
|
|
||||||
|
|
||||||
Debug::newSection("Search Loop, group $iGroupLoop, loop $iQueryLoop");
|
|
||||||
Debug::printGroupedSearch(
|
|
||||||
array($iGroupedRank => array($oSearch)),
|
|
||||||
$oValidTokens->debugTokenByWordIdList()
|
|
||||||
);
|
|
||||||
|
|
||||||
$aNewResults = $oSearch->query(
|
|
||||||
$this->oDB,
|
|
||||||
$this->iMinAddressRank,
|
|
||||||
$this->iMaxAddressRank,
|
|
||||||
$this->iLimit
|
|
||||||
);
|
|
||||||
|
|
||||||
// The same result may appear in different rounds, only
|
|
||||||
// use the one with minimal rank.
|
|
||||||
foreach ($aNewResults as $iPlace => $oRes) {
|
|
||||||
if (!isset($aResults[$iPlace])
|
|
||||||
|| $aResults[$iPlace]->iResultRank > $oRes->iResultRank) {
|
|
||||||
$aResults[$iPlace] = $oRes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($iQueryLoop > 20) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($aResults)) {
|
|
||||||
$aSplitResults = Result::splitResults($aResults);
|
|
||||||
Debug::printVar('Split results', $aSplitResults);
|
|
||||||
if ($iGroupLoop <= 4
|
|
||||||
&& reset($aSplitResults['head'])->iResultRank > 0
|
|
||||||
&& $iGroupedRank !== array_key_last($aGroupedSearches)) {
|
|
||||||
// Haven't found an exact match for the query yet.
|
|
||||||
// Therefore add result from the next group level.
|
|
||||||
$aNextResults = $aSplitResults['head'];
|
|
||||||
foreach ($aNextResults as $oRes) {
|
|
||||||
$oRes->iResultRank--;
|
|
||||||
}
|
|
||||||
foreach ($aSplitResults['tail'] as $oRes) {
|
|
||||||
$oRes->iResultRank--;
|
|
||||||
$aNextResults[$oRes->iId] = $oRes;
|
|
||||||
}
|
|
||||||
$aResults = array();
|
|
||||||
} else {
|
|
||||||
$aResults = $aSplitResults['head'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($aResults) && ($this->iMinAddressRank != 0 || $this->iMaxAddressRank != 30)) {
|
|
||||||
// Need to verify passes rank limits before dropping out of the loop (yuk!)
|
|
||||||
// reduces the number of place ids, like a filter
|
|
||||||
// rank_address is 30 for interpolated housenumbers
|
|
||||||
$aFilterSql = array();
|
|
||||||
$sPlaceIds = Result::joinIdsByTable($aResults, Result::TABLE_PLACEX);
|
|
||||||
if ($sPlaceIds) {
|
|
||||||
$sSQL = 'SELECT place_id FROM placex ';
|
|
||||||
$sSQL .= 'WHERE place_id in ('.$sPlaceIds.') ';
|
|
||||||
$sSQL .= ' AND (';
|
|
||||||
$sSQL .= " placex.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank ";
|
|
||||||
$sSQL .= " OR placex.rank_search between $this->iMinAddressRank and $this->iMaxAddressRank ";
|
|
||||||
if ($this->aAddressRankList) {
|
|
||||||
$sSQL .= ' OR placex.rank_address in ('.join(',', $this->aAddressRankList).')';
|
|
||||||
}
|
|
||||||
$sSQL .= ')';
|
|
||||||
$aFilterSql[] = $sSQL;
|
|
||||||
}
|
|
||||||
$sPlaceIds = Result::joinIdsByTable($aResults, Result::TABLE_POSTCODE);
|
|
||||||
if ($sPlaceIds) {
|
|
||||||
$sSQL = ' SELECT place_id FROM location_postcode lp ';
|
|
||||||
$sSQL .= 'WHERE place_id in ('.$sPlaceIds.') ';
|
|
||||||
$sSQL .= " AND (lp.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank ";
|
|
||||||
if ($this->aAddressRankList) {
|
|
||||||
$sSQL .= ' OR lp.rank_address in ('.join(',', $this->aAddressRankList).')';
|
|
||||||
}
|
|
||||||
$sSQL .= ') ';
|
|
||||||
$aFilterSql[] = $sSQL;
|
|
||||||
}
|
|
||||||
|
|
||||||
$aFilteredIDs = array();
|
|
||||||
if ($aFilterSql) {
|
|
||||||
$sSQL = join(' UNION ', $aFilterSql);
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
$aFilteredIDs = $this->oDB->getCol($sSQL);
|
|
||||||
}
|
|
||||||
|
|
||||||
$tempIDs = array();
|
|
||||||
foreach ($aResults as $oResult) {
|
|
||||||
if (($this->iMaxAddressRank == 30 &&
|
|
||||||
($oResult->iTable == Result::TABLE_OSMLINE
|
|
||||||
|| $oResult->iTable == Result::TABLE_TIGER))
|
|
||||||
|| in_array($oResult->iId, $aFilteredIDs)
|
|
||||||
) {
|
|
||||||
$tempIDs[$oResult->iId] = $oResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$aResults = $tempIDs;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($aResults) || $iGroupLoop > 4 || $iQueryLoop > 30) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Just interpret as a reverse geocode
|
|
||||||
$oReverse = new ReverseGeocode($this->oDB);
|
|
||||||
$oReverse->setZoom(18);
|
|
||||||
|
|
||||||
$oLookup = $oReverse->lookupPoint($oCtx->sqlNear, false);
|
|
||||||
|
|
||||||
Debug::printVar('Reverse search', $oLookup);
|
|
||||||
|
|
||||||
if ($oLookup) {
|
|
||||||
$aResults = array($oLookup->iId => $oLookup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No results? Done
|
|
||||||
if (empty($aResults)) {
|
|
||||||
if ($this->bFallback && $this->fallbackStructuredQuery()) {
|
|
||||||
return $this->lookup();
|
|
||||||
}
|
|
||||||
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->aAddressRankList) {
|
|
||||||
$this->oPlaceLookup->setAddressRankList($this->aAddressRankList);
|
|
||||||
}
|
|
||||||
$this->oPlaceLookup->setAllowedTypesSQLList($this->sAllowedTypesSQLList);
|
|
||||||
$this->oPlaceLookup->setLanguagePreference($this->aLangPrefOrder);
|
|
||||||
if ($oCtx->hasNearPoint()) {
|
|
||||||
$this->oPlaceLookup->setAnchorSql($oCtx->sqlNear);
|
|
||||||
}
|
|
||||||
|
|
||||||
$aSearchResults = $this->oPlaceLookup->lookup($aResults);
|
|
||||||
|
|
||||||
$aRecheckWords = preg_split('/\b[\s,\\-]*/u', $sQuery);
|
|
||||||
foreach ($aRecheckWords as $i => $sWord) {
|
|
||||||
if (!preg_match('/[\pL\pN]/', $sWord)) {
|
|
||||||
unset($aRecheckWords[$i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::printVar('Recheck words', $aRecheckWords);
|
|
||||||
|
|
||||||
foreach ($aSearchResults as $iIdx => $aResult) {
|
|
||||||
$fRadius = ClassTypes\getDefRadius($aResult);
|
|
||||||
|
|
||||||
$aOutlineResult = $this->oPlaceLookup->getOutlines($aResult['place_id'], $aResult['lon'], $aResult['lat'], $fRadius);
|
|
||||||
if ($aOutlineResult) {
|
|
||||||
$aResult = array_merge($aResult, $aOutlineResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is there an icon set for this type of result?
|
|
||||||
$sIcon = ClassTypes\getIconFile($aResult);
|
|
||||||
if (isset($sIcon)) {
|
|
||||||
$aResult['icon'] = $sIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sLabel = ClassTypes\getLabel($aResult);
|
|
||||||
if (isset($sLabel)) {
|
|
||||||
$aResult['label'] = $sLabel;
|
|
||||||
}
|
|
||||||
$aResult['name'] = $aResult['langaddress'];
|
|
||||||
|
|
||||||
if ($oCtx->hasNearPoint()) {
|
|
||||||
$aResult['importance'] = 0.001;
|
|
||||||
$aResult['foundorder'] = $aResult['addressimportance'];
|
|
||||||
} else {
|
|
||||||
if ($aResult['importance'] == 0) {
|
|
||||||
$aResult['importance'] = 0.0001;
|
|
||||||
}
|
|
||||||
$aResult['importance'] *= $this->viewboxImportanceFactor(
|
|
||||||
$aResult['lon'],
|
|
||||||
$aResult['lat']
|
|
||||||
);
|
|
||||||
|
|
||||||
// secondary ordering (for results with same importance (the smaller the better):
|
|
||||||
// - approximate importance of address parts
|
|
||||||
if (isset($aResult['addressimportance']) && $aResult['addressimportance']) {
|
|
||||||
$aResult['foundorder'] = -$aResult['addressimportance']/10;
|
|
||||||
} else {
|
|
||||||
$aResult['foundorder'] = -$aResult['importance'];
|
|
||||||
}
|
|
||||||
// - number of exact matches from the query
|
|
||||||
$aResult['foundorder'] -= $aResults[$aResult['place_id']]->iExactMatches;
|
|
||||||
// - importance of the class/type
|
|
||||||
$iClassImportance = ClassTypes\getImportance($aResult);
|
|
||||||
if (isset($iClassImportance)) {
|
|
||||||
$aResult['foundorder'] += 0.0001 * $iClassImportance;
|
|
||||||
} else {
|
|
||||||
$aResult['foundorder'] += 0.01;
|
|
||||||
}
|
|
||||||
// - rank
|
|
||||||
$aResult['foundorder'] -= 0.00001 * (30 - $aResult['rank_search']);
|
|
||||||
|
|
||||||
// Adjust importance for the number of exact string matches in the result
|
|
||||||
$iCountWords = 0;
|
|
||||||
$sAddress = $aResult['langaddress'];
|
|
||||||
foreach ($aRecheckWords as $i => $sWord) {
|
|
||||||
if (grapheme_stripos($sAddress, $sWord)!==false) {
|
|
||||||
$iCountWords++;
|
|
||||||
if (preg_match('/(^|,)\s*'.preg_quote($sWord, '/').'\s*(,|$)/', $sAddress)) {
|
|
||||||
$iCountWords += 0.1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0.1 is a completely arbitrary number but something in the range 0.1 to 0.5 would seem right
|
|
||||||
$aResult['importance'] = $aResult['importance'] + ($iCountWords*0.1);
|
|
||||||
}
|
|
||||||
$aSearchResults[$iIdx] = $aResult;
|
|
||||||
}
|
|
||||||
uasort($aSearchResults, 'byImportance');
|
|
||||||
Debug::printVar('Pre-filter results', $aSearchResults);
|
|
||||||
|
|
||||||
$aOSMIDDone = array();
|
|
||||||
$aClassTypeNameDone = array();
|
|
||||||
$aToFilter = $aSearchResults;
|
|
||||||
$aSearchResults = array();
|
|
||||||
|
|
||||||
foreach ($aToFilter as $aResult) {
|
|
||||||
$this->aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
|
|
||||||
if (!$this->oPlaceLookup->doDeDupe() || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
|
|
||||||
&& !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']]))
|
|
||||||
) {
|
|
||||||
$aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true;
|
|
||||||
$aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true;
|
|
||||||
$aSearchResults[] = $aResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Absolute limit on number of results
|
|
||||||
if (count($aSearchResults) >= $this->iFinalLimit) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::printVar('Post-filter results', $aSearchResults);
|
|
||||||
return $aSearchResults;
|
|
||||||
} // end lookup()
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'Query' => $this->sQuery,
|
|
||||||
'Structured query' => $this->aStructuredQuery,
|
|
||||||
'Name keys' => Debug::fmtArrayVals($this->aLangPrefOrder),
|
|
||||||
'Excluded place IDs' => Debug::fmtArrayVals($this->aExcludePlaceIDs),
|
|
||||||
'Limit (for searches)' => $this->iLimit,
|
|
||||||
'Limit (for results)'=> $this->iFinalLimit,
|
|
||||||
'Country codes' => Debug::fmtArrayVals($this->aCountryCodes),
|
|
||||||
'Bounded search' => $this->bBoundedSearch,
|
|
||||||
'Viewbox' => Debug::fmtArrayVals($this->aViewBox),
|
|
||||||
'Route points' => Debug::fmtArrayVals($this->aRoutePoints),
|
|
||||||
'Route width' => $this->aRouteWidth,
|
|
||||||
'Max rank' => $this->iMaxRank,
|
|
||||||
'Min address rank' => $this->iMinAddressRank,
|
|
||||||
'Max address rank' => $this->iMaxAddressRank,
|
|
||||||
'Address rank list' => Debug::fmtArrayVals($this->aAddressRankList)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} // end class
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
class ParameterParser
|
|
||||||
{
|
|
||||||
private $aParams;
|
|
||||||
|
|
||||||
|
|
||||||
public function __construct($aParams = null)
|
|
||||||
{
|
|
||||||
$this->aParams = ($aParams === null) ? $_GET : $aParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBool($sName, $bDefault = false)
|
|
||||||
{
|
|
||||||
if (!isset($this->aParams[$sName])
|
|
||||||
|| !is_string($this->aParams[$sName])
|
|
||||||
|| strlen($this->aParams[$sName]) == 0
|
|
||||||
) {
|
|
||||||
return $bDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (bool) $this->aParams[$sName];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getInt($sName, $bDefault = false)
|
|
||||||
{
|
|
||||||
if (!isset($this->aParams[$sName]) || is_array($this->aParams[$sName])) {
|
|
||||||
return $bDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!preg_match('/^[+-]?[0-9]+$/', $this->aParams[$sName])) {
|
|
||||||
userError("Integer number expected for parameter '$sName'");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int) $this->aParams[$sName];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFloat($sName, $bDefault = false)
|
|
||||||
{
|
|
||||||
if (!isset($this->aParams[$sName]) || is_array($this->aParams[$sName])) {
|
|
||||||
return $bDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!preg_match('/^[+-]?[0-9]*\.?[0-9]+$/', $this->aParams[$sName])) {
|
|
||||||
userError("Floating-point number expected for parameter '$sName'");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (float) $this->aParams[$sName];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getString($sName, $bDefault = false)
|
|
||||||
{
|
|
||||||
if (!isset($this->aParams[$sName])
|
|
||||||
|| !is_string($this->aParams[$sName])
|
|
||||||
|| strlen($this->aParams[$sName]) == 0
|
|
||||||
) {
|
|
||||||
return $bDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->aParams[$sName];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSet($sName, $aValues, $sDefault = false)
|
|
||||||
{
|
|
||||||
if (!isset($this->aParams[$sName])
|
|
||||||
|| !is_string($this->aParams[$sName])
|
|
||||||
|| strlen($this->aParams[$sName]) == 0
|
|
||||||
) {
|
|
||||||
return $sDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!in_array($this->aParams[$sName], $aValues, true)) {
|
|
||||||
userError("Parameter '$sName' must be one of: ".join(', ', $aValues));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->aParams[$sName];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getStringList($sName, $aDefault = false)
|
|
||||||
{
|
|
||||||
$sValue = $this->getString($sName);
|
|
||||||
|
|
||||||
if ($sValue) {
|
|
||||||
// removes all NULL, FALSE and Empty Strings but leaves 0 (zero) values
|
|
||||||
return array_values(array_filter(explode(',', $sValue), 'strlen'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPreferredLanguages($sFallback = null)
|
|
||||||
{
|
|
||||||
if ($sFallback === null && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
|
||||||
$sFallback = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$aLanguages = array();
|
|
||||||
$sLangString = $this->getString('accept-language', $sFallback);
|
|
||||||
|
|
||||||
if ($sLangString
|
|
||||||
&& preg_match_all('/(([a-z]{1,8})([-_][a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $sLangString, $aLanguagesParse, PREG_SET_ORDER)
|
|
||||||
) {
|
|
||||||
foreach ($aLanguagesParse as $iLang => $aLanguage) {
|
|
||||||
$aLanguages[$aLanguage[1]] = isset($aLanguage[5])?(float)$aLanguage[5]:1 - ($iLang/100);
|
|
||||||
if (!isset($aLanguages[$aLanguage[2]])) {
|
|
||||||
$aLanguages[$aLanguage[2]] = $aLanguages[$aLanguage[1]]/10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arsort($aLanguages);
|
|
||||||
}
|
|
||||||
if (empty($aLanguages) && CONST_Default_Language) {
|
|
||||||
$aLanguages[CONST_Default_Language] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($aLanguages as $sLanguage => $fLanguagePref) {
|
|
||||||
$this->addNameTag($aLangPrefOrder, 'name:'.$sLanguage);
|
|
||||||
}
|
|
||||||
$this->addNameTag($aLangPrefOrder, 'name');
|
|
||||||
$this->addNameTag($aLangPrefOrder, 'brand');
|
|
||||||
foreach ($aLanguages as $sLanguage => $fLanguagePref) {
|
|
||||||
$this->addNameTag($aLangPrefOrder, 'official_name:'.$sLanguage);
|
|
||||||
$this->addNameTag($aLangPrefOrder, 'short_name:'.$sLanguage);
|
|
||||||
}
|
|
||||||
$this->addNameTag($aLangPrefOrder, 'official_name');
|
|
||||||
$this->addNameTag($aLangPrefOrder, 'short_name');
|
|
||||||
$this->addNameTag($aLangPrefOrder, 'ref');
|
|
||||||
$this->addNameTag($aLangPrefOrder, 'type');
|
|
||||||
return $aLangPrefOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addNameTag(&$aLangPrefOrder, $sTag)
|
|
||||||
{
|
|
||||||
$aLangPrefOrder[$sTag] = $sTag;
|
|
||||||
$aLangPrefOrder['_place_'.$sTag] = '_place_'.$sTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasSetAny($aParamNames)
|
|
||||||
{
|
|
||||||
foreach ($aParamNames as $sName) {
|
|
||||||
if ($this->getBool($sName)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Segment of a query string.
|
|
||||||
*
|
|
||||||
* The parts of a query strings are usually separated by commas.
|
|
||||||
*/
|
|
||||||
class Phrase
|
|
||||||
{
|
|
||||||
// Complete phrase as a string (guaranteed to have no leading or trailing
|
|
||||||
// spaces).
|
|
||||||
private $sPhrase;
|
|
||||||
// Element type for structured searches.
|
|
||||||
private $sPhraseType;
|
|
||||||
// Possible segmentations of the phrase.
|
|
||||||
private $aWordSets;
|
|
||||||
|
|
||||||
public function __construct($sPhrase, $sPhraseType)
|
|
||||||
{
|
|
||||||
$this->sPhrase = trim($sPhrase);
|
|
||||||
$this->sPhraseType = $sPhraseType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the original phrase of the string.
|
|
||||||
*/
|
|
||||||
public function getPhrase()
|
|
||||||
{
|
|
||||||
return $this->sPhrase;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the element type of the phrase.
|
|
||||||
*
|
|
||||||
* @return string Pharse type if the phrase comes from a structured query
|
|
||||||
* or empty string otherwise.
|
|
||||||
*/
|
|
||||||
public function getPhraseType()
|
|
||||||
{
|
|
||||||
return $this->sPhraseType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setWordSets($aWordSets)
|
|
||||||
{
|
|
||||||
$this->aWordSets = $aWordSets;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the array of possible segmentations of the phrase.
|
|
||||||
*
|
|
||||||
* @return string[][] Array of segmentations, each consisting of an
|
|
||||||
* array of terms.
|
|
||||||
*/
|
|
||||||
public function getWordSets()
|
|
||||||
{
|
|
||||||
return $this->aWordSets;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invert the set of possible segmentations.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function invertWordSets()
|
|
||||||
{
|
|
||||||
foreach ($this->aWordSets as $i => $aSet) {
|
|
||||||
$this->aWordSets[$i] = array_reverse($aSet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'Type' => $this->sPhraseType,
|
|
||||||
'Phrase' => $this->sPhrase,
|
|
||||||
'WordSets' => $this->aWordSets
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,615 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/AddressDetails.php');
|
|
||||||
require_once(CONST_LibDir.'/Result.php');
|
|
||||||
|
|
||||||
class PlaceLookup
|
|
||||||
{
|
|
||||||
protected $oDB;
|
|
||||||
|
|
||||||
protected $aLangPrefOrderSql = "''";
|
|
||||||
|
|
||||||
protected $bAddressDetails = false;
|
|
||||||
protected $bExtraTags = false;
|
|
||||||
protected $bNameDetails = false;
|
|
||||||
|
|
||||||
protected $bIncludePolygonAsText = false;
|
|
||||||
protected $bIncludePolygonAsGeoJSON = false;
|
|
||||||
protected $bIncludePolygonAsKML = false;
|
|
||||||
protected $bIncludePolygonAsSVG = false;
|
|
||||||
protected $fPolygonSimplificationThreshold = 0.0;
|
|
||||||
|
|
||||||
protected $sAnchorSql = null;
|
|
||||||
protected $sAddressRankListSql = null;
|
|
||||||
protected $sAllowedTypesSQLList = null;
|
|
||||||
protected $bDeDupe = true;
|
|
||||||
|
|
||||||
|
|
||||||
public function __construct(&$oDB)
|
|
||||||
{
|
|
||||||
$this->oDB =& $oDB;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function doDeDupe()
|
|
||||||
{
|
|
||||||
return $this->bDeDupe;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setIncludeAddressDetails($b)
|
|
||||||
{
|
|
||||||
$this->bAddressDetails = $b;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function loadParamArray($oParams, $sGeomType = null)
|
|
||||||
{
|
|
||||||
$aLangs = $oParams->getPreferredLanguages();
|
|
||||||
$this->aLangPrefOrderSql =
|
|
||||||
'ARRAY['.join(',', $this->oDB->getDBQuotedList($aLangs)).']';
|
|
||||||
|
|
||||||
$this->bExtraTags = $oParams->getBool('extratags', false);
|
|
||||||
$this->bNameDetails = $oParams->getBool('namedetails', false);
|
|
||||||
|
|
||||||
$this->bDeDupe = $oParams->getBool('dedupe', $this->bDeDupe);
|
|
||||||
|
|
||||||
if ($sGeomType === null || $sGeomType == 'geojson') {
|
|
||||||
$this->bIncludePolygonAsGeoJSON = $oParams->getBool('polygon_geojson');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($oParams->getString('format', '') !== 'geojson') {
|
|
||||||
if ($sGeomType === null || $sGeomType == 'text') {
|
|
||||||
$this->bIncludePolygonAsText = $oParams->getBool('polygon_text');
|
|
||||||
}
|
|
||||||
if ($sGeomType === null || $sGeomType == 'kml') {
|
|
||||||
$this->bIncludePolygonAsKML = $oParams->getBool('polygon_kml');
|
|
||||||
}
|
|
||||||
if ($sGeomType === null || $sGeomType == 'svg') {
|
|
||||||
$this->bIncludePolygonAsSVG = $oParams->getBool('polygon_svg');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->fPolygonSimplificationThreshold
|
|
||||||
= $oParams->getFloat('polygon_threshold', 0.0);
|
|
||||||
|
|
||||||
$iWantedTypes =
|
|
||||||
($this->bIncludePolygonAsText ? 1 : 0) +
|
|
||||||
($this->bIncludePolygonAsGeoJSON ? 1 : 0) +
|
|
||||||
($this->bIncludePolygonAsKML ? 1 : 0) +
|
|
||||||
($this->bIncludePolygonAsSVG ? 1 : 0);
|
|
||||||
if ($iWantedTypes > CONST_PolygonOutput_MaximumTypes) {
|
|
||||||
if (CONST_PolygonOutput_MaximumTypes) {
|
|
||||||
userError('Select only '.CONST_PolygonOutput_MaximumTypes.' polygon output option');
|
|
||||||
} else {
|
|
||||||
userError('Polygon output is disabled');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMoreUrlParams()
|
|
||||||
{
|
|
||||||
$aParams = array();
|
|
||||||
|
|
||||||
if ($this->bAddressDetails) {
|
|
||||||
$aParams['addressdetails'] = '1';
|
|
||||||
}
|
|
||||||
if ($this->bExtraTags) {
|
|
||||||
$aParams['extratags'] = '1';
|
|
||||||
}
|
|
||||||
if ($this->bNameDetails) {
|
|
||||||
$aParams['namedetails'] = '1';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->bIncludePolygonAsText) {
|
|
||||||
$aParams['polygon_text'] = '1';
|
|
||||||
}
|
|
||||||
if ($this->bIncludePolygonAsGeoJSON) {
|
|
||||||
$aParams['polygon_geojson'] = '1';
|
|
||||||
}
|
|
||||||
if ($this->bIncludePolygonAsKML) {
|
|
||||||
$aParams['polygon_kml'] = '1';
|
|
||||||
}
|
|
||||||
if ($this->bIncludePolygonAsSVG) {
|
|
||||||
$aParams['polygon_svg'] = '1';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->fPolygonSimplificationThreshold > 0.0) {
|
|
||||||
$aParams['polygon_threshold'] = $this->fPolygonSimplificationThreshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->bDeDupe) {
|
|
||||||
$aParams['dedupe'] = '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setAnchorSql($sPoint)
|
|
||||||
{
|
|
||||||
$this->sAnchorSql = $sPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setAddressRankList($aList)
|
|
||||||
{
|
|
||||||
$this->sAddressRankListSql = '('.join(',', $aList).')';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setAllowedTypesSQLList($sSql)
|
|
||||||
{
|
|
||||||
$this->sAllowedTypesSQLList = $sSql;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLanguagePreference($aLangPrefOrder)
|
|
||||||
{
|
|
||||||
$this->aLangPrefOrderSql = $this->oDB->getArraySQL(
|
|
||||||
$this->oDB->getDBQuotedList($aLangPrefOrder)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addressImportanceSql($sGeometry, $sPlaceId)
|
|
||||||
{
|
|
||||||
if ($this->sAnchorSql) {
|
|
||||||
$sSQL = 'ST_Distance('.$this->sAnchorSql.','.$sGeometry.')';
|
|
||||||
} else {
|
|
||||||
$sSQL = '(SELECT max(ai_p.importance * (ai_p.rank_address + 2))';
|
|
||||||
$sSQL .= ' FROM place_addressline ai_s, placex ai_p';
|
|
||||||
$sSQL .= ' WHERE ai_s.place_id = '.$sPlaceId;
|
|
||||||
$sSQL .= ' AND ai_p.place_id = ai_s.address_place_id ';
|
|
||||||
$sSQL .= ' AND ai_s.isaddress ';
|
|
||||||
$sSQL .= ' AND ai_p.importance is not null)';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sSQL.' AS addressimportance,';
|
|
||||||
}
|
|
||||||
|
|
||||||
private function langAddressSql($sHousenumber)
|
|
||||||
{
|
|
||||||
if ($this->bAddressDetails) {
|
|
||||||
return ''; // langaddress will be computed from address details
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'get_address_by_language(place_id,'.$sHousenumber.','.$this->aLangPrefOrderSql.') AS langaddress,';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function lookupOSMID($sType, $iID)
|
|
||||||
{
|
|
||||||
$sSQL = 'select place_id from placex where osm_type = :type and osm_id = :id';
|
|
||||||
$iPlaceID = $this->oDB->getOne($sSQL, array(':type' => $sType, ':id' => $iID));
|
|
||||||
|
|
||||||
if (!$iPlaceID) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$aResults = $this->lookup(array($iPlaceID => new Result($iPlaceID)), 0, 30, true);
|
|
||||||
|
|
||||||
return empty($aResults) ? null : reset($aResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function lookup($aResults, $iMinRank = 0, $iMaxRank = 30, $bAllowLinked = false)
|
|
||||||
{
|
|
||||||
Debug::newFunction('Place lookup');
|
|
||||||
|
|
||||||
if (empty($aResults)) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
$aSubSelects = array();
|
|
||||||
|
|
||||||
$sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_PLACEX);
|
|
||||||
if ($sPlaceIDs) {
|
|
||||||
Debug::printVar('Ids from placex', $sPlaceIDs);
|
|
||||||
$sSQL = 'SELECT ';
|
|
||||||
$sSQL .= ' osm_type,';
|
|
||||||
$sSQL .= ' osm_id,';
|
|
||||||
$sSQL .= ' class,';
|
|
||||||
$sSQL .= ' type,';
|
|
||||||
$sSQL .= ' admin_level,';
|
|
||||||
$sSQL .= ' rank_search,';
|
|
||||||
$sSQL .= ' rank_address,';
|
|
||||||
$sSQL .= ' min(place_id) AS place_id,';
|
|
||||||
$sSQL .= ' min(parent_place_id) AS parent_place_id,';
|
|
||||||
$sSQL .= ' -1 as housenumber,';
|
|
||||||
$sSQL .= ' country_code,';
|
|
||||||
$sSQL .= $this->langAddressSql('-1');
|
|
||||||
$sSQL .= ' get_name_by_language(name,'.$this->aLangPrefOrderSql.') AS placename,';
|
|
||||||
$sSQL .= " get_name_by_language(name, ARRAY['ref']) AS ref,";
|
|
||||||
if ($this->bExtraTags) {
|
|
||||||
$sSQL .= 'hstore_to_json(extratags)::text AS extra,';
|
|
||||||
}
|
|
||||||
if ($this->bNameDetails) {
|
|
||||||
$sSQL .= 'hstore_to_json(name)::text AS names,';
|
|
||||||
}
|
|
||||||
$sSQL .= ' avg(ST_X(centroid)) AS lon, ';
|
|
||||||
$sSQL .= ' avg(ST_Y(centroid)) AS lat, ';
|
|
||||||
$sSQL .= ' COALESCE(importance,0.75-(rank_search::float/40)) AS importance, ';
|
|
||||||
$sSQL .= $this->addressImportanceSql(
|
|
||||||
'ST_Collect(centroid)',
|
|
||||||
'min(CASE WHEN placex.rank_search < 28 THEN placex.place_id ELSE placex.parent_place_id END)'
|
|
||||||
);
|
|
||||||
$sSQL .= " COALESCE(extratags->'place', extratags->'linked_place') AS extra_place ";
|
|
||||||
$sSQL .= ' FROM placex';
|
|
||||||
$sSQL .= " WHERE place_id in ($sPlaceIDs) ";
|
|
||||||
$sSQL .= ' AND (';
|
|
||||||
$sSQL .= " placex.rank_address between $iMinRank and $iMaxRank ";
|
|
||||||
if (14 >= $iMinRank && 14 <= $iMaxRank) {
|
|
||||||
$sSQL .= " OR (extratags->'place') = 'city'";
|
|
||||||
}
|
|
||||||
if ($this->sAddressRankListSql) {
|
|
||||||
$sSQL .= ' OR placex.rank_address in '.$this->sAddressRankListSql;
|
|
||||||
}
|
|
||||||
$sSQL .= ' ) ';
|
|
||||||
if ($this->sAllowedTypesSQLList) {
|
|
||||||
$sSQL .= 'AND placex.class in '.$this->sAllowedTypesSQLList;
|
|
||||||
}
|
|
||||||
if (!$bAllowLinked) {
|
|
||||||
$sSQL .= ' AND linked_place_id is null ';
|
|
||||||
}
|
|
||||||
$sSQL .= ' GROUP BY ';
|
|
||||||
$sSQL .= ' osm_type, ';
|
|
||||||
$sSQL .= ' osm_id, ';
|
|
||||||
$sSQL .= ' class, ';
|
|
||||||
$sSQL .= ' type, ';
|
|
||||||
$sSQL .= ' admin_level, ';
|
|
||||||
$sSQL .= ' rank_search, ';
|
|
||||||
$sSQL .= ' rank_address, ';
|
|
||||||
$sSQL .= ' housenumber,';
|
|
||||||
$sSQL .= ' country_code, ';
|
|
||||||
$sSQL .= ' importance, ';
|
|
||||||
if (!$this->bDeDupe) {
|
|
||||||
$sSQL .= 'place_id,';
|
|
||||||
}
|
|
||||||
if (!$this->bAddressDetails) {
|
|
||||||
$sSQL .= 'langaddress, ';
|
|
||||||
}
|
|
||||||
$sSQL .= ' placename, ';
|
|
||||||
$sSQL .= ' ref, ';
|
|
||||||
if ($this->bExtraTags) {
|
|
||||||
$sSQL .= 'extratags, ';
|
|
||||||
}
|
|
||||||
if ($this->bNameDetails) {
|
|
||||||
$sSQL .= 'name, ';
|
|
||||||
}
|
|
||||||
$sSQL .= ' extra_place ';
|
|
||||||
|
|
||||||
$aSubSelects[] = $sSQL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// postcode table
|
|
||||||
$sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_POSTCODE);
|
|
||||||
if ($sPlaceIDs) {
|
|
||||||
Debug::printVar('Ids from location_postcode', $sPlaceIDs);
|
|
||||||
$sSQL = 'SELECT';
|
|
||||||
$sSQL .= " 'P' as osm_type,";
|
|
||||||
$sSQL .= ' (SELECT osm_id from placex p WHERE p.place_id = lp.parent_place_id) as osm_id,';
|
|
||||||
$sSQL .= " 'place' as class, 'postcode' as type,";
|
|
||||||
$sSQL .= ' null::smallint as admin_level, rank_search, rank_address,';
|
|
||||||
$sSQL .= ' place_id, parent_place_id,';
|
|
||||||
$sSQL .= ' -1 as housenumber,';
|
|
||||||
$sSQL .= ' country_code,';
|
|
||||||
$sSQL .= $this->langAddressSql('-1');
|
|
||||||
$sSQL .= ' postcode as placename,';
|
|
||||||
$sSQL .= ' postcode as ref,';
|
|
||||||
if ($this->bExtraTags) {
|
|
||||||
$sSQL .= 'null::text AS extra,';
|
|
||||||
}
|
|
||||||
if ($this->bNameDetails) {
|
|
||||||
$sSQL .= 'null::text AS names,';
|
|
||||||
}
|
|
||||||
$sSQL .= ' ST_x(geometry) AS lon, ST_y(geometry) AS lat,';
|
|
||||||
$sSQL .= ' (0.75-(rank_search::float/40)) AS importance, ';
|
|
||||||
$sSQL .= $this->addressImportanceSql('geometry', 'lp.parent_place_id');
|
|
||||||
$sSQL .= ' null::text AS extra_place ';
|
|
||||||
$sSQL .= 'FROM location_postcode lp';
|
|
||||||
$sSQL .= " WHERE place_id in ($sPlaceIDs) ";
|
|
||||||
$sSQL .= " AND lp.rank_address between $iMinRank and $iMaxRank";
|
|
||||||
|
|
||||||
$aSubSelects[] = $sSQL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// All other tables are rank 30 only.
|
|
||||||
if ($iMaxRank == 30) {
|
|
||||||
// TIGER table
|
|
||||||
if (CONST_Use_US_Tiger_Data) {
|
|
||||||
$sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_TIGER);
|
|
||||||
if ($sPlaceIDs) {
|
|
||||||
Debug::printVar('Ids from Tiger table', $sPlaceIDs);
|
|
||||||
$sHousenumbers = Result::sqlHouseNumberTable($aResults, Result::TABLE_TIGER);
|
|
||||||
// Tiger search only if a housenumber was searched and if it was found
|
|
||||||
// (realized through a join)
|
|
||||||
$sSQL = ' SELECT ';
|
|
||||||
$sSQL .= " 'T' AS osm_type, ";
|
|
||||||
$sSQL .= ' (SELECT osm_id from placex p WHERE p.place_id=blub.parent_place_id) as osm_id, ';
|
|
||||||
$sSQL .= " 'place' AS class, ";
|
|
||||||
$sSQL .= " 'house' AS type, ";
|
|
||||||
$sSQL .= ' null::smallint AS admin_level, ';
|
|
||||||
$sSQL .= ' 30 AS rank_search, ';
|
|
||||||
$sSQL .= ' 30 AS rank_address, ';
|
|
||||||
$sSQL .= ' place_id, ';
|
|
||||||
$sSQL .= ' parent_place_id, ';
|
|
||||||
$sSQL .= ' housenumber_for_place as housenumber,';
|
|
||||||
$sSQL .= " 'us' AS country_code, ";
|
|
||||||
$sSQL .= $this->langAddressSql('housenumber_for_place');
|
|
||||||
$sSQL .= ' null::text AS placename, ';
|
|
||||||
$sSQL .= ' null::text AS ref, ';
|
|
||||||
if ($this->bExtraTags) {
|
|
||||||
$sSQL .= 'null::text AS extra,';
|
|
||||||
}
|
|
||||||
if ($this->bNameDetails) {
|
|
||||||
$sSQL .= 'null::text AS names,';
|
|
||||||
}
|
|
||||||
$sSQL .= ' st_x(centroid) AS lon, ';
|
|
||||||
$sSQL .= ' st_y(centroid) AS lat,';
|
|
||||||
$sSQL .= ' -1.15 AS importance, ';
|
|
||||||
$sSQL .= $this->addressImportanceSql('centroid', 'blub.parent_place_id');
|
|
||||||
$sSQL .= ' null::text AS extra_place ';
|
|
||||||
$sSQL .= ' FROM (';
|
|
||||||
$sSQL .= ' SELECT place_id, '; // interpolate the Tiger housenumbers here
|
|
||||||
$sSQL .= ' CASE WHEN startnumber != endnumber';
|
|
||||||
$sSQL .= ' THEN ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float)';
|
|
||||||
$sSQL .= ' ELSE ST_LineInterpolatePoint(linegeo, 0.5) END AS centroid, ';
|
|
||||||
$sSQL .= ' parent_place_id, ';
|
|
||||||
$sSQL .= ' housenumber_for_place';
|
|
||||||
$sSQL .= ' FROM (';
|
|
||||||
$sSQL .= ' location_property_tiger ';
|
|
||||||
$sSQL .= ' JOIN (values '.$sHousenumbers.') AS housenumbers(place_id, housenumber_for_place) USING(place_id)) ';
|
|
||||||
$sSQL .= ' WHERE ';
|
|
||||||
$sSQL .= ' housenumber_for_place >= startnumber';
|
|
||||||
$sSQL .= ' AND housenumber_for_place <= endnumber';
|
|
||||||
$sSQL .= ' ) AS blub'; //postgres wants an alias here
|
|
||||||
|
|
||||||
$aSubSelects[] = $sSQL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// osmline - interpolated housenumbers
|
|
||||||
$sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_OSMLINE);
|
|
||||||
if ($sPlaceIDs) {
|
|
||||||
Debug::printVar('Ids from interpolation', $sPlaceIDs);
|
|
||||||
$sHousenumbers = Result::sqlHouseNumberTable($aResults, Result::TABLE_OSMLINE);
|
|
||||||
// interpolation line search only if a housenumber was searched
|
|
||||||
// (realized through a join)
|
|
||||||
$sSQL = 'SELECT ';
|
|
||||||
$sSQL .= " 'W' AS osm_type, ";
|
|
||||||
$sSQL .= ' osm_id, ';
|
|
||||||
$sSQL .= " 'place' AS class, ";
|
|
||||||
$sSQL .= " 'house' AS type, ";
|
|
||||||
$sSQL .= ' null::smallint AS admin_level, ';
|
|
||||||
$sSQL .= ' 30 AS rank_search, ';
|
|
||||||
$sSQL .= ' 30 AS rank_address, ';
|
|
||||||
$sSQL .= ' place_id, ';
|
|
||||||
$sSQL .= ' parent_place_id, ';
|
|
||||||
$sSQL .= ' housenumber_for_place as housenumber,';
|
|
||||||
$sSQL .= ' country_code, ';
|
|
||||||
$sSQL .= $this->langAddressSql('housenumber_for_place');
|
|
||||||
$sSQL .= ' null::text AS placename, ';
|
|
||||||
$sSQL .= ' null::text AS ref, ';
|
|
||||||
if ($this->bExtraTags) {
|
|
||||||
$sSQL .= 'null::text AS extra, ';
|
|
||||||
}
|
|
||||||
if ($this->bNameDetails) {
|
|
||||||
$sSQL .= 'null::text AS names, ';
|
|
||||||
}
|
|
||||||
$sSQL .= ' st_x(centroid) AS lon, ';
|
|
||||||
$sSQL .= ' st_y(centroid) AS lat, ';
|
|
||||||
// slightly smaller than the importance for normal houses
|
|
||||||
$sSQL .= ' -0.1 AS importance, ';
|
|
||||||
$sSQL .= $this->addressImportanceSql('centroid', 'blub.parent_place_id');
|
|
||||||
$sSQL .= ' null::text AS extra_place ';
|
|
||||||
$sSQL .= ' FROM (';
|
|
||||||
$sSQL .= ' SELECT ';
|
|
||||||
$sSQL .= ' osm_id, ';
|
|
||||||
$sSQL .= ' place_id, ';
|
|
||||||
$sSQL .= ' country_code, ';
|
|
||||||
$sSQL .= ' CASE '; // interpolate the housenumbers here
|
|
||||||
$sSQL .= ' WHEN startnumber != endnumber ';
|
|
||||||
$sSQL .= ' THEN ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) ';
|
|
||||||
$sSQL .= ' ELSE linegeo ';
|
|
||||||
$sSQL .= ' END as centroid, ';
|
|
||||||
$sSQL .= ' parent_place_id, ';
|
|
||||||
$sSQL .= ' housenumber_for_place ';
|
|
||||||
$sSQL .= ' FROM (';
|
|
||||||
$sSQL .= ' location_property_osmline ';
|
|
||||||
$sSQL .= ' JOIN (values '.$sHousenumbers.') AS housenumbers(place_id, housenumber_for_place) USING(place_id)';
|
|
||||||
$sSQL .= ' ) ';
|
|
||||||
$sSQL .= ' WHERE housenumber_for_place >= 0 ';
|
|
||||||
$sSQL .= ' ) as blub'; //postgres wants an alias here
|
|
||||||
|
|
||||||
$aSubSelects[] = $sSQL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($aSubSelects)) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
$sSQL = join(' UNION ', $aSubSelects);
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
$aPlaces = $this->oDB->getAll($sSQL, null, 'Could not lookup place');
|
|
||||||
|
|
||||||
foreach ($aPlaces as &$aPlace) {
|
|
||||||
$aPlace['importance'] = (float) $aPlace['importance'];
|
|
||||||
if ($this->bAddressDetails) {
|
|
||||||
// to get addressdetails for tiger data, the housenumber is needed
|
|
||||||
$aPlace['address'] = new AddressDetails(
|
|
||||||
$this->oDB,
|
|
||||||
$aPlace['place_id'],
|
|
||||||
$aPlace['housenumber'],
|
|
||||||
$this->aLangPrefOrderSql
|
|
||||||
);
|
|
||||||
$aPlace['langaddress'] = $aPlace['address']->getLocaleAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->bExtraTags) {
|
|
||||||
if ($aPlace['extra']) {
|
|
||||||
$aPlace['sExtraTags'] = json_decode($aPlace['extra'], true);
|
|
||||||
} else {
|
|
||||||
$aPlace['sExtraTags'] = (object) array();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->bNameDetails) {
|
|
||||||
$aPlace['sNameDetails'] = $this->extractNames($aPlace['names']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$aPlace['addresstype'] = ClassTypes\getLabelTag(
|
|
||||||
$aPlace,
|
|
||||||
$aPlace['country_code']
|
|
||||||
);
|
|
||||||
|
|
||||||
$aResults[$aPlace['place_id']] = $aPlace;
|
|
||||||
}
|
|
||||||
|
|
||||||
$aResults = array_filter(
|
|
||||||
$aResults,
|
|
||||||
function ($v) {
|
|
||||||
return !($v instanceof Result);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
Debug::printVar('Places', $aResults);
|
|
||||||
|
|
||||||
return $aResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function extractNames($sNames)
|
|
||||||
{
|
|
||||||
if (!$sNames) {
|
|
||||||
return (object) array();
|
|
||||||
}
|
|
||||||
|
|
||||||
$aFullNames = json_decode($sNames, true);
|
|
||||||
$aNames = array();
|
|
||||||
|
|
||||||
foreach ($aFullNames as $sKey => $sValue) {
|
|
||||||
if (strpos($sKey, '_place_') === 0) {
|
|
||||||
$sSubKey = substr($sKey, 7);
|
|
||||||
if (array_key_exists($sSubKey, $aFullNames)) {
|
|
||||||
$aNames[$sKey] = $sValue;
|
|
||||||
} else {
|
|
||||||
$aNames[$sSubKey] = $sValue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$aNames[$sKey] = $sValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* returns an array which will contain the keys
|
|
||||||
* aBoundingBox
|
|
||||||
* and may also contain one or more of the keys
|
|
||||||
* asgeojson
|
|
||||||
* askml
|
|
||||||
* assvg
|
|
||||||
* astext
|
|
||||||
* lat
|
|
||||||
* lon
|
|
||||||
*/
|
|
||||||
public function getOutlines($iPlaceID, $fLon = null, $fLat = null, $fRadius = null, $fLonReverse = null, $fLatReverse = null)
|
|
||||||
{
|
|
||||||
|
|
||||||
$aOutlineResult = array();
|
|
||||||
if (!$iPlaceID) {
|
|
||||||
return $aOutlineResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the bounding box and outline polygon
|
|
||||||
$sSQL = 'select place_id,0 as numfeatures,st_area(geometry) as area,';
|
|
||||||
$sSQL .= ' ST_Y(centroid) as centrelat, ST_X(centroid) as centrelon,';
|
|
||||||
$sSQL .= ' ST_YMin(geometry) as minlat,ST_YMax(geometry) as maxlat,';
|
|
||||||
$sSQL .= ' ST_XMin(geometry) as minlon,ST_XMax(geometry) as maxlon';
|
|
||||||
if ($this->bIncludePolygonAsGeoJSON) {
|
|
||||||
$sSQL .= ',ST_AsGeoJSON(geometry) as asgeojson';
|
|
||||||
}
|
|
||||||
if ($this->bIncludePolygonAsKML) {
|
|
||||||
$sSQL .= ',ST_AsKML(geometry) as askml';
|
|
||||||
}
|
|
||||||
if ($this->bIncludePolygonAsSVG) {
|
|
||||||
$sSQL .= ',ST_AsSVG(geometry) as assvg';
|
|
||||||
}
|
|
||||||
if ($this->bIncludePolygonAsText) {
|
|
||||||
$sSQL .= ',ST_AsText(geometry) as astext';
|
|
||||||
}
|
|
||||||
|
|
||||||
$sSQL .= ' FROM (SELECT place_id';
|
|
||||||
if ($fLonReverse != null && $fLatReverse != null) {
|
|
||||||
$sSQL .= ',CASE WHEN (class = \'highway\') AND (ST_GeometryType(geometry) = \'ST_LineString\') THEN ';
|
|
||||||
$sSQL .=' ST_ClosestPoint(geometry, ST_SetSRID(ST_Point('.$fLatReverse.','.$fLonReverse.'),4326))';
|
|
||||||
$sSQL .=' ELSE centroid END AS centroid';
|
|
||||||
} else {
|
|
||||||
$sSQL .= ',centroid';
|
|
||||||
}
|
|
||||||
if ($this->fPolygonSimplificationThreshold > 0) {
|
|
||||||
$sSQL .= ',ST_SimplifyPreserveTopology(geometry,'.$this->fPolygonSimplificationThreshold.') as geometry';
|
|
||||||
} else {
|
|
||||||
$sSQL .= ',geometry';
|
|
||||||
}
|
|
||||||
$sSQL .= ' FROM placex where place_id = '.$iPlaceID.') as plx';
|
|
||||||
|
|
||||||
$aPointPolygon = $this->oDB->getRow($sSQL, null, 'Could not get outline');
|
|
||||||
|
|
||||||
if ($aPointPolygon && $aPointPolygon['place_id']) {
|
|
||||||
if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null) {
|
|
||||||
$aOutlineResult['lat'] = $aPointPolygon['centrelat'];
|
|
||||||
$aOutlineResult['lon'] = $aPointPolygon['centrelon'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->bIncludePolygonAsGeoJSON) {
|
|
||||||
$aOutlineResult['asgeojson'] = $aPointPolygon['asgeojson'];
|
|
||||||
}
|
|
||||||
if ($this->bIncludePolygonAsKML) {
|
|
||||||
$aOutlineResult['askml'] = $aPointPolygon['askml'];
|
|
||||||
}
|
|
||||||
if ($this->bIncludePolygonAsSVG) {
|
|
||||||
$aOutlineResult['assvg'] = $aPointPolygon['assvg'];
|
|
||||||
}
|
|
||||||
if ($this->bIncludePolygonAsText) {
|
|
||||||
$aOutlineResult['astext'] = $aPointPolygon['astext'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abs($aPointPolygon['minlat'] - $aPointPolygon['maxlat']) < 0.0000001) {
|
|
||||||
$aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
|
|
||||||
$aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abs($aPointPolygon['minlon'] - $aPointPolygon['maxlon']) < 0.0000001) {
|
|
||||||
$aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius;
|
|
||||||
$aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
$aOutlineResult['aBoundingBox'] = array(
|
|
||||||
(string)$aPointPolygon['minlat'],
|
|
||||||
(string)$aPointPolygon['maxlat'],
|
|
||||||
(string)$aPointPolygon['minlon'],
|
|
||||||
(string)$aPointPolygon['maxlon']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// as a fallback we generate a bounding box without knowing the size of the geometry
|
|
||||||
if ((!isset($aOutlineResult['aBoundingBox'])) && isset($fLon)) {
|
|
||||||
$aBounds = array(
|
|
||||||
'minlat' => $fLat - $fRadius,
|
|
||||||
'maxlat' => $fLat + $fRadius,
|
|
||||||
'minlon' => $fLon - $fRadius,
|
|
||||||
'maxlon' => $fLon + $fRadius
|
|
||||||
);
|
|
||||||
|
|
||||||
$aOutlineResult['aBoundingBox'] = array(
|
|
||||||
(string)$aBounds['minlat'],
|
|
||||||
(string)$aBounds['maxlat'],
|
|
||||||
(string)$aBounds['minlon'],
|
|
||||||
(string)$aBounds['maxlon']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return $aOutlineResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A single result of a search operation or a reverse lookup.
|
|
||||||
*
|
|
||||||
* This object only contains the id of the result. It does not yet
|
|
||||||
* have any details needed to format the output document.
|
|
||||||
*/
|
|
||||||
class Result
|
|
||||||
{
|
|
||||||
const TABLE_PLACEX = 0;
|
|
||||||
const TABLE_POSTCODE = 1;
|
|
||||||
const TABLE_OSMLINE = 2;
|
|
||||||
const TABLE_TIGER = 3;
|
|
||||||
|
|
||||||
/// Database table that contains the result.
|
|
||||||
public $iTable;
|
|
||||||
/// Id of the result.
|
|
||||||
public $iId;
|
|
||||||
/// House number (only for interpolation results).
|
|
||||||
public $iHouseNumber = -1;
|
|
||||||
/// Number of exact matches in address (address searches only).
|
|
||||||
public $iExactMatches = 0;
|
|
||||||
/// Subranking within the results (the higher the worse).
|
|
||||||
public $iResultRank = 0;
|
|
||||||
/// Address rank of the result.
|
|
||||||
public $iAddressRank;
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'Table' => $this->iTable,
|
|
||||||
'ID' => $this->iId,
|
|
||||||
'House number' => $this->iHouseNumber,
|
|
||||||
'Exact Matches' => $this->iExactMatches,
|
|
||||||
'Result rank' => $this->iResultRank
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function __construct($sId, $iTable = Result::TABLE_PLACEX)
|
|
||||||
{
|
|
||||||
$this->iTable = $iTable;
|
|
||||||
$this->iId = (int) $sId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function joinIdsByTable($aResults, $iTable)
|
|
||||||
{
|
|
||||||
return join(',', array_keys(array_filter(
|
|
||||||
$aResults,
|
|
||||||
function ($aValue) use ($iTable) {
|
|
||||||
return $aValue->iTable == $iTable;
|
|
||||||
}
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function joinIdsByTableMinRank($aResults, $iTable, $iMinAddressRank)
|
|
||||||
{
|
|
||||||
return join(',', array_keys(array_filter(
|
|
||||||
$aResults,
|
|
||||||
function ($aValue) use ($iTable, $iMinAddressRank) {
|
|
||||||
return $aValue->iTable == $iTable && $aValue->iAddressRank >= $iMinAddressRank;
|
|
||||||
}
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function joinIdsByTableMaxRank($aResults, $iTable, $iMaxAddressRank)
|
|
||||||
{
|
|
||||||
return join(',', array_keys(array_filter(
|
|
||||||
$aResults,
|
|
||||||
function ($aValue) use ($iTable, $iMaxAddressRank) {
|
|
||||||
return $aValue->iTable == $iTable && $aValue->iAddressRank <= $iMaxAddressRank;
|
|
||||||
}
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function sqlHouseNumberTable($aResults, $iTable)
|
|
||||||
{
|
|
||||||
$sHousenumbers = '';
|
|
||||||
$sSep = '';
|
|
||||||
foreach ($aResults as $oResult) {
|
|
||||||
if ($oResult->iTable == $iTable) {
|
|
||||||
$sHousenumbers .= $sSep.'('.$oResult->iId.',';
|
|
||||||
$sHousenumbers .= $oResult->iHouseNumber.')';
|
|
||||||
$sSep = ',';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sHousenumbers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split a result array into highest ranked result and the rest
|
|
||||||
*
|
|
||||||
* @param object[] $aResults List of results to split.
|
|
||||||
*
|
|
||||||
* @return array[]
|
|
||||||
*/
|
|
||||||
public static function splitResults($aResults)
|
|
||||||
{
|
|
||||||
$aHead = array();
|
|
||||||
$aTail = array();
|
|
||||||
$iMinRank = 10000;
|
|
||||||
|
|
||||||
foreach ($aResults as $oRes) {
|
|
||||||
if ($oRes->iResultRank < $iMinRank) {
|
|
||||||
$aTail += $aHead;
|
|
||||||
$aHead = array($oRes->iId => $oRes);
|
|
||||||
$iMinRank = $oRes->iResultRank;
|
|
||||||
} elseif ($oRes->iResultRank == $iMinRank) {
|
|
||||||
$aHead[$oRes->iId] = $oRes;
|
|
||||||
} else {
|
|
||||||
$aTail[$oRes->iId] = $oRes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array('head' => $aHead, 'tail' => $aTail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,401 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/Result.php');
|
|
||||||
|
|
||||||
class ReverseGeocode
|
|
||||||
{
|
|
||||||
protected $oDB;
|
|
||||||
protected $iMaxRank = 28;
|
|
||||||
|
|
||||||
|
|
||||||
public function __construct(&$oDB)
|
|
||||||
{
|
|
||||||
$this->oDB =& $oDB;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function setZoom($iZoom)
|
|
||||||
{
|
|
||||||
// Zoom to rank, this could probably be calculated but a lookup gives fine control
|
|
||||||
$aZoomRank = array(
|
|
||||||
0 => 2, // Continent / Sea
|
|
||||||
1 => 2,
|
|
||||||
2 => 2,
|
|
||||||
3 => 4, // Country
|
|
||||||
4 => 4,
|
|
||||||
5 => 8, // State
|
|
||||||
6 => 10, // Region
|
|
||||||
7 => 10,
|
|
||||||
8 => 12, // County
|
|
||||||
9 => 12,
|
|
||||||
10 => 17, // City
|
|
||||||
11 => 17,
|
|
||||||
12 => 18, // Town
|
|
||||||
13 => 19, // Village
|
|
||||||
14 => 22, // Neighbourhood
|
|
||||||
15 => 25, // Locality
|
|
||||||
16 => 26, // major street
|
|
||||||
17 => 27, // minor street
|
|
||||||
18 => 30, // or >, Building
|
|
||||||
19 => 30, // or >, Building
|
|
||||||
);
|
|
||||||
$this->iMaxRank = (isset($iZoom) && isset($aZoomRank[$iZoom]))?$aZoomRank[$iZoom]:28;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the closest interpolation with the given search diameter.
|
|
||||||
*
|
|
||||||
* @param string $sPointSQL Reverse geocoding point as SQL
|
|
||||||
* @param float $fSearchDiam Search diameter
|
|
||||||
*
|
|
||||||
* @return Record of the interpolation or null.
|
|
||||||
*/
|
|
||||||
protected function lookupInterpolation($sPointSQL, $fSearchDiam)
|
|
||||||
{
|
|
||||||
Debug::newFunction('lookupInterpolation');
|
|
||||||
$sSQL = 'SELECT place_id, parent_place_id, 30 as rank_search,';
|
|
||||||
$sSQL .= ' (CASE WHEN endnumber != startnumber';
|
|
||||||
$sSQL .= ' THEN (endnumber - startnumber) * ST_LineLocatePoint(linegeo,'.$sPointSQL.')';
|
|
||||||
$sSQL .= ' ELSE startnumber END) as fhnr,';
|
|
||||||
$sSQL .= ' startnumber, endnumber, step,';
|
|
||||||
$sSQL .= ' ST_Distance(linegeo,'.$sPointSQL.') as distance';
|
|
||||||
$sSQL .= ' FROM location_property_osmline';
|
|
||||||
$sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', linegeo, '.$fSearchDiam.')';
|
|
||||||
$sSQL .= ' and indexed_status = 0 and startnumber is not NULL ';
|
|
||||||
$sSQL .= ' and parent_place_id != 0';
|
|
||||||
$sSQL .= ' ORDER BY distance ASC limit 1';
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
return $this->oDB->getRow(
|
|
||||||
$sSQL,
|
|
||||||
null,
|
|
||||||
'Could not determine closest housenumber on an osm interpolation line.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function lookupLargeArea($sPointSQL, $iMaxRank)
|
|
||||||
{
|
|
||||||
$sCountryCode = $this->getCountryCode($sPointSQL);
|
|
||||||
if (CONST_Search_WithinCountries and $sCountryCode == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($iMaxRank > 4) {
|
|
||||||
$aPlace = $this->lookupPolygon($sPointSQL, $iMaxRank);
|
|
||||||
if ($aPlace) {
|
|
||||||
return new Result($aPlace['place_id']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no polygon which contains the searchpoint is found,
|
|
||||||
// searches in the country_osm_grid table for a polygon.
|
|
||||||
return $this->lookupInCountry($sPointSQL, $iMaxRank, $sCountryCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getCountryCode($sPointSQL)
|
|
||||||
{
|
|
||||||
Debug::newFunction('getCountryCode');
|
|
||||||
// searches for polygon in table country_osm_grid which contains the searchpoint
|
|
||||||
// and searches for the nearest place node to the searchpoint in this polygon
|
|
||||||
$sSQL = 'SELECT country_code FROM country_osm_grid';
|
|
||||||
$sSQL .= ' WHERE ST_CONTAINS(geometry, '.$sPointSQL.') LIMIT 1';
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
$sCountryCode = $this->oDB->getOne(
|
|
||||||
$sSQL,
|
|
||||||
null,
|
|
||||||
'Could not determine country polygon containing the point.'
|
|
||||||
);
|
|
||||||
return $sCountryCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function lookupInCountry($sPointSQL, $iMaxRank, $sCountryCode)
|
|
||||||
{
|
|
||||||
Debug::newFunction('lookupInCountry');
|
|
||||||
if ($sCountryCode) {
|
|
||||||
if ($iMaxRank > 4) {
|
|
||||||
// look for place nodes with the given country code
|
|
||||||
$sSQL = 'SELECT place_id FROM';
|
|
||||||
$sSQL .= ' (SELECT place_id, rank_search,';
|
|
||||||
$sSQL .= ' ST_distance('.$sPointSQL.', geometry) as distance';
|
|
||||||
$sSQL .= ' FROM placex';
|
|
||||||
$sSQL .= ' WHERE osm_type = \'N\'';
|
|
||||||
$sSQL .= ' AND country_code = \''.$sCountryCode.'\'';
|
|
||||||
$sSQL .= ' AND rank_address between 4 and 25'; // needed to select right index
|
|
||||||
$sSQL .= ' AND rank_search between 5 and ' .min(25, $iMaxRank);
|
|
||||||
$sSQL .= ' AND type != \'postcode\'';
|
|
||||||
$sSQL .= ' AND name IS NOT NULL ';
|
|
||||||
$sSQL .= ' and indexed_status = 0 and linked_place_id is null';
|
|
||||||
$sSQL .= ' AND ST_Buffer(geometry, reverse_place_diameter(rank_search)) && '.$sPointSQL;
|
|
||||||
$sSQL .= ') as a ';
|
|
||||||
$sSQL .= 'WHERE distance <= reverse_place_diameter(rank_search)';
|
|
||||||
$sSQL .= ' ORDER BY rank_search DESC, distance ASC';
|
|
||||||
$sSQL .= ' LIMIT 1';
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
$aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine place node.');
|
|
||||||
Debug::printVar('Country node', $aPlace);
|
|
||||||
|
|
||||||
if ($aPlace) {
|
|
||||||
return new Result($aPlace['place_id']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// still nothing, then return the country object
|
|
||||||
$sSQL = 'SELECT place_id, ST_distance('.$sPointSQL.', centroid) as distance';
|
|
||||||
$sSQL .= ' FROM placex';
|
|
||||||
$sSQL .= ' WHERE country_code = \''.$sCountryCode.'\'';
|
|
||||||
$sSQL .= ' AND rank_search = 4 AND rank_address = 4';
|
|
||||||
$sSQL .= ' AND class in (\'boundary\', \'place\')';
|
|
||||||
$sSQL .= ' AND linked_place_id is null';
|
|
||||||
$sSQL .= ' ORDER BY distance ASC';
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
$aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine place node.');
|
|
||||||
Debug::printVar('Country place', $aPlace);
|
|
||||||
if ($aPlace) {
|
|
||||||
return new Result($aPlace['place_id']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search for areas or nodes for areas or nodes between state and suburb level.
|
|
||||||
*
|
|
||||||
* @param string $sPointSQL Search point as SQL string.
|
|
||||||
* @param int $iMaxRank Maximum address rank of the feature.
|
|
||||||
*
|
|
||||||
* @return Record of the found feature or null.
|
|
||||||
*
|
|
||||||
* Searches first for polygon that contains the search point.
|
|
||||||
* If such a polygon is found, place nodes with a higher rank are
|
|
||||||
* searched inside the polygon.
|
|
||||||
*/
|
|
||||||
protected function lookupPolygon($sPointSQL, $iMaxRank)
|
|
||||||
{
|
|
||||||
Debug::newFunction('lookupPolygon');
|
|
||||||
// polygon search begins at suburb-level
|
|
||||||
if ($iMaxRank > 25) {
|
|
||||||
$iMaxRank = 25;
|
|
||||||
}
|
|
||||||
// no polygon search over country-level
|
|
||||||
if ($iMaxRank < 5) {
|
|
||||||
$iMaxRank = 5;
|
|
||||||
}
|
|
||||||
// search for polygon
|
|
||||||
$sSQL = 'SELECT place_id, parent_place_id, rank_address, rank_search FROM';
|
|
||||||
$sSQL .= '(select place_id, parent_place_id, rank_address, rank_search, country_code, geometry';
|
|
||||||
$sSQL .= ' FROM placex';
|
|
||||||
$sSQL .= ' WHERE ST_GeometryType(geometry) in (\'ST_Polygon\', \'ST_MultiPolygon\')';
|
|
||||||
// Ensure that query planner doesn't use the index on rank_search.
|
|
||||||
$sSQL .= ' AND coalesce(rank_search, 0) between 5 and ' .$iMaxRank;
|
|
||||||
$sSQL .= ' AND rank_address between 4 and 25'; // needed for index selection
|
|
||||||
$sSQL .= ' AND geometry && '.$sPointSQL;
|
|
||||||
$sSQL .= ' AND type != \'postcode\' ';
|
|
||||||
$sSQL .= ' AND name is not null';
|
|
||||||
$sSQL .= ' AND indexed_status = 0 and linked_place_id is null';
|
|
||||||
$sSQL .= ' ORDER BY rank_search DESC LIMIT 50 ) as a';
|
|
||||||
$sSQL .= ' WHERE ST_Contains(geometry, '.$sPointSQL.' )';
|
|
||||||
$sSQL .= ' ORDER BY rank_search DESC LIMIT 1';
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
$aPoly = $this->oDB->getRow($sSQL, null, 'Could not determine polygon containing the point.');
|
|
||||||
Debug::printVar('Polygon result', $aPoly);
|
|
||||||
|
|
||||||
if ($aPoly) {
|
|
||||||
// if a polygon is found, search for placenodes begins ...
|
|
||||||
$iRankAddress = $aPoly['rank_address'];
|
|
||||||
$iRankSearch = $aPoly['rank_search'];
|
|
||||||
$iPlaceID = $aPoly['place_id'];
|
|
||||||
|
|
||||||
if ($iRankSearch != $iMaxRank) {
|
|
||||||
$sSQL = 'SELECT place_id FROM ';
|
|
||||||
$sSQL .= '(SELECT place_id, rank_search, country_code, geometry,';
|
|
||||||
$sSQL .= ' ST_distance('.$sPointSQL.', geometry) as distance';
|
|
||||||
$sSQL .= ' FROM placex';
|
|
||||||
$sSQL .= ' WHERE osm_type = \'N\'';
|
|
||||||
$sSQL .= ' AND rank_search > '.$iRankSearch;
|
|
||||||
$sSQL .= ' AND rank_search <= '.$iMaxRank;
|
|
||||||
$sSQL .= ' AND rank_address between 4 and 25'; // needed to select right index
|
|
||||||
$sSQL .= ' AND type != \'postcode\'';
|
|
||||||
$sSQL .= ' AND name IS NOT NULL ';
|
|
||||||
$sSQL .= ' AND indexed_status = 0 AND linked_place_id is null';
|
|
||||||
$sSQL .= ' AND ST_Buffer(geometry, reverse_place_diameter(rank_search)) && '.$sPointSQL;
|
|
||||||
$sSQL .= ' ORDER BY rank_search DESC, distance ASC';
|
|
||||||
$sSQL .= ' limit 100) as a';
|
|
||||||
$sSQL .= ' WHERE ST_Contains((SELECT geometry FROM placex WHERE place_id = '.$iPlaceID.'), geometry )';
|
|
||||||
$sSQL .= ' AND distance <= reverse_place_diameter(rank_search)';
|
|
||||||
$sSQL .= ' ORDER BY rank_search DESC, distance ASC';
|
|
||||||
$sSQL .= ' LIMIT 1';
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
$aPlaceNode = $this->oDB->getRow($sSQL, null, 'Could not determine place node.');
|
|
||||||
Debug::printVar('Nearest place node', $aPlaceNode);
|
|
||||||
if ($aPlaceNode) {
|
|
||||||
return $aPlaceNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $aPoly;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function lookup($fLat, $fLon, $bDoInterpolation = true)
|
|
||||||
{
|
|
||||||
return $this->lookupPoint(
|
|
||||||
'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)',
|
|
||||||
$bDoInterpolation
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function lookupPoint($sPointSQL, $bDoInterpolation = true)
|
|
||||||
{
|
|
||||||
Debug::newFunction('lookupPoint');
|
|
||||||
// Find the nearest point
|
|
||||||
$fSearchDiam = 0.006;
|
|
||||||
$oResult = null;
|
|
||||||
$aPlace = null;
|
|
||||||
|
|
||||||
// for POI or street level
|
|
||||||
if ($this->iMaxRank >= 26) {
|
|
||||||
// starts if the search is on POI or street level,
|
|
||||||
// searches for the nearest POI or street,
|
|
||||||
// if a street is found and a POI is searched for,
|
|
||||||
// the nearest POI which the found street is a parent of is chosen.
|
|
||||||
$sSQL = 'select place_id,parent_place_id,rank_address,country_code,';
|
|
||||||
$sSQL .= ' ST_distance('.$sPointSQL.', geometry) as distance';
|
|
||||||
$sSQL .= ' FROM ';
|
|
||||||
$sSQL .= ' placex';
|
|
||||||
$sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, '.$fSearchDiam.')';
|
|
||||||
$sSQL .= ' AND';
|
|
||||||
$sSQL .= ' rank_address between 26 and '.$this->iMaxRank;
|
|
||||||
$sSQL .= ' and (name is not null or housenumber is not null';
|
|
||||||
$sSQL .= ' or rank_address between 26 and 27)';
|
|
||||||
$sSQL .= ' and (rank_address between 26 and 27';
|
|
||||||
$sSQL .= ' or ST_GeometryType(geometry) != \'ST_LineString\')';
|
|
||||||
$sSQL .= ' and class not in (\'boundary\')';
|
|
||||||
$sSQL .= ' and indexed_status = 0 and linked_place_id is null';
|
|
||||||
$sSQL .= ' and (ST_GeometryType(geometry) not in (\'ST_Polygon\',\'ST_MultiPolygon\') ';
|
|
||||||
$sSQL .= ' OR ST_DWithin('.$sPointSQL.', centroid, '.$fSearchDiam.'))';
|
|
||||||
$sSQL .= ' ORDER BY distance ASC limit 1';
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
$aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine closest place.');
|
|
||||||
|
|
||||||
Debug::printVar('POI/street level result', $aPlace);
|
|
||||||
if ($aPlace) {
|
|
||||||
$iPlaceID = $aPlace['place_id'];
|
|
||||||
$oResult = new Result($iPlaceID);
|
|
||||||
$iRankAddress = $aPlace['rank_address'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($aPlace) {
|
|
||||||
// if street and maxrank > streetlevel
|
|
||||||
if ($iRankAddress <= 27 && $this->iMaxRank > 27) {
|
|
||||||
// find the closest object (up to a certain radius) of which the street is a parent of
|
|
||||||
$sSQL = ' select place_id,';
|
|
||||||
$sSQL .= ' ST_distance('.$sPointSQL.', geometry) as distance';
|
|
||||||
$sSQL .= ' FROM ';
|
|
||||||
$sSQL .= ' placex';
|
|
||||||
// radius ?
|
|
||||||
$sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, 0.001)';
|
|
||||||
$sSQL .= ' AND parent_place_id = '.$iPlaceID;
|
|
||||||
$sSQL .= ' and rank_address > 28';
|
|
||||||
$sSQL .= ' and ST_GeometryType(geometry) != \'ST_LineString\'';
|
|
||||||
$sSQL .= ' and (name is not null or housenumber is not null)';
|
|
||||||
$sSQL .= ' and class not in (\'boundary\')';
|
|
||||||
$sSQL .= ' and indexed_status = 0 and linked_place_id is null';
|
|
||||||
$sSQL .= ' ORDER BY distance ASC limit 1';
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
$aStreet = $this->oDB->getRow($sSQL, null, 'Could not determine closest place.');
|
|
||||||
Debug::printVar('Closest POI result', $aStreet);
|
|
||||||
|
|
||||||
if ($aStreet) {
|
|
||||||
$aPlace = $aStreet;
|
|
||||||
$oResult = new Result($aStreet['place_id']);
|
|
||||||
$iRankAddress = 30;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the US we can check TIGER data for nearest housenumber
|
|
||||||
if (CONST_Use_US_Tiger_Data
|
|
||||||
&& $iRankAddress <= 27
|
|
||||||
&& $aPlace['country_code'] == 'us'
|
|
||||||
&& $this->iMaxRank >= 28
|
|
||||||
) {
|
|
||||||
$sSQL = 'SELECT place_id,parent_place_id,30 as rank_search,';
|
|
||||||
$sSQL .= ' (endnumber - startnumber) * ST_LineLocatePoint(linegeo,'.$sPointSQL.') as fhnr,';
|
|
||||||
$sSQL .= ' startnumber, endnumber, step,';
|
|
||||||
$sSQL .= ' ST_Distance('.$sPointSQL.', linegeo) as distance';
|
|
||||||
$sSQL .= ' FROM location_property_tiger WHERE parent_place_id = '.$oResult->iId;
|
|
||||||
$sSQL .= ' AND ST_DWithin('.$sPointSQL.', linegeo, 0.001)';
|
|
||||||
$sSQL .= ' ORDER BY distance ASC limit 1';
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
$aPlaceTiger = $this->oDB->getRow($sSQL, null, 'Could not determine closest Tiger place.');
|
|
||||||
Debug::printVar('Tiger house number result', $aPlaceTiger);
|
|
||||||
|
|
||||||
if ($aPlaceTiger) {
|
|
||||||
$aPlace = $aPlaceTiger;
|
|
||||||
$oResult = new Result($aPlaceTiger['place_id'], Result::TABLE_TIGER);
|
|
||||||
$iRndNum = max(0, round($aPlaceTiger['fhnr'] / $aPlaceTiger['step']) * $aPlaceTiger['step']);
|
|
||||||
$oResult->iHouseNumber = $aPlaceTiger['startnumber'] + $iRndNum;
|
|
||||||
if ($oResult->iHouseNumber > $aPlaceTiger['endnumber']) {
|
|
||||||
$oResult->iHouseNumber = $aPlaceTiger['endnumber'];
|
|
||||||
}
|
|
||||||
$iRankAddress = 30;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($bDoInterpolation && $this->iMaxRank >= 30) {
|
|
||||||
$fDistance = $fSearchDiam;
|
|
||||||
if ($aPlace) {
|
|
||||||
// We can't reliably go from the closest street to an
|
|
||||||
// interpolation line because the closest interpolation
|
|
||||||
// may have a different street segments as a parent.
|
|
||||||
// Therefore allow an interpolation line to take precedence
|
|
||||||
// even when the street is closer.
|
|
||||||
$fDistance = $iRankAddress < 28 ? 0.001 : $aPlace['distance'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$aHouse = $this->lookupInterpolation($sPointSQL, $fDistance);
|
|
||||||
Debug::printVar('Interpolation result', $aPlace);
|
|
||||||
|
|
||||||
if ($aHouse) {
|
|
||||||
$oResult = new Result($aHouse['place_id'], Result::TABLE_OSMLINE);
|
|
||||||
$iRndNum = max(0, round($aHouse['fhnr'] / $aHouse['step']) * $aHouse['step']);
|
|
||||||
$oResult->iHouseNumber = $aHouse['startnumber'] + $iRndNum;
|
|
||||||
if ($oResult->iHouseNumber > $aHouse['endnumber']) {
|
|
||||||
$oResult->iHouseNumber = $aHouse['endnumber'];
|
|
||||||
}
|
|
||||||
$aPlace = $aHouse;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$aPlace) {
|
|
||||||
// if no POI or street is found ...
|
|
||||||
$oResult = $this->lookupLargeArea($sPointSQL, 25);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// lower than street level ($iMaxRank < 26 )
|
|
||||||
$oResult = $this->lookupLargeArea($sPointSQL, $this->iMaxRank);
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::printVar('Final result', $oResult);
|
|
||||||
return $oResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,319 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/lib.php');
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collection of search constraints that are independent of the
|
|
||||||
* actual interpretation of the search query.
|
|
||||||
*
|
|
||||||
* The search context is shared between all SearchDescriptions. This
|
|
||||||
* object mainly serves as context provider for the database queries.
|
|
||||||
* Therefore most data is directly cached as SQL statements.
|
|
||||||
*/
|
|
||||||
class SearchContext
|
|
||||||
{
|
|
||||||
/// Search radius around a given Near reference point.
|
|
||||||
private $fNearRadius = false;
|
|
||||||
/// True if search must be restricted to viewbox only.
|
|
||||||
public $bViewboxBounded = false;
|
|
||||||
|
|
||||||
/// Reference point for search (as SQL).
|
|
||||||
public $sqlNear = '';
|
|
||||||
/// Viewbox selected for search (as SQL).
|
|
||||||
public $sqlViewboxSmall = '';
|
|
||||||
/// Viewbox with a larger buffer around (as SQL).
|
|
||||||
public $sqlViewboxLarge = '';
|
|
||||||
/// Reference along a route (as SQL).
|
|
||||||
public $sqlViewboxCentre = '';
|
|
||||||
/// List of countries to restrict search to (as array).
|
|
||||||
public $aCountryList = null;
|
|
||||||
/// List of countries to restrict search to (as SQL).
|
|
||||||
public $sqlCountryList = '';
|
|
||||||
/// List of place IDs to exclude (as SQL).
|
|
||||||
private $sqlExcludeList = '';
|
|
||||||
/// Subset of word ids of full words in the query.
|
|
||||||
private $aFullNameWords = array();
|
|
||||||
|
|
||||||
public function setFullNameWords($aWordList)
|
|
||||||
{
|
|
||||||
$this->aFullNameWords = $aWordList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFullNameTerms()
|
|
||||||
{
|
|
||||||
return $this->aFullNameWords;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a reference point is defined.
|
|
||||||
*
|
|
||||||
* @return bool True if a reference point is defined.
|
|
||||||
*/
|
|
||||||
public function hasNearPoint()
|
|
||||||
{
|
|
||||||
return $this->fNearRadius !== false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get radius around reference point.
|
|
||||||
*
|
|
||||||
* @return float Search radius around reference point.
|
|
||||||
*/
|
|
||||||
public function nearRadius()
|
|
||||||
{
|
|
||||||
return $this->fNearRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set search reference point in WGS84.
|
|
||||||
*
|
|
||||||
* If set, then only places around this point will be taken into account.
|
|
||||||
*
|
|
||||||
* @param float $fLat Latitude of point.
|
|
||||||
* @param float $fLon Longitude of point.
|
|
||||||
* @param float $fRadius Search radius around point.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setNearPoint($fLat, $fLon, $fRadius = 0.1)
|
|
||||||
{
|
|
||||||
$this->fNearRadius = $fRadius;
|
|
||||||
$this->sqlNear = 'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the search is geographically restricted.
|
|
||||||
*
|
|
||||||
* Searches are restricted if a reference point is given or if
|
|
||||||
* a bounded viewbox is set.
|
|
||||||
*
|
|
||||||
* @return bool True, if the search is geographically bounded.
|
|
||||||
*/
|
|
||||||
public function isBoundedSearch()
|
|
||||||
{
|
|
||||||
return $this->hasNearPoint() || ($this->sqlViewboxSmall && $this->bViewboxBounded);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set rectangular viewbox.
|
|
||||||
*
|
|
||||||
* The viewbox may be bounded which means that no search results
|
|
||||||
* must be outside the viewbox.
|
|
||||||
*
|
|
||||||
* @param float[4] $aViewBox Coordinates of the viewbox.
|
|
||||||
* @param bool $bBounded True if the viewbox is bounded.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setViewboxFromBox(&$aViewBox, $bBounded)
|
|
||||||
{
|
|
||||||
$this->bViewboxBounded = $bBounded;
|
|
||||||
$this->sqlViewboxCentre = '';
|
|
||||||
|
|
||||||
$this->sqlViewboxSmall = sprintf(
|
|
||||||
'ST_SetSRID(ST_MakeBox2D(ST_Point(%F,%F),ST_Point(%F,%F)),4326)',
|
|
||||||
$aViewBox[0],
|
|
||||||
$aViewBox[1],
|
|
||||||
$aViewBox[2],
|
|
||||||
$aViewBox[3]
|
|
||||||
);
|
|
||||||
|
|
||||||
$fHeight = abs($aViewBox[0] - $aViewBox[2]);
|
|
||||||
$fWidth = abs($aViewBox[1] - $aViewBox[3]);
|
|
||||||
|
|
||||||
$this->sqlViewboxLarge = sprintf(
|
|
||||||
'ST_SetSRID(ST_MakeBox2D(ST_Point(%F,%F),ST_Point(%F,%F)),4326)',
|
|
||||||
max($aViewBox[0], $aViewBox[2]) + $fHeight,
|
|
||||||
max($aViewBox[1], $aViewBox[3]) + $fWidth,
|
|
||||||
min($aViewBox[0], $aViewBox[2]) - $fHeight,
|
|
||||||
min($aViewBox[1], $aViewBox[3]) - $fWidth
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set viewbox along a route.
|
|
||||||
*
|
|
||||||
* The viewbox may be bounded which means that no search results
|
|
||||||
* must be outside the viewbox.
|
|
||||||
*
|
|
||||||
* @param object $oDB Nominatim::DB instance to use for computing the box.
|
|
||||||
* @param string[] $aRoutePoints List of x,y coordinates along a route.
|
|
||||||
* @param float $fRouteWidth Buffer around the route to use.
|
|
||||||
* @param bool $bBounded True if the viewbox bounded.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setViewboxFromRoute(&$oDB, $aRoutePoints, $fRouteWidth, $bBounded)
|
|
||||||
{
|
|
||||||
$this->bViewboxBounded = $bBounded;
|
|
||||||
$this->sqlViewboxCentre = "ST_SetSRID('LINESTRING(";
|
|
||||||
$sSep = '';
|
|
||||||
foreach ($aRoutePoints as $aPoint) {
|
|
||||||
$fPoint = (float)$aPoint;
|
|
||||||
$this->sqlViewboxCentre .= $sSep.$fPoint;
|
|
||||||
$sSep = ($sSep == ' ') ? ',' : ' ';
|
|
||||||
}
|
|
||||||
$this->sqlViewboxCentre .= ")'::geometry,4326)";
|
|
||||||
|
|
||||||
$sSQL = 'ST_BUFFER('.$this->sqlViewboxCentre.','.($fRouteWidth/69).')';
|
|
||||||
$sGeom = $oDB->getOne('select '.$sSQL, null, 'Could not get small viewbox');
|
|
||||||
$this->sqlViewboxSmall = "'".$sGeom."'::geometry";
|
|
||||||
|
|
||||||
$sSQL = 'ST_BUFFER('.$this->sqlViewboxCentre.','.($fRouteWidth/30).')';
|
|
||||||
$sGeom = $oDB->getOne('select '.$sSQL, null, 'Could not get large viewbox');
|
|
||||||
$this->sqlViewboxLarge = "'".$sGeom."'::geometry";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set list of excluded place IDs.
|
|
||||||
*
|
|
||||||
* @param integer[] $aExcluded List of IDs.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setExcludeList($aExcluded)
|
|
||||||
{
|
|
||||||
$this->sqlExcludeList = ' not in ('.join(',', $aExcluded).')';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set list of countries to restrict search to.
|
|
||||||
*
|
|
||||||
* @param string[] $aCountries List of two-letter lower-case country codes.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setCountryList($aCountries)
|
|
||||||
{
|
|
||||||
$this->sqlCountryList = '('.join(',', array_map('addQuotes', $aCountries)).')';
|
|
||||||
$this->aCountryList = $aCountries;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract a reference point from a query string.
|
|
||||||
*
|
|
||||||
* @param string $sQuery Query to scan.
|
|
||||||
*
|
|
||||||
* @return string The remaining query string.
|
|
||||||
*/
|
|
||||||
public function setNearPointFromQuery($sQuery)
|
|
||||||
{
|
|
||||||
$aResult = parseLatLon($sQuery);
|
|
||||||
|
|
||||||
if ($aResult !== false
|
|
||||||
&& $aResult[1] <= 90.1
|
|
||||||
&& $aResult[1] >= -90.1
|
|
||||||
&& $aResult[2] <= 180.1
|
|
||||||
&& $aResult[2] >= -180.1
|
|
||||||
) {
|
|
||||||
$this->setNearPoint($aResult[1], $aResult[2]);
|
|
||||||
$sQuery = trim(str_replace($aResult[0], ' ', $sQuery));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an SQL snippet for computing the distance from the reference point.
|
|
||||||
*
|
|
||||||
* @param string $sObj SQL variable name to compute the distance from.
|
|
||||||
*
|
|
||||||
* @return string An SQL string.
|
|
||||||
*/
|
|
||||||
public function distanceSQL($sObj)
|
|
||||||
{
|
|
||||||
return 'ST_Distance('.$this->sqlNear.", $sObj)";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an SQL snippet for checking if something is within range of the
|
|
||||||
* reference point.
|
|
||||||
*
|
|
||||||
* @param string $sObj SQL variable name to compute if it is within range.
|
|
||||||
*
|
|
||||||
* @return string An SQL string.
|
|
||||||
*/
|
|
||||||
public function withinSQL($sObj)
|
|
||||||
{
|
|
||||||
return sprintf('ST_DWithin(%s, %s, %F)', $sObj, $this->sqlNear, $this->fNearRadius);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an SQL snippet of the importance factor of the viewbox.
|
|
||||||
*
|
|
||||||
* The importance factor is computed by checking if an object is within
|
|
||||||
* the viewbox and/or the extended version of the viewbox.
|
|
||||||
*
|
|
||||||
* @param string $sObj SQL variable name of object to weight the importance
|
|
||||||
*
|
|
||||||
* @return string SQL snippet of the factor with a leading multiply sign.
|
|
||||||
*/
|
|
||||||
public function viewboxImportanceSQL($sObj)
|
|
||||||
{
|
|
||||||
$sSQL = '';
|
|
||||||
|
|
||||||
if ($this->sqlViewboxSmall) {
|
|
||||||
$sSQL = " * CASE WHEN ST_Contains($this->sqlViewboxSmall, $sObj) THEN 1 ELSE 0.5 END";
|
|
||||||
}
|
|
||||||
if ($this->sqlViewboxLarge) {
|
|
||||||
$sSQL = " * CASE WHEN ST_Contains($this->sqlViewboxLarge, $sObj) THEN 1 ELSE 0.5 END";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sSQL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SQL snippet checking if a place ID should be excluded.
|
|
||||||
*
|
|
||||||
* @param string $sVariable SQL variable name of place ID to check,
|
|
||||||
* potentially prefixed with more SQL.
|
|
||||||
*
|
|
||||||
* @return string SQL snippet.
|
|
||||||
*/
|
|
||||||
public function excludeSQL($sVariable)
|
|
||||||
{
|
|
||||||
if ($this->sqlExcludeList) {
|
|
||||||
return $sVariable.$this->sqlExcludeList;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the given country is covered by the search context.
|
|
||||||
*
|
|
||||||
* @param string $sCountryCode Country code of the country to check.
|
|
||||||
*
|
|
||||||
* @return True, if no country code restrictions are set or the
|
|
||||||
* country is included in the country list.
|
|
||||||
*/
|
|
||||||
public function isCountryApplicable($sCountryCode)
|
|
||||||
{
|
|
||||||
return $this->aCountryList === null || in_array($sCountryCode, $this->aCountryList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'Near radius' => $this->fNearRadius,
|
|
||||||
'Near point (SQL)' => $this->sqlNear,
|
|
||||||
'Bounded viewbox' => $this->bViewboxBounded,
|
|
||||||
'Viewbox (SQL, small)' => $this->sqlViewboxSmall,
|
|
||||||
'Viewbox (SQL, large)' => $this->sqlViewboxLarge,
|
|
||||||
'Viewbox (SQL, centre)' => $this->sqlViewboxCentre,
|
|
||||||
'Countries (SQL)' => $this->sqlCountryList,
|
|
||||||
'Excluded IDs (SQL)' => $this->sqlExcludeList
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,985 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/SpecialSearchOperator.php');
|
|
||||||
require_once(CONST_LibDir.'/SearchContext.php');
|
|
||||||
require_once(CONST_LibDir.'/Result.php');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Description of a single interpretation of a search query.
|
|
||||||
*/
|
|
||||||
class SearchDescription
|
|
||||||
{
|
|
||||||
/// Ranking how well the description fits the query.
|
|
||||||
private $iSearchRank = 0;
|
|
||||||
/// Country code of country the result must belong to.
|
|
||||||
private $sCountryCode = '';
|
|
||||||
/// List of word ids making up the name of the object.
|
|
||||||
private $aName = array();
|
|
||||||
/// True if the name is rare enough to force index use on name.
|
|
||||||
private $bRareName = false;
|
|
||||||
/// True if the name requires to be accompanied by address terms.
|
|
||||||
private $bNameNeedsAddress = false;
|
|
||||||
/// List of word ids making up the address of the object.
|
|
||||||
private $aAddress = array();
|
|
||||||
/// List of word ids that appear in the name but should be ignored.
|
|
||||||
private $aNameNonSearch = array();
|
|
||||||
/// List of word ids that appear in the address but should be ignored.
|
|
||||||
private $aAddressNonSearch = array();
|
|
||||||
/// Kind of search for special searches, see Nominatim::Operator.
|
|
||||||
private $iOperator = Operator::NONE;
|
|
||||||
/// Class of special feature to search for.
|
|
||||||
private $sClass = '';
|
|
||||||
/// Type of special feature to search for.
|
|
||||||
private $sType = '';
|
|
||||||
/// Housenumber of the object.
|
|
||||||
private $sHouseNumber = '';
|
|
||||||
/// Postcode for the object.
|
|
||||||
private $sPostcode = '';
|
|
||||||
/// Global search constraints.
|
|
||||||
private $oContext;
|
|
||||||
|
|
||||||
// Temporary values used while creating the search description.
|
|
||||||
|
|
||||||
/// Index of phrase currently processed.
|
|
||||||
private $iNamePhrase = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an empty search description.
|
|
||||||
*
|
|
||||||
* @param object $oContext Global context to use. Will be inherited by
|
|
||||||
* all derived search objects.
|
|
||||||
*/
|
|
||||||
public function __construct($oContext)
|
|
||||||
{
|
|
||||||
$this->oContext = $oContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current search rank.
|
|
||||||
*
|
|
||||||
* The higher the search rank the lower the likelihood that the
|
|
||||||
* search is a correct interpretation of the search query.
|
|
||||||
*
|
|
||||||
* @return integer Search rank.
|
|
||||||
*/
|
|
||||||
public function getRank()
|
|
||||||
{
|
|
||||||
return $this->iSearchRank;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract key/value pairs from a query.
|
|
||||||
*
|
|
||||||
* Key/value pairs are recognised if they are of the form [<key>=<value>].
|
|
||||||
* If multiple terms of this kind are found then all terms are removed
|
|
||||||
* but only the first is used for search.
|
|
||||||
*
|
|
||||||
* @param string $sQuery Original query string.
|
|
||||||
*
|
|
||||||
* @return string The query string with the special search patterns removed.
|
|
||||||
*/
|
|
||||||
public function extractKeyValuePairs($sQuery)
|
|
||||||
{
|
|
||||||
// Search for terms of kind [<key>=<value>].
|
|
||||||
preg_match_all(
|
|
||||||
'/\\[([\\w_]*)=([\\w_]*)\\]/',
|
|
||||||
$sQuery,
|
|
||||||
$aSpecialTermsRaw,
|
|
||||||
PREG_SET_ORDER
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach ($aSpecialTermsRaw as $aTerm) {
|
|
||||||
$sQuery = str_replace($aTerm[0], ' ', $sQuery);
|
|
||||||
if (!$this->hasOperator()) {
|
|
||||||
$this->setPoiSearch(Operator::TYPE, $aTerm[1], $aTerm[2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the combination of parameters is sensible.
|
|
||||||
*
|
|
||||||
* @return bool True, if the search looks valid.
|
|
||||||
*/
|
|
||||||
public function isValidSearch()
|
|
||||||
{
|
|
||||||
if (empty($this->aName)) {
|
|
||||||
if ($this->sHouseNumber) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!$this->sClass && !$this->sCountryCode) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($this->bNameNeedsAddress && empty($this->aAddress)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////// Search building functions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a copy of this search description adding to search rank.
|
|
||||||
*
|
|
||||||
* @param integer $iTermCost Cost to add to the current search rank.
|
|
||||||
*
|
|
||||||
* @return object Cloned search description.
|
|
||||||
*/
|
|
||||||
public function clone($iTermCost)
|
|
||||||
{
|
|
||||||
$oSearch = clone $this;
|
|
||||||
$oSearch->iSearchRank += $iTermCost;
|
|
||||||
|
|
||||||
return $oSearch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the search currently includes a name.
|
|
||||||
*
|
|
||||||
* @param bool bIncludeNonNames If true stop-word tokens are taken into
|
|
||||||
* account, too.
|
|
||||||
*
|
|
||||||
* @return bool True, if search has a name.
|
|
||||||
*/
|
|
||||||
public function hasName($bIncludeNonNames = false)
|
|
||||||
{
|
|
||||||
return !empty($this->aName)
|
|
||||||
|| (!empty($this->aNameNonSearch) && $bIncludeNonNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the search currently includes an address term.
|
|
||||||
*
|
|
||||||
* @return bool True, if any address term is included, including stop-word
|
|
||||||
* terms.
|
|
||||||
*/
|
|
||||||
public function hasAddress()
|
|
||||||
{
|
|
||||||
return !empty($this->aAddress) || !empty($this->aAddressNonSearch);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a country restriction is currently included in the search.
|
|
||||||
*
|
|
||||||
* @return bool True, if a country restriction is set.
|
|
||||||
*/
|
|
||||||
public function hasCountry()
|
|
||||||
{
|
|
||||||
return $this->sCountryCode !== '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a postcode is currently included in the search.
|
|
||||||
*
|
|
||||||
* @return bool True, if a postcode is set.
|
|
||||||
*/
|
|
||||||
public function hasPostcode()
|
|
||||||
{
|
|
||||||
return $this->sPostcode !== '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a house number is set for the search.
|
|
||||||
*
|
|
||||||
* @return bool True, if a house number is set.
|
|
||||||
*/
|
|
||||||
public function hasHousenumber()
|
|
||||||
{
|
|
||||||
return $this->sHouseNumber !== '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a special type of place is requested.
|
|
||||||
*
|
|
||||||
* param integer iOperator When set, check for the particular
|
|
||||||
* operator used for the special type.
|
|
||||||
*
|
|
||||||
* @return bool True, if speial type is requested or, if requested,
|
|
||||||
* a special type with the given operator.
|
|
||||||
*/
|
|
||||||
public function hasOperator($iOperator = null)
|
|
||||||
{
|
|
||||||
return $iOperator === null ? $this->iOperator != Operator::NONE : $this->iOperator == $iOperator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the given token to the list of terms to search for in the address.
|
|
||||||
*
|
|
||||||
* @param integer iID ID of term to add.
|
|
||||||
* @param bool bSearchable Term should be used to search for result
|
|
||||||
* (i.e. term is not a stop word).
|
|
||||||
*/
|
|
||||||
public function addAddressToken($iId, $bSearchable = true)
|
|
||||||
{
|
|
||||||
if ($bSearchable) {
|
|
||||||
$this->aAddress[$iId] = $iId;
|
|
||||||
} else {
|
|
||||||
$this->aAddressNonSearch[$iId] = $iId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the given full-word token to the list of terms to search for in the
|
|
||||||
* name.
|
|
||||||
*
|
|
||||||
* @param integer iId ID of term to add.
|
|
||||||
* @param bool bRareName True if the term is infrequent enough to not
|
|
||||||
* require other constraints for efficient search.
|
|
||||||
*/
|
|
||||||
public function addNameToken($iId, $bRareName)
|
|
||||||
{
|
|
||||||
$this->aName[$iId] = $iId;
|
|
||||||
$this->bRareName = $bRareName;
|
|
||||||
$this->bNameNeedsAddress = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the given partial token to the list of terms to search for in
|
|
||||||
* the name.
|
|
||||||
*
|
|
||||||
* @param integer iID ID of term to add.
|
|
||||||
* @param bool bSearchable Term should be used to search for result
|
|
||||||
* (i.e. term is not a stop word).
|
|
||||||
* @param bool bNeedsAddress True if the term is too unspecific to be used
|
|
||||||
* in a stand-alone search without an address
|
|
||||||
* to narrow down the search.
|
|
||||||
* @param integer iPhraseNumber Index of phrase, where the partial term
|
|
||||||
* appears.
|
|
||||||
*/
|
|
||||||
public function addPartialNameToken($iId, $bSearchable, $bNeedsAddress, $iPhraseNumber)
|
|
||||||
{
|
|
||||||
if (empty($this->aName)) {
|
|
||||||
$this->bNameNeedsAddress = $bNeedsAddress;
|
|
||||||
} elseif ($bSearchable && count($this->aName) >= 2) {
|
|
||||||
$this->bNameNeedsAddress = false;
|
|
||||||
} else {
|
|
||||||
$this->bNameNeedsAddress &= $bNeedsAddress;
|
|
||||||
}
|
|
||||||
if ($bSearchable) {
|
|
||||||
$this->aName[$iId] = $iId;
|
|
||||||
} else {
|
|
||||||
$this->aNameNonSearch[$iId] = $iId;
|
|
||||||
}
|
|
||||||
$this->iNamePhrase = $iPhraseNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set country restriction for the search.
|
|
||||||
*
|
|
||||||
* @param string sCountryCode Country code of country to restrict search to.
|
|
||||||
*/
|
|
||||||
public function setCountry($sCountryCode)
|
|
||||||
{
|
|
||||||
$this->sCountryCode = $sCountryCode;
|
|
||||||
$this->iNamePhrase = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set postcode search constraint.
|
|
||||||
*
|
|
||||||
* @param string sPostcode Postcode the result should have.
|
|
||||||
*/
|
|
||||||
public function setPostcode($sPostcode)
|
|
||||||
{
|
|
||||||
$this->sPostcode = $sPostcode;
|
|
||||||
$this->iNamePhrase = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make this search a search for a postcode object.
|
|
||||||
*
|
|
||||||
* @param integer iId Token Id for the postcode.
|
|
||||||
* @param string sPostcode Postcode to look for.
|
|
||||||
*/
|
|
||||||
public function setPostcodeAsName($iId, $sPostcode)
|
|
||||||
{
|
|
||||||
$this->iOperator = Operator::POSTCODE;
|
|
||||||
$this->aAddress = array_merge($this->aAddress, $this->aName);
|
|
||||||
$this->aName = array($iId => $sPostcode);
|
|
||||||
$this->bRareName = true;
|
|
||||||
$this->iNamePhrase = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set house number search cnstraint.
|
|
||||||
*
|
|
||||||
* @param string sNumber House number the result should have.
|
|
||||||
*/
|
|
||||||
public function setHousenumber($sNumber)
|
|
||||||
{
|
|
||||||
$this->sHouseNumber = $sNumber;
|
|
||||||
$this->iNamePhrase = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make this search a search for a house number.
|
|
||||||
*
|
|
||||||
* @param integer iId Token Id for the house number.
|
|
||||||
*/
|
|
||||||
public function setHousenumberAsName($iId)
|
|
||||||
{
|
|
||||||
$this->aAddress = array_merge($this->aAddress, $this->aName);
|
|
||||||
$this->bRareName = false;
|
|
||||||
$this->bNameNeedsAddress = true;
|
|
||||||
$this->aName = array($iId => $iId);
|
|
||||||
$this->iNamePhrase = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make this search a POI search.
|
|
||||||
*
|
|
||||||
* In a POI search, objects are not (only) searched by their name
|
|
||||||
* but also by the primary OSM key/value pair (class and type in Nominatim).
|
|
||||||
*
|
|
||||||
* @param integer $iOperator Type of POI search
|
|
||||||
* @param string $sClass Class (or OSM tag key) of POI.
|
|
||||||
* @param string $sType Type (or OSM tag value) of POI.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setPoiSearch($iOperator, $sClass, $sType)
|
|
||||||
{
|
|
||||||
$this->iOperator = $iOperator;
|
|
||||||
$this->sClass = $sClass;
|
|
||||||
$this->sType = $sType;
|
|
||||||
$this->iNamePhrase = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNamePhrase()
|
|
||||||
{
|
|
||||||
return $this->iNamePhrase;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the global search context.
|
|
||||||
*
|
|
||||||
* @return object Objects of global search constraints.
|
|
||||||
*/
|
|
||||||
public function getContext()
|
|
||||||
{
|
|
||||||
return $this->oContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////// Query functions
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Query database for places that match this search.
|
|
||||||
*
|
|
||||||
* @param object $oDB Nominatim::DB instance to use.
|
|
||||||
* @param integer $iMinRank Minimum address rank to restrict search to.
|
|
||||||
* @param integer $iMaxRank Maximum address rank to restrict search to.
|
|
||||||
* @param integer $iLimit Maximum number of results.
|
|
||||||
*
|
|
||||||
* @return mixed[] An array with two fields: IDs contains the list of
|
|
||||||
* matching place IDs and houseNumber the houseNumber
|
|
||||||
* if applicable or -1 if not.
|
|
||||||
*/
|
|
||||||
public function query(&$oDB, $iMinRank, $iMaxRank, $iLimit)
|
|
||||||
{
|
|
||||||
$aResults = array();
|
|
||||||
|
|
||||||
if ($this->sCountryCode
|
|
||||||
&& empty($this->aName)
|
|
||||||
&& !$this->iOperator
|
|
||||||
&& !$this->sClass
|
|
||||||
&& !$this->oContext->hasNearPoint()
|
|
||||||
) {
|
|
||||||
// Just looking for a country - look it up
|
|
||||||
if (4 >= $iMinRank && 4 <= $iMaxRank) {
|
|
||||||
$aResults = $this->queryCountry($oDB);
|
|
||||||
}
|
|
||||||
} elseif (empty($this->aName) && empty($this->aAddress)) {
|
|
||||||
// Neither name nor address? Then we must be
|
|
||||||
// looking for a POI in a geographic area.
|
|
||||||
if ($this->oContext->isBoundedSearch()) {
|
|
||||||
$aResults = $this->queryNearbyPoi($oDB, $iLimit);
|
|
||||||
}
|
|
||||||
} elseif ($this->iOperator == Operator::POSTCODE) {
|
|
||||||
// looking for postcode
|
|
||||||
$aResults = $this->queryPostcode($oDB, $iLimit);
|
|
||||||
} else {
|
|
||||||
// Ordinary search:
|
|
||||||
// First search for places according to name and address.
|
|
||||||
$aResults = $this->queryNamedPlace(
|
|
||||||
$oDB,
|
|
||||||
$iMinRank,
|
|
||||||
$iMaxRank,
|
|
||||||
$iLimit
|
|
||||||
);
|
|
||||||
|
|
||||||
// finally get POIs if requested
|
|
||||||
if ($this->sClass && !empty($aResults)) {
|
|
||||||
$aResults = $this->queryPoiByOperator($oDB, $aResults, $iLimit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::printDebugTable('Place IDs', $aResults);
|
|
||||||
|
|
||||||
if (!empty($aResults) && $this->sPostcode) {
|
|
||||||
$sPlaceIds = Result::joinIdsByTable($aResults, Result::TABLE_PLACEX);
|
|
||||||
if ($sPlaceIds) {
|
|
||||||
$sSQL = 'SELECT place_id FROM placex';
|
|
||||||
$sSQL .= ' WHERE place_id in ('.$sPlaceIds.')';
|
|
||||||
$sSQL .= " AND postcode != '".$this->sPostcode."'";
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
$aFilteredPlaceIDs = $oDB->getCol($sSQL);
|
|
||||||
if ($aFilteredPlaceIDs) {
|
|
||||||
foreach ($aFilteredPlaceIDs as $iPlaceId) {
|
|
||||||
$aResults[$iPlaceId]->iResultRank++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function queryCountry(&$oDB)
|
|
||||||
{
|
|
||||||
$sSQL = 'SELECT place_id FROM placex ';
|
|
||||||
$sSQL .= "WHERE country_code='".$this->sCountryCode."'";
|
|
||||||
$sSQL .= ' AND rank_search = 4';
|
|
||||||
if ($this->oContext->bViewboxBounded) {
|
|
||||||
$sSQL .= ' AND ST_Intersects('.$this->oContext->sqlViewboxSmall.', geometry)';
|
|
||||||
}
|
|
||||||
$sSQL .= ' ORDER BY st_area(geometry) DESC LIMIT 1';
|
|
||||||
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
$iPlaceId = $oDB->getOne($sSQL);
|
|
||||||
|
|
||||||
$aResults = array();
|
|
||||||
if ($iPlaceId) {
|
|
||||||
$aResults[$iPlaceId] = new Result($iPlaceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function queryNearbyPoi(&$oDB, $iLimit)
|
|
||||||
{
|
|
||||||
if (!$this->sClass) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
$aDBResults = array();
|
|
||||||
$sPoiTable = $this->poiTable();
|
|
||||||
|
|
||||||
if ($oDB->tableExists($sPoiTable)) {
|
|
||||||
$sSQL = 'SELECT place_id FROM '.$sPoiTable.' ct';
|
|
||||||
if ($this->oContext->sqlCountryList) {
|
|
||||||
$sSQL .= ' JOIN placex USING (place_id)';
|
|
||||||
}
|
|
||||||
if ($this->oContext->hasNearPoint()) {
|
|
||||||
$sSQL .= ' WHERE '.$this->oContext->withinSQL('ct.centroid');
|
|
||||||
} elseif ($this->oContext->bViewboxBounded) {
|
|
||||||
$sSQL .= ' WHERE ST_Contains('.$this->oContext->sqlViewboxSmall.', ct.centroid)';
|
|
||||||
}
|
|
||||||
if ($this->oContext->sqlCountryList) {
|
|
||||||
$sSQL .= ' AND country_code in '.$this->oContext->sqlCountryList;
|
|
||||||
}
|
|
||||||
$sSQL .= $this->oContext->excludeSQL(' AND place_id');
|
|
||||||
if ($this->oContext->sqlViewboxCentre) {
|
|
||||||
$sSQL .= ' ORDER BY ST_Distance(';
|
|
||||||
$sSQL .= $this->oContext->sqlViewboxCentre.', ct.centroid) ASC';
|
|
||||||
} elseif ($this->oContext->hasNearPoint()) {
|
|
||||||
$sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('ct.centroid').' ASC';
|
|
||||||
}
|
|
||||||
$sSQL .= " LIMIT $iLimit";
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
$aDBResults = $oDB->getCol($sSQL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->oContext->hasNearPoint()) {
|
|
||||||
$sSQL = 'SELECT place_id FROM placex WHERE ';
|
|
||||||
$sSQL .= 'class = :class and type = :type';
|
|
||||||
$sSQL .= ' AND '.$this->oContext->withinSQL('geometry');
|
|
||||||
$sSQL .= ' AND linked_place_id is null';
|
|
||||||
if ($this->oContext->sqlCountryList) {
|
|
||||||
$sSQL .= ' AND country_code in '.$this->oContext->sqlCountryList;
|
|
||||||
}
|
|
||||||
$sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('centroid').' ASC';
|
|
||||||
$sSQL .= " LIMIT $iLimit";
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
$aDBResults = $oDB->getCol(
|
|
||||||
$sSQL,
|
|
||||||
array(':class' => $this->sClass, ':type' => $this->sType)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$aResults = array();
|
|
||||||
foreach ($aDBResults as $iPlaceId) {
|
|
||||||
$aResults[$iPlaceId] = new Result($iPlaceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function queryPostcode(&$oDB, $iLimit)
|
|
||||||
{
|
|
||||||
$sSQL = 'SELECT p.place_id FROM location_postcode p ';
|
|
||||||
|
|
||||||
if (!empty($this->aAddress)) {
|
|
||||||
$sSQL .= ', search_name s ';
|
|
||||||
$sSQL .= 'WHERE s.place_id = p.parent_place_id ';
|
|
||||||
$sSQL .= 'AND array_cat(s.nameaddress_vector, s.name_vector)';
|
|
||||||
$sSQL .= ' @> '.$oDB->getArraySQL($this->aAddress).' AND ';
|
|
||||||
} else {
|
|
||||||
$sSQL .= 'WHERE ';
|
|
||||||
}
|
|
||||||
|
|
||||||
$sSQL .= "p.postcode = '".reset($this->aName)."'";
|
|
||||||
$sSQL .= $this->countryCodeSQL(' AND p.country_code');
|
|
||||||
if ($this->oContext->bViewboxBounded) {
|
|
||||||
$sSQL .= ' AND ST_Intersects('.$this->oContext->sqlViewboxSmall.', geometry)';
|
|
||||||
}
|
|
||||||
$sSQL .= $this->oContext->excludeSQL(' AND p.place_id');
|
|
||||||
$sSQL .= " LIMIT $iLimit";
|
|
||||||
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
$aResults = array();
|
|
||||||
foreach ($oDB->getCol($sSQL) as $iPlaceId) {
|
|
||||||
$aResults[$iPlaceId] = new Result($iPlaceId, Result::TABLE_POSTCODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function queryNamedPlace(&$oDB, $iMinAddressRank, $iMaxAddressRank, $iLimit)
|
|
||||||
{
|
|
||||||
$aTerms = array();
|
|
||||||
$aOrder = array();
|
|
||||||
|
|
||||||
if (!empty($this->aName)) {
|
|
||||||
$aTerms[] = 'name_vector @> '.$oDB->getArraySQL($this->aName);
|
|
||||||
}
|
|
||||||
if (!empty($this->aAddress)) {
|
|
||||||
// For infrequent name terms disable index usage for address
|
|
||||||
if ($this->bRareName) {
|
|
||||||
$aTerms[] = 'array_cat(nameaddress_vector,ARRAY[]::integer[]) @> '.$oDB->getArraySQL($this->aAddress);
|
|
||||||
} else {
|
|
||||||
$aTerms[] = 'nameaddress_vector @> '.$oDB->getArraySQL($this->aAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$sCountryTerm = $this->countryCodeSQL('country_code');
|
|
||||||
if ($sCountryTerm) {
|
|
||||||
$aTerms[] = $sCountryTerm;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->sHouseNumber) {
|
|
||||||
$aTerms[] = 'address_rank between 16 and 30';
|
|
||||||
} elseif (!$this->sClass || $this->iOperator == Operator::NAME) {
|
|
||||||
if ($iMinAddressRank > 0) {
|
|
||||||
$aTerms[] = "((address_rank between $iMinAddressRank and $iMaxAddressRank) or (search_rank between $iMinAddressRank and $iMaxAddressRank))";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->oContext->hasNearPoint()) {
|
|
||||||
$aTerms[] = $this->oContext->withinSQL('centroid');
|
|
||||||
$aOrder[] = $this->oContext->distanceSQL('centroid');
|
|
||||||
} elseif ($this->sPostcode) {
|
|
||||||
if (empty($this->aAddress)) {
|
|
||||||
$aTerms[] = "EXISTS(SELECT place_id FROM location_postcode p WHERE p.postcode = '".$this->sPostcode."' AND ST_DWithin(search_name.centroid, p.geometry, 0.12))";
|
|
||||||
} else {
|
|
||||||
$aOrder[] = "(SELECT min(ST_Distance(search_name.centroid, p.geometry)) FROM location_postcode p WHERE p.postcode = '".$this->sPostcode."')";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$sExcludeSQL = $this->oContext->excludeSQL('place_id');
|
|
||||||
if ($sExcludeSQL) {
|
|
||||||
$aTerms[] = $sExcludeSQL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->oContext->bViewboxBounded) {
|
|
||||||
$aTerms[] = 'centroid && '.$this->oContext->sqlViewboxSmall;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->sHouseNumber) {
|
|
||||||
$sImportanceSQL = '- abs(26 - address_rank) + 3';
|
|
||||||
} else {
|
|
||||||
$sImportanceSQL = '(CASE WHEN importance = 0 OR importance IS NULL THEN 0.75001-(search_rank::float/40) ELSE importance END)';
|
|
||||||
}
|
|
||||||
$sImportanceSQL .= $this->oContext->viewboxImportanceSQL('centroid');
|
|
||||||
$aOrder[] = "$sImportanceSQL DESC";
|
|
||||||
|
|
||||||
$aFullNameAddress = $this->oContext->getFullNameTerms();
|
|
||||||
if (!empty($aFullNameAddress)) {
|
|
||||||
$sExactMatchSQL = ' ( ';
|
|
||||||
$sExactMatchSQL .= ' SELECT count(*) FROM ( ';
|
|
||||||
$sExactMatchSQL .= ' SELECT unnest('.$oDB->getArraySQL($aFullNameAddress).')';
|
|
||||||
$sExactMatchSQL .= ' INTERSECT ';
|
|
||||||
$sExactMatchSQL .= ' SELECT unnest(nameaddress_vector)';
|
|
||||||
$sExactMatchSQL .= ' ) s';
|
|
||||||
$sExactMatchSQL .= ') as exactmatch';
|
|
||||||
$aOrder[] = 'exactmatch DESC';
|
|
||||||
} else {
|
|
||||||
$sExactMatchSQL = '0::int as exactmatch';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($aTerms)) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->hasHousenumber()) {
|
|
||||||
$sHouseNumberRegex = $oDB->getDBQuoted('\\\\m'.$this->sHouseNumber.'\\\\M');
|
|
||||||
|
|
||||||
// Housenumbers on streets and places.
|
|
||||||
$sPlacexSql = 'SELECT array_agg(place_id) FROM placex';
|
|
||||||
$sPlacexSql .= ' WHERE parent_place_id = sin.place_id AND sin.address_rank < 30';
|
|
||||||
$sPlacexSql .= $this->oContext->excludeSQL(' AND place_id');
|
|
||||||
$sPlacexSql .= ' and housenumber ~* E'.$sHouseNumberRegex;
|
|
||||||
|
|
||||||
// Interpolations on streets and places.
|
|
||||||
$sInterpolSql = 'null';
|
|
||||||
$sTigerSql = 'null';
|
|
||||||
if (preg_match('/^[0-9]+$/', $this->sHouseNumber)) {
|
|
||||||
$sIpolHnr = 'WHERE parent_place_id = sin.place_id ';
|
|
||||||
$sIpolHnr .= ' AND startnumber is not NULL AND sin.address_rank < 30';
|
|
||||||
$sIpolHnr .= ' AND '.$this->sHouseNumber.' between startnumber and endnumber';
|
|
||||||
$sIpolHnr .= ' AND ('.$this->sHouseNumber.' - startnumber) % step = 0';
|
|
||||||
|
|
||||||
$sInterpolSql = 'SELECT array_agg(place_id) FROM location_property_osmline '.$sIpolHnr;
|
|
||||||
if (CONST_Use_US_Tiger_Data) {
|
|
||||||
$sTigerSql = 'SELECT array_agg(place_id) FROM location_property_tiger '.$sIpolHnr;
|
|
||||||
$sTigerSql .= " and sin.country_code = 'us'";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->sClass) {
|
|
||||||
$iLimit = 40;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sSelfHnr = 'SELECT * FROM placex WHERE place_id = search_name.place_id';
|
|
||||||
$sSelfHnr .= ' AND housenumber ~* E'.$sHouseNumberRegex;
|
|
||||||
|
|
||||||
$aTerms[] = '(address_rank < 30 or exists('.$sSelfHnr.'))';
|
|
||||||
|
|
||||||
|
|
||||||
$sSQL = 'SELECT sin.*, ';
|
|
||||||
$sSQL .= '('.$sPlacexSql.') as placex_hnr, ';
|
|
||||||
$sSQL .= '('.$sInterpolSql.') as interpol_hnr, ';
|
|
||||||
$sSQL .= '('.$sTigerSql.') as tiger_hnr ';
|
|
||||||
$sSQL .= ' FROM (';
|
|
||||||
$sSQL .= ' SELECT place_id, address_rank, country_code,'.$sExactMatchSQL.',';
|
|
||||||
$sSQL .= ' CASE WHEN importance = 0 OR importance IS NULL';
|
|
||||||
$sSQL .= ' THEN 0.75001-(search_rank::float/40) ELSE importance END as importance';
|
|
||||||
$sSQL .= ' FROM search_name';
|
|
||||||
$sSQL .= ' WHERE '.join(' and ', $aTerms);
|
|
||||||
$sSQL .= ' ORDER BY '.join(', ', $aOrder);
|
|
||||||
$sSQL .= ' LIMIT 40000';
|
|
||||||
$sSQL .= ') as sin';
|
|
||||||
$sSQL .= ' ORDER BY address_rank = 30 desc, placex_hnr, interpol_hnr, tiger_hnr,';
|
|
||||||
$sSQL .= ' importance';
|
|
||||||
$sSQL .= ' LIMIT '.$iLimit;
|
|
||||||
} else {
|
|
||||||
if ($this->sClass) {
|
|
||||||
$iLimit = 40;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sSQL = 'SELECT place_id, address_rank, '.$sExactMatchSQL;
|
|
||||||
$sSQL .= ' FROM search_name';
|
|
||||||
$sSQL .= ' WHERE '.join(' and ', $aTerms);
|
|
||||||
$sSQL .= ' ORDER BY '.join(', ', $aOrder);
|
|
||||||
$sSQL .= ' LIMIT '.$iLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
$aDBResults = $oDB->getAll($sSQL, null, 'Could not get places for search terms.');
|
|
||||||
|
|
||||||
$aResults = array();
|
|
||||||
|
|
||||||
foreach ($aDBResults as $aResult) {
|
|
||||||
$oResult = new Result($aResult['place_id']);
|
|
||||||
$oResult->iExactMatches = $aResult['exactmatch'];
|
|
||||||
$oResult->iAddressRank = $aResult['address_rank'];
|
|
||||||
|
|
||||||
$bNeedResult = true;
|
|
||||||
if ($this->hasHousenumber() && $aResult['address_rank'] < 30) {
|
|
||||||
if ($aResult['placex_hnr']) {
|
|
||||||
foreach (explode(',', substr($aResult['placex_hnr'], 1, -1)) as $sPlaceID) {
|
|
||||||
$iPlaceID = intval($sPlaceID);
|
|
||||||
$oHnrResult = new Result($iPlaceID);
|
|
||||||
$oHnrResult->iExactMatches = $aResult['exactmatch'];
|
|
||||||
$oHnrResult->iAddressRank = 30;
|
|
||||||
$aResults[$iPlaceID] = $oHnrResult;
|
|
||||||
$bNeedResult = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($aResult['interpol_hnr']) {
|
|
||||||
foreach (explode(',', substr($aResult['interpol_hnr'], 1, -1)) as $sPlaceID) {
|
|
||||||
$iPlaceID = intval($sPlaceID);
|
|
||||||
$oHnrResult = new Result($iPlaceID, Result::TABLE_OSMLINE);
|
|
||||||
$oHnrResult->iExactMatches = $aResult['exactmatch'];
|
|
||||||
$oHnrResult->iAddressRank = 30;
|
|
||||||
$oHnrResult->iHouseNumber = intval($this->sHouseNumber);
|
|
||||||
$aResults[$iPlaceID] = $oHnrResult;
|
|
||||||
$bNeedResult = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($aResult['tiger_hnr']) {
|
|
||||||
foreach (explode(',', substr($aResult['tiger_hnr'], 1, -1)) as $sPlaceID) {
|
|
||||||
$iPlaceID = intval($sPlaceID);
|
|
||||||
$oHnrResult = new Result($iPlaceID, Result::TABLE_TIGER);
|
|
||||||
$oHnrResult->iExactMatches = $aResult['exactmatch'];
|
|
||||||
$oHnrResult->iAddressRank = 30;
|
|
||||||
$oHnrResult->iHouseNumber = intval($this->sHouseNumber);
|
|
||||||
$aResults[$iPlaceID] = $oHnrResult;
|
|
||||||
$bNeedResult = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($aResult['address_rank'] < 26) {
|
|
||||||
$oResult->iResultRank += 2;
|
|
||||||
} else {
|
|
||||||
$oResult->iResultRank++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($bNeedResult) {
|
|
||||||
$aResults[$aResult['place_id']] = $oResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function queryPoiByOperator(&$oDB, $aParentIDs, $iLimit)
|
|
||||||
{
|
|
||||||
$aResults = array();
|
|
||||||
$sPlaceIDs = Result::joinIdsByTable($aParentIDs, Result::TABLE_PLACEX);
|
|
||||||
|
|
||||||
if (!$sPlaceIDs) {
|
|
||||||
return $aResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->iOperator == Operator::TYPE || $this->iOperator == Operator::NAME) {
|
|
||||||
// If they were searching for a named class (i.e. 'Kings Head pub')
|
|
||||||
// then we might have an extra match
|
|
||||||
$sSQL = 'SELECT place_id FROM placex ';
|
|
||||||
$sSQL .= " WHERE place_id in ($sPlaceIDs)";
|
|
||||||
$sSQL .= " AND class='".$this->sClass."' ";
|
|
||||||
$sSQL .= " AND type='".$this->sType."'";
|
|
||||||
$sSQL .= ' AND linked_place_id is null';
|
|
||||||
$sSQL .= $this->oContext->excludeSQL(' AND place_id');
|
|
||||||
$sSQL .= ' ORDER BY rank_search ASC ';
|
|
||||||
$sSQL .= " LIMIT $iLimit";
|
|
||||||
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
foreach ($oDB->getCol($sSQL) as $iPlaceId) {
|
|
||||||
$aResults[$iPlaceId] = new Result($iPlaceId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NEAR and IN are handled the same
|
|
||||||
if ($this->iOperator == Operator::TYPE || $this->iOperator == Operator::NEAR) {
|
|
||||||
$sClassTable = $this->poiTable();
|
|
||||||
$bCacheTable = $oDB->tableExists($sClassTable);
|
|
||||||
|
|
||||||
$sSQL = "SELECT min(rank_search) FROM placex WHERE place_id in ($sPlaceIDs)";
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
$iMaxRank = (int) $oDB->getOne($sSQL);
|
|
||||||
|
|
||||||
// For state / country level searches the normal radius search doesn't work very well
|
|
||||||
$sPlaceGeom = false;
|
|
||||||
if ($iMaxRank < 9 && $bCacheTable) {
|
|
||||||
// Try and get a polygon to search in instead
|
|
||||||
$sSQL = 'SELECT geometry FROM placex';
|
|
||||||
$sSQL .= " WHERE place_id in ($sPlaceIDs)";
|
|
||||||
$sSQL .= " AND rank_search < $iMaxRank + 5";
|
|
||||||
$sSQL .= ' AND ST_Area(Box2d(geometry)) < 20';
|
|
||||||
$sSQL .= " AND ST_GeometryType(geometry) in ('ST_Polygon','ST_MultiPolygon')";
|
|
||||||
$sSQL .= ' ORDER BY rank_search ASC ';
|
|
||||||
$sSQL .= ' LIMIT 1';
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
$sPlaceGeom = $oDB->getOne($sSQL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($sPlaceGeom) {
|
|
||||||
$sPlaceIDs = false;
|
|
||||||
} else {
|
|
||||||
$iMaxRank += 5;
|
|
||||||
$sSQL = 'SELECT place_id FROM placex';
|
|
||||||
$sSQL .= " WHERE place_id in ($sPlaceIDs) and rank_search < $iMaxRank";
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
$aPlaceIDs = $oDB->getCol($sSQL);
|
|
||||||
$sPlaceIDs = join(',', $aPlaceIDs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($sPlaceIDs || $sPlaceGeom) {
|
|
||||||
$fRange = 0.01;
|
|
||||||
if ($bCacheTable) {
|
|
||||||
// More efficient - can make the range bigger
|
|
||||||
$fRange = 0.05;
|
|
||||||
|
|
||||||
$sOrderBySQL = '';
|
|
||||||
if ($this->oContext->hasNearPoint()) {
|
|
||||||
$sOrderBySQL = $this->oContext->distanceSQL('l.centroid');
|
|
||||||
} elseif ($sPlaceIDs) {
|
|
||||||
$sOrderBySQL = 'ST_Distance(l.centroid, f.geometry)';
|
|
||||||
} elseif ($sPlaceGeom) {
|
|
||||||
$sOrderBySQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)";
|
|
||||||
}
|
|
||||||
|
|
||||||
$sSQL = 'SELECT distinct i.place_id';
|
|
||||||
if ($sOrderBySQL) {
|
|
||||||
$sSQL .= ', i.order_term';
|
|
||||||
}
|
|
||||||
$sSQL .= ' from (SELECT l.place_id';
|
|
||||||
if ($sOrderBySQL) {
|
|
||||||
$sSQL .= ','.$sOrderBySQL.' as order_term';
|
|
||||||
}
|
|
||||||
$sSQL .= ' from '.$sClassTable.' as l';
|
|
||||||
|
|
||||||
if ($sPlaceIDs) {
|
|
||||||
$sSQL .= ',placex as f WHERE ';
|
|
||||||
$sSQL .= "f.place_id in ($sPlaceIDs) ";
|
|
||||||
$sSQL .= " AND ST_DWithin(l.centroid, f.centroid, $fRange)";
|
|
||||||
} elseif ($sPlaceGeom) {
|
|
||||||
$sSQL .= " WHERE ST_Contains('$sPlaceGeom', l.centroid)";
|
|
||||||
}
|
|
||||||
|
|
||||||
$sSQL .= $this->oContext->excludeSQL(' AND l.place_id');
|
|
||||||
$sSQL .= 'limit 300) i ';
|
|
||||||
if ($sOrderBySQL) {
|
|
||||||
$sSQL .= 'order by order_term asc';
|
|
||||||
}
|
|
||||||
$sSQL .= " limit $iLimit";
|
|
||||||
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
foreach ($oDB->getCol($sSQL) as $iPlaceId) {
|
|
||||||
$aResults[$iPlaceId] = new Result($iPlaceId);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ($this->oContext->hasNearPoint()) {
|
|
||||||
$fRange = $this->oContext->nearRadius();
|
|
||||||
}
|
|
||||||
|
|
||||||
$sOrderBySQL = '';
|
|
||||||
if ($this->oContext->hasNearPoint()) {
|
|
||||||
$sOrderBySQL = $this->oContext->distanceSQL('l.geometry');
|
|
||||||
} else {
|
|
||||||
$sOrderBySQL = 'ST_Distance(l.geometry, f.geometry)';
|
|
||||||
}
|
|
||||||
|
|
||||||
$sSQL = 'SELECT distinct l.place_id';
|
|
||||||
if ($sOrderBySQL) {
|
|
||||||
$sSQL .= ','.$sOrderBySQL.' as orderterm';
|
|
||||||
}
|
|
||||||
$sSQL .= ' FROM placex as l, placex as f';
|
|
||||||
$sSQL .= " WHERE f.place_id in ($sPlaceIDs)";
|
|
||||||
$sSQL .= " AND ST_DWithin(l.geometry, f.centroid, $fRange)";
|
|
||||||
$sSQL .= " AND l.class='".$this->sClass."'";
|
|
||||||
$sSQL .= " AND l.type='".$this->sType."'";
|
|
||||||
$sSQL .= $this->oContext->excludeSQL(' AND l.place_id');
|
|
||||||
if ($sOrderBySQL) {
|
|
||||||
$sSQL .= 'ORDER BY orderterm ASC';
|
|
||||||
}
|
|
||||||
$sSQL .= " limit $iLimit";
|
|
||||||
|
|
||||||
Debug::printSQL($sSQL);
|
|
||||||
|
|
||||||
foreach ($oDB->getCol($sSQL) as $iPlaceId) {
|
|
||||||
$aResults[$iPlaceId] = new Result($iPlaceId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function poiTable()
|
|
||||||
{
|
|
||||||
return 'place_classtype_'.$this->sClass.'_'.$this->sType;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function countryCodeSQL($sVar)
|
|
||||||
{
|
|
||||||
if ($this->sCountryCode) {
|
|
||||||
return $sVar.' = \''.$this->sCountryCode."'";
|
|
||||||
}
|
|
||||||
if ($this->oContext->sqlCountryList) {
|
|
||||||
return $sVar.' in '.$this->oContext->sqlCountryList;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////// Sort functions
|
|
||||||
|
|
||||||
|
|
||||||
public static function bySearchRank($a, $b)
|
|
||||||
{
|
|
||||||
if ($a->iSearchRank == $b->iSearchRank) {
|
|
||||||
return $a->iOperator + strlen($a->sHouseNumber)
|
|
||||||
- $b->iOperator - strlen($b->sHouseNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $a->iSearchRank < $b->iSearchRank ? -1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////// Debugging functions
|
|
||||||
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'Search rank' => $this->iSearchRank,
|
|
||||||
'Country code' => $this->sCountryCode,
|
|
||||||
'Name terms' => $this->aName,
|
|
||||||
'Name terms (stop words)' => $this->aNameNonSearch,
|
|
||||||
'Address terms' => $this->aAddress,
|
|
||||||
'Address terms (stop words)' => $this->aAddressNonSearch,
|
|
||||||
'Address terms (full words)' => $this->aFullNameAddress ?? '',
|
|
||||||
'Special search' => $this->iOperator,
|
|
||||||
'Class' => $this->sClass,
|
|
||||||
'Type' => $this->sType,
|
|
||||||
'House number' => $this->sHouseNumber,
|
|
||||||
'Postcode' => $this->sPostcode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function dumpAsHtmlTableRow(&$aWordIDs)
|
|
||||||
{
|
|
||||||
$kf = function ($k) use (&$aWordIDs) {
|
|
||||||
return $aWordIDs[$k] ?? '['.$k.']';
|
|
||||||
};
|
|
||||||
|
|
||||||
echo '<tr>';
|
|
||||||
echo "<td>$this->iSearchRank</td>";
|
|
||||||
echo '<td>'.join(', ', array_map($kf, $this->aName)).'</td>';
|
|
||||||
echo '<td>'.join(', ', array_map($kf, $this->aNameNonSearch)).'</td>';
|
|
||||||
echo '<td>'.join(', ', array_map($kf, $this->aAddress)).'</td>';
|
|
||||||
echo '<td>'.join(', ', array_map($kf, $this->aAddressNonSearch)).'</td>';
|
|
||||||
echo '<td>'.$this->sCountryCode.'</td>';
|
|
||||||
echo '<td>'.Operator::toString($this->iOperator).'</td>';
|
|
||||||
echo '<td>'.$this->sClass.'</td>';
|
|
||||||
echo '<td>'.$this->sType.'</td>';
|
|
||||||
echo '<td>'.$this->sPostcode.'</td>';
|
|
||||||
echo '<td>'.$this->sHouseNumber.'</td>';
|
|
||||||
|
|
||||||
echo '</tr>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Description of the position of a token within a query.
|
|
||||||
*/
|
|
||||||
class SearchPosition
|
|
||||||
{
|
|
||||||
private $sPhraseType;
|
|
||||||
|
|
||||||
private $iPhrase;
|
|
||||||
private $iNumPhrases;
|
|
||||||
|
|
||||||
private $iToken;
|
|
||||||
private $iNumTokens;
|
|
||||||
|
|
||||||
|
|
||||||
public function __construct($sPhraseType, $iPhrase, $iNumPhrases)
|
|
||||||
{
|
|
||||||
$this->sPhraseType = $sPhraseType;
|
|
||||||
$this->iPhrase = $iPhrase;
|
|
||||||
$this->iNumPhrases = $iNumPhrases;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTokenPosition($iToken, $iNumTokens)
|
|
||||||
{
|
|
||||||
$this->iToken = $iToken;
|
|
||||||
$this->iNumTokens = $iNumTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the phrase can be of the given type.
|
|
||||||
*
|
|
||||||
* @param string $sType Type of phrse requested.
|
|
||||||
*
|
|
||||||
* @return True if the phrase is untyped or of the given type.
|
|
||||||
*/
|
|
||||||
public function maybePhrase($sType)
|
|
||||||
{
|
|
||||||
return $this->sPhraseType == '' || $this->sPhraseType == $sType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the phrase is exactly of the given type.
|
|
||||||
*
|
|
||||||
* @param string $sType Type of phrse requested.
|
|
||||||
*
|
|
||||||
* @return True if the phrase of the given type.
|
|
||||||
*/
|
|
||||||
public function isPhrase($sType)
|
|
||||||
{
|
|
||||||
return $this->sPhraseType == $sType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if the token is the very first in the query.
|
|
||||||
*/
|
|
||||||
public function isFirstToken()
|
|
||||||
{
|
|
||||||
return $this->iPhrase == 0 && $this->iToken == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the token is the final one in the query.
|
|
||||||
*/
|
|
||||||
public function isLastToken()
|
|
||||||
{
|
|
||||||
return $this->iToken + 1 == $this->iNumTokens && $this->iPhrase + 1 == $this->iNumPhrases;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the current token is part of the first phrase in the query.
|
|
||||||
*/
|
|
||||||
public function isFirstPhrase()
|
|
||||||
{
|
|
||||||
return $this->iPhrase == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the phrase position in the query.
|
|
||||||
*/
|
|
||||||
public function getPhrase()
|
|
||||||
{
|
|
||||||
return $this->iPhrase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
class Shell
|
|
||||||
{
|
|
||||||
public function __construct($sBaseCmd, ...$aParams)
|
|
||||||
{
|
|
||||||
if (!$sBaseCmd) {
|
|
||||||
throw new \Exception('Command missing in new() call');
|
|
||||||
}
|
|
||||||
$this->baseCmd = $sBaseCmd;
|
|
||||||
$this->aParams = array();
|
|
||||||
$this->aEnv = null; // null = use the same environment as the current PHP process
|
|
||||||
|
|
||||||
$this->stdoutString = null;
|
|
||||||
|
|
||||||
foreach ($aParams as $sParam) {
|
|
||||||
$this->addParams($sParam);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addParams(...$aParams)
|
|
||||||
{
|
|
||||||
foreach ($aParams as $sParam) {
|
|
||||||
if (isset($sParam) && $sParam !== null && $sParam !== '') {
|
|
||||||
array_push($this->aParams, $sParam);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addEnvPair($sKey, $sVal)
|
|
||||||
{
|
|
||||||
if (isset($sKey) && $sKey && isset($sVal)) {
|
|
||||||
if (!isset($this->aEnv)) {
|
|
||||||
$this->aEnv = $_ENV;
|
|
||||||
}
|
|
||||||
$this->aEnv = array_merge($this->aEnv, array($sKey => $sVal), $_ENV);
|
|
||||||
}
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function escapedCmd()
|
|
||||||
{
|
|
||||||
$aEscaped = array_map(function ($sParam) {
|
|
||||||
return $this->escapeParam($sParam);
|
|
||||||
}, array_merge(array($this->baseCmd), $this->aParams));
|
|
||||||
|
|
||||||
return join(' ', $aEscaped);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function run($bExitOnFail = false)
|
|
||||||
{
|
|
||||||
$sCmd = $this->escapedCmd();
|
|
||||||
// $aEnv does not need escaping, proc_open seems to handle it fine
|
|
||||||
|
|
||||||
$aFDs = array(
|
|
||||||
0 => array('pipe', 'r'),
|
|
||||||
1 => STDOUT,
|
|
||||||
2 => STDERR
|
|
||||||
);
|
|
||||||
$aPipes = null;
|
|
||||||
$hProc = @proc_open($sCmd, $aFDs, $aPipes, null, $this->aEnv);
|
|
||||||
if (!is_resource($hProc)) {
|
|
||||||
throw new \Exception('Unable to run command: ' . $sCmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose($aPipes[0]); // no stdin
|
|
||||||
|
|
||||||
$iStat = proc_close($hProc);
|
|
||||||
|
|
||||||
if ($iStat != 0 && $bExitOnFail) {
|
|
||||||
exit($iStat);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $iStat;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function escapeParam($sParam)
|
|
||||||
{
|
|
||||||
return (preg_match('/^-*\w+$/', $sParam)) ? $sParam : escapeshellarg($sParam);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A word list creator based on simple splitting by space.
|
|
||||||
*
|
|
||||||
* Creates possible permutations of split phrases by finding all combination
|
|
||||||
* of splitting the phrase on space boundaries.
|
|
||||||
*/
|
|
||||||
class SimpleWordList
|
|
||||||
{
|
|
||||||
const MAX_WORDSET_LEN = 20;
|
|
||||||
const MAX_WORDSETS = 100;
|
|
||||||
|
|
||||||
// The phrase as a list of simple terms (without spaces).
|
|
||||||
private $aWords;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new word list
|
|
||||||
*
|
|
||||||
* @param string sPhrase Phrase to create the word list from. The phrase is
|
|
||||||
* expected to be normalised, so that there are no
|
|
||||||
* subsequent spaces.
|
|
||||||
*/
|
|
||||||
public function __construct($sPhrase)
|
|
||||||
{
|
|
||||||
if (strlen($sPhrase) > 0) {
|
|
||||||
$this->aWords = explode(' ', $sPhrase);
|
|
||||||
} else {
|
|
||||||
$this->aWords = array();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all possible tokens that are present in this word list.
|
|
||||||
*
|
|
||||||
* @return array The list of string tokens in the word list.
|
|
||||||
*/
|
|
||||||
public function getTokens()
|
|
||||||
{
|
|
||||||
$aTokens = array();
|
|
||||||
$iNumWords = count($this->aWords);
|
|
||||||
|
|
||||||
for ($i = 0; $i < $iNumWords; $i++) {
|
|
||||||
$sPhrase = $this->aWords[$i];
|
|
||||||
$aTokens[$sPhrase] = $sPhrase;
|
|
||||||
|
|
||||||
for ($j = $i + 1; $j < $iNumWords; $j++) {
|
|
||||||
$sPhrase .= ' '.$this->aWords[$j];
|
|
||||||
$aTokens[$sPhrase] = $sPhrase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute all possible permutations of phrase splits that result in
|
|
||||||
* words which are in the token list.
|
|
||||||
*/
|
|
||||||
public function getWordSets($oTokens)
|
|
||||||
{
|
|
||||||
$iNumWords = count($this->aWords);
|
|
||||||
|
|
||||||
if ($iNumWords == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Caches the word set for the partial phrase up to word i.
|
|
||||||
$aSetCache = array_fill(0, $iNumWords, array());
|
|
||||||
|
|
||||||
// Initialise first element of cache. There can only be the word.
|
|
||||||
if ($oTokens->containsAny($this->aWords[0])) {
|
|
||||||
$aSetCache[0][] = array($this->aWords[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now do the next elements using what we already have.
|
|
||||||
for ($i = 1; $i < $iNumWords; $i++) {
|
|
||||||
for ($j = $i; $j > 0; $j--) {
|
|
||||||
$sPartial = $j == $i ? $this->aWords[$j] : $this->aWords[$j].' '.$sPartial;
|
|
||||||
if (!empty($aSetCache[$j - 1]) && $oTokens->containsAny($sPartial)) {
|
|
||||||
$aPartial = array($sPartial);
|
|
||||||
foreach ($aSetCache[$j - 1] as $aSet) {
|
|
||||||
if (count($aSet) < SimpleWordList::MAX_WORDSET_LEN) {
|
|
||||||
$aSetCache[$i][] = array_merge($aSet, $aPartial);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (count($aSetCache[$i]) > 2 * SimpleWordList::MAX_WORDSETS) {
|
|
||||||
usort(
|
|
||||||
$aSetCache[$i],
|
|
||||||
array('\Nominatim\SimpleWordList', 'cmpByArraylen')
|
|
||||||
);
|
|
||||||
$aSetCache[$i] = array_slice(
|
|
||||||
$aSetCache[$i],
|
|
||||||
0,
|
|
||||||
SimpleWordList::MAX_WORDSETS
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// finally the current full phrase
|
|
||||||
$sPartial = $this->aWords[0].' '.$sPartial;
|
|
||||||
if ($oTokens->containsAny($sPartial)) {
|
|
||||||
$aSetCache[$i][] = array($sPartial);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$aWordSets = $aSetCache[$iNumWords - 1];
|
|
||||||
usort($aWordSets, array('\Nominatim\SimpleWordList', 'cmpByArraylen'));
|
|
||||||
return array_slice($aWordSets, 0, SimpleWordList::MAX_WORDSETS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom search routine which takes two arrays. The array with the fewest
|
|
||||||
* items wins. If same number of items then the one with the longest first
|
|
||||||
* element wins.
|
|
||||||
*/
|
|
||||||
public static function cmpByArraylen($aA, $aB)
|
|
||||||
{
|
|
||||||
$iALen = count($aA);
|
|
||||||
$iBLen = count($aB);
|
|
||||||
|
|
||||||
if ($iALen == $iBLen) {
|
|
||||||
return strlen($aB[0]) <=> strlen($aA[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ($iALen < $iBLen) ? -1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return $this->aWords;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Operators describing special searches.
|
|
||||||
*/
|
|
||||||
abstract class Operator
|
|
||||||
{
|
|
||||||
/// No operator selected.
|
|
||||||
const NONE = 0;
|
|
||||||
/// Search for POI of the given type.
|
|
||||||
const TYPE = 1;
|
|
||||||
/// Search for POIs near the given place.
|
|
||||||
const NEAR = 2;
|
|
||||||
/// Search for POIS in the given place.
|
|
||||||
const IN = 3;
|
|
||||||
/// Search for POIS named as given.
|
|
||||||
const NAME = 4;
|
|
||||||
/// Search for postcodes.
|
|
||||||
const POSTCODE = 5;
|
|
||||||
|
|
||||||
private static $aConstantNames = null;
|
|
||||||
|
|
||||||
|
|
||||||
public static function toString($iOperator)
|
|
||||||
{
|
|
||||||
if ($iOperator == Operator::NONE) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Operator::$aConstantNames === null) {
|
|
||||||
$oReflector = new \ReflectionClass('Nominatim\Operator');
|
|
||||||
$aConstants = $oReflector->getConstants();
|
|
||||||
|
|
||||||
Operator::$aConstantNames = array();
|
|
||||||
foreach ($aConstants as $sName => $iValue) {
|
|
||||||
Operator::$aConstantNames[$iValue] = $sName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Operator::$aConstantNames[$iOperator];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
require_once(CONST_TokenizerDir.'/tokenizer.php');
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
|
|
||||||
class Status
|
|
||||||
{
|
|
||||||
protected $oDB;
|
|
||||||
|
|
||||||
public function __construct(&$oDB)
|
|
||||||
{
|
|
||||||
$this->oDB =& $oDB;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function status()
|
|
||||||
{
|
|
||||||
if (!$this->oDB) {
|
|
||||||
throw new Exception('No database', 700);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->oDB->connect();
|
|
||||||
} catch (\Nominatim\DatabaseError $e) {
|
|
||||||
throw new Exception('Database connection failed', 700);
|
|
||||||
}
|
|
||||||
|
|
||||||
$oTokenizer = new \Nominatim\Tokenizer($this->oDB);
|
|
||||||
$oTokenizer->checkStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function dataDate()
|
|
||||||
{
|
|
||||||
$sSQL = 'SELECT EXTRACT(EPOCH FROM lastimportdate) FROM import_status LIMIT 1';
|
|
||||||
$iDataDateEpoch = $this->oDB->getOne($sSQL);
|
|
||||||
|
|
||||||
if ($iDataDateEpoch === false) {
|
|
||||||
throw new Exception('Import date is not available', 705);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $iDataDateEpoch;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function databaseVersion()
|
|
||||||
{
|
|
||||||
$sSQL = 'SELECT value FROM nominatim_properties WHERE property = \'database_version\'';
|
|
||||||
return $this->oDB->getOne($sSQL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim\Token;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A country token.
|
|
||||||
*/
|
|
||||||
class Country
|
|
||||||
{
|
|
||||||
/// Database word id, if available.
|
|
||||||
private $iId;
|
|
||||||
/// Two-letter country code (lower-cased).
|
|
||||||
private $sCountryCode;
|
|
||||||
|
|
||||||
public function __construct($iId, $sCountryCode)
|
|
||||||
{
|
|
||||||
$this->iId = $iId;
|
|
||||||
$this->sCountryCode = $sCountryCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->iId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the token can be added to the given search.
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return True if the token is compatible with the search configuration
|
|
||||||
* given the position.
|
|
||||||
*/
|
|
||||||
public function isExtendable($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
return !$oSearch->hasCountry()
|
|
||||||
&& $oPosition->maybePhrase('country')
|
|
||||||
&& $oSearch->getContext()->isCountryApplicable($this->sCountryCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return SearchDescription[] List of derived search descriptions.
|
|
||||||
*/
|
|
||||||
public function extendSearch($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
$oNewSearch = $oSearch->clone($oPosition->isLastToken() ? 1 : 6);
|
|
||||||
$oNewSearch->setCountry($this->sCountryCode);
|
|
||||||
|
|
||||||
return array($oNewSearch);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'ID' => $this->iId,
|
|
||||||
'Type' => 'country',
|
|
||||||
'Info' => $this->sCountryCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugCode()
|
|
||||||
{
|
|
||||||
return 'C';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim\Token;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A house number token.
|
|
||||||
*/
|
|
||||||
class HouseNumber
|
|
||||||
{
|
|
||||||
/// Database word id, if available.
|
|
||||||
private $iId;
|
|
||||||
/// Normalized house number.
|
|
||||||
private $sToken;
|
|
||||||
|
|
||||||
public function __construct($iId, $sToken)
|
|
||||||
{
|
|
||||||
$this->iId = $iId;
|
|
||||||
$this->sToken = $sToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->iId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the token can be added to the given search.
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return True if the token is compatible with the search configuration
|
|
||||||
* given the position.
|
|
||||||
*/
|
|
||||||
public function isExtendable($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
return !$oSearch->hasHousenumber()
|
|
||||||
&& !$oSearch->hasOperator(\Nominatim\Operator::POSTCODE)
|
|
||||||
&& $oPosition->maybePhrase('street');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return SearchDescription[] List of derived search descriptions.
|
|
||||||
*/
|
|
||||||
public function extendSearch($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
$aNewSearches = array();
|
|
||||||
|
|
||||||
// sanity check: if the housenumber is not mainly made
|
|
||||||
// up of numbers, add a penalty
|
|
||||||
$iSearchCost = 1;
|
|
||||||
if (preg_match('/\\d/', $this->sToken) === 0
|
|
||||||
|| preg_match_all('/[^0-9 ]/', $this->sToken, $aMatches) > 3) {
|
|
||||||
$iSearchCost += strlen($this->sToken) - 1;
|
|
||||||
}
|
|
||||||
if (!$oSearch->hasOperator(\Nominatim\Operator::NONE)) {
|
|
||||||
$iSearchCost++;
|
|
||||||
}
|
|
||||||
if (empty($this->iId)) {
|
|
||||||
$iSearchCost++;
|
|
||||||
}
|
|
||||||
// also must not appear in the middle of the address
|
|
||||||
if ($oSearch->hasAddress() || $oSearch->hasPostcode()) {
|
|
||||||
$iSearchCost++;
|
|
||||||
}
|
|
||||||
|
|
||||||
$oNewSearch = $oSearch->clone($iSearchCost);
|
|
||||||
$oNewSearch->setHousenumber($this->sToken);
|
|
||||||
$aNewSearches[] = $oNewSearch;
|
|
||||||
|
|
||||||
// Housenumbers may appear in the name when the place has its own
|
|
||||||
// address terms.
|
|
||||||
if ($this->iId !== null
|
|
||||||
&& ($oSearch->getNamePhrase() >= 0 || !$oSearch->hasName())
|
|
||||||
&& !$oSearch->hasAddress()
|
|
||||||
) {
|
|
||||||
$oNewSearch = $oSearch->clone($iSearchCost);
|
|
||||||
$oNewSearch->setHousenumberAsName($this->iId);
|
|
||||||
|
|
||||||
$aNewSearches[] = $oNewSearch;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aNewSearches;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'ID' => $this->iId,
|
|
||||||
'Type' => 'house number',
|
|
||||||
'Info' => array('nr' => $this->sToken)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugCode()
|
|
||||||
{
|
|
||||||
return 'H';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim;
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/TokenCountry.php');
|
|
||||||
require_once(CONST_LibDir.'/TokenHousenumber.php');
|
|
||||||
require_once(CONST_LibDir.'/TokenPostcode.php');
|
|
||||||
require_once(CONST_LibDir.'/TokenSpecialTerm.php');
|
|
||||||
require_once(CONST_LibDir.'/TokenWord.php');
|
|
||||||
require_once(CONST_LibDir.'/TokenPartial.php');
|
|
||||||
require_once(CONST_LibDir.'/SpecialSearchOperator.php');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves information about the tokens that appear in a search query.
|
|
||||||
*
|
|
||||||
* Tokens are sorted by their normalized form, the token word. There are different
|
|
||||||
* kinds of tokens, represented by different Token* classes. Note that
|
|
||||||
* tokens do not have a common base class. All tokens need to have a field
|
|
||||||
* with the word id that points to an entry in the `word` database table
|
|
||||||
* but otherwise the information saved about a token can be very different.
|
|
||||||
*/
|
|
||||||
class TokenList
|
|
||||||
{
|
|
||||||
// List of list of tokens indexed by their word_token.
|
|
||||||
private $aTokens = array();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return total number of tokens.
|
|
||||||
*
|
|
||||||
* @return Integer
|
|
||||||
*/
|
|
||||||
public function count()
|
|
||||||
{
|
|
||||||
return count($this->aTokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if there are tokens for the given token word.
|
|
||||||
*
|
|
||||||
* @param string $sWord Token word to look for.
|
|
||||||
*
|
|
||||||
* @return bool True if there is one or more token for the token word.
|
|
||||||
*/
|
|
||||||
public function contains($sWord)
|
|
||||||
{
|
|
||||||
return isset($this->aTokens[$sWord]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if there are partial or full tokens for the given word.
|
|
||||||
*
|
|
||||||
* @param string $sWord Token word to look for.
|
|
||||||
*
|
|
||||||
* @return bool True if there is one or more token for the token word.
|
|
||||||
*/
|
|
||||||
public function containsAny($sWord)
|
|
||||||
{
|
|
||||||
return isset($this->aTokens[$sWord]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the list of tokens for the given token word.
|
|
||||||
*
|
|
||||||
* @param string $sWord Token word to look for.
|
|
||||||
*
|
|
||||||
* @return object[] Array of tokens for the given token word or an
|
|
||||||
* empty array if no tokens could be found.
|
|
||||||
*/
|
|
||||||
public function get($sWord)
|
|
||||||
{
|
|
||||||
return isset($this->aTokens[$sWord]) ? $this->aTokens[$sWord] : array();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFullWordIDs()
|
|
||||||
{
|
|
||||||
$ids = array();
|
|
||||||
|
|
||||||
foreach ($this->aTokens as $aTokenList) {
|
|
||||||
foreach ($aTokenList as $oToken) {
|
|
||||||
if (is_a($oToken, '\Nominatim\Token\Word')) {
|
|
||||||
$ids[$oToken->getId()] = $oToken->getId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new token for the given word.
|
|
||||||
*
|
|
||||||
* @param string $sWord Word the token describes.
|
|
||||||
* @param object $oToken Token object to add.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function addToken($sWord, $oToken)
|
|
||||||
{
|
|
||||||
if (isset($this->aTokens[$sWord])) {
|
|
||||||
$this->aTokens[$sWord][] = $oToken;
|
|
||||||
} else {
|
|
||||||
$this->aTokens[$sWord] = array($oToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugTokenByWordIdList()
|
|
||||||
{
|
|
||||||
$aWordsIDs = array();
|
|
||||||
foreach ($this->aTokens as $sToken => $aWords) {
|
|
||||||
foreach ($aWords as $aToken) {
|
|
||||||
$iId = $aToken->getId();
|
|
||||||
if ($iId !== null) {
|
|
||||||
$aWordsIDs[$iId] = '#'.$sToken.'('.$aToken->debugCode().' '.$iId.')#';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aWordsIDs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return $this->aTokens;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim\Token;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A standard word token.
|
|
||||||
*/
|
|
||||||
class Partial
|
|
||||||
{
|
|
||||||
/// Database word id, if applicable.
|
|
||||||
private $iId;
|
|
||||||
/// Number of appearances in the database.
|
|
||||||
private $iSearchNameCount;
|
|
||||||
/// True, if the token consists exclusively of digits and spaces.
|
|
||||||
private $bNumberToken;
|
|
||||||
|
|
||||||
public function __construct($iId, $sToken, $iSearchNameCount)
|
|
||||||
{
|
|
||||||
$this->iId = $iId;
|
|
||||||
$this->bNumberToken = (bool) preg_match('#^[0-9 ]+$#', $sToken);
|
|
||||||
$this->iSearchNameCount = $iSearchNameCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->iId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the token can be added to the given search.
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return True if the token is compatible with the search configuration
|
|
||||||
* given the position.
|
|
||||||
*/
|
|
||||||
public function isExtendable($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
return !$oPosition->isPhrase('country');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return SearchDescription[] List of derived search descriptions.
|
|
||||||
*/
|
|
||||||
public function extendSearch($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
$aNewSearches = array();
|
|
||||||
|
|
||||||
// Partial token in Address.
|
|
||||||
if (($oPosition->isPhrase('') || !$oPosition->isFirstPhrase())
|
|
||||||
&& $oSearch->hasName()
|
|
||||||
) {
|
|
||||||
$iSearchCost = $this->bNumberToken ? 2 : 1;
|
|
||||||
if ($this->iSearchNameCount >= CONST_Max_Word_Frequency) {
|
|
||||||
$iSearchCost += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$oNewSearch = $oSearch->clone($iSearchCost);
|
|
||||||
$oNewSearch->addAddressToken(
|
|
||||||
$this->iId,
|
|
||||||
$this->iSearchNameCount < CONST_Max_Word_Frequency
|
|
||||||
);
|
|
||||||
|
|
||||||
$aNewSearches[] = $oNewSearch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Partial token in Name.
|
|
||||||
if ((!$oSearch->hasPostcode() && !$oSearch->hasAddress())
|
|
||||||
&& (!$oSearch->hasName(true)
|
|
||||||
|| $oSearch->getNamePhrase() == $oPosition->getPhrase())
|
|
||||||
) {
|
|
||||||
$iSearchCost = 1;
|
|
||||||
if (!$oSearch->hasName(true)) {
|
|
||||||
$iSearchCost += 1;
|
|
||||||
}
|
|
||||||
if ($this->bNumberToken) {
|
|
||||||
$iSearchCost += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$oNewSearch = $oSearch->clone($iSearchCost);
|
|
||||||
$oNewSearch->addPartialNameToken(
|
|
||||||
$this->iId,
|
|
||||||
$this->iSearchNameCount < CONST_Max_Word_Frequency,
|
|
||||||
$this->iSearchNameCount > CONST_Search_NameOnlySearchFrequencyThreshold,
|
|
||||||
$oPosition->getPhrase()
|
|
||||||
);
|
|
||||||
|
|
||||||
$aNewSearches[] = $oNewSearch;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aNewSearches;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'ID' => $this->iId,
|
|
||||||
'Type' => 'partial',
|
|
||||||
'Info' => array(
|
|
||||||
'count' => $this->iSearchNameCount
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugCode()
|
|
||||||
{
|
|
||||||
return 'w';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim\Token;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A postcode token.
|
|
||||||
*/
|
|
||||||
class Postcode
|
|
||||||
{
|
|
||||||
/// Database word id, if available.
|
|
||||||
private $iId;
|
|
||||||
/// Full normalized postcode (upper cased).
|
|
||||||
private $sPostcode;
|
|
||||||
// Optional country code the postcode belongs to (currently unused).
|
|
||||||
private $sCountryCode;
|
|
||||||
|
|
||||||
public function __construct($iId, $sPostcode, $sCountryCode = '')
|
|
||||||
{
|
|
||||||
$this->iId = $iId;
|
|
||||||
$iSplitPos = strpos($sPostcode, '@');
|
|
||||||
if ($iSplitPos === false) {
|
|
||||||
$this->sPostcode = $sPostcode;
|
|
||||||
} else {
|
|
||||||
$this->sPostcode = substr($sPostcode, 0, $iSplitPos);
|
|
||||||
}
|
|
||||||
$this->sCountryCode = empty($sCountryCode) ? '' : $sCountryCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->iId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the token can be added to the given search.
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return True if the token is compatible with the search configuration
|
|
||||||
* given the position.
|
|
||||||
*/
|
|
||||||
public function isExtendable($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
return !$oSearch->hasPostcode() && $oPosition->maybePhrase('postalcode');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return SearchDescription[] List of derived search descriptions.
|
|
||||||
*/
|
|
||||||
public function extendSearch($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
$aNewSearches = array();
|
|
||||||
|
|
||||||
// If we have structured search or this is the first term,
|
|
||||||
// make the postcode the primary search element.
|
|
||||||
if ($oSearch->hasOperator(\Nominatim\Operator::NONE) && $oPosition->isFirstToken()) {
|
|
||||||
$oNewSearch = $oSearch->clone(1);
|
|
||||||
$oNewSearch->setPostcodeAsName($this->iId, $this->sPostcode);
|
|
||||||
|
|
||||||
$aNewSearches[] = $oNewSearch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a structured search or this is not the first term,
|
|
||||||
// add the postcode as an addendum.
|
|
||||||
if (!$oSearch->hasOperator(\Nominatim\Operator::POSTCODE)
|
|
||||||
&& ($oPosition->isPhrase('postalcode') || $oSearch->hasName())
|
|
||||||
) {
|
|
||||||
$iPenalty = 1;
|
|
||||||
if (strlen($this->sPostcode) < 4) {
|
|
||||||
$iPenalty += 4 - strlen($this->sPostcode);
|
|
||||||
}
|
|
||||||
$oNewSearch = $oSearch->clone($iPenalty);
|
|
||||||
$oNewSearch->setPostcode($this->sPostcode);
|
|
||||||
|
|
||||||
$aNewSearches[] = $oNewSearch;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aNewSearches;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'ID' => $this->iId,
|
|
||||||
'Type' => 'postcode',
|
|
||||||
'Info' => $this->sPostcode.'('.$this->sCountryCode.')'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugCode()
|
|
||||||
{
|
|
||||||
return 'P';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim\Token;
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/SpecialSearchOperator.php');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A word token describing a place type.
|
|
||||||
*/
|
|
||||||
class SpecialTerm
|
|
||||||
{
|
|
||||||
/// Database word id, if applicable.
|
|
||||||
private $iId;
|
|
||||||
/// Class (or OSM tag key) of the place to look for.
|
|
||||||
private $sClass;
|
|
||||||
/// Type (or OSM tag value) of the place to look for.
|
|
||||||
private $sType;
|
|
||||||
/// Relationship of the operator to the object (see Operator class).
|
|
||||||
private $iOperator;
|
|
||||||
|
|
||||||
public function __construct($iID, $sClass, $sType, $iOperator)
|
|
||||||
{
|
|
||||||
$this->iId = $iID;
|
|
||||||
$this->sClass = $sClass;
|
|
||||||
$this->sType = $sType;
|
|
||||||
$this->iOperator = $iOperator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->iId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the token can be added to the given search.
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return True if the token is compatible with the search configuration
|
|
||||||
* given the position.
|
|
||||||
*/
|
|
||||||
public function isExtendable($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
return !$oSearch->hasOperator()
|
|
||||||
&& $oPosition->isPhrase('')
|
|
||||||
&& ($this->iOperator != \Nominatim\Operator::NONE
|
|
||||||
|| (!$oSearch->hasAddress() && !$oSearch->hasHousenumber() && !$oSearch->hasCountry()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return SearchDescription[] List of derived search descriptions.
|
|
||||||
*/
|
|
||||||
public function extendSearch($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
$iSearchCost = 0;
|
|
||||||
|
|
||||||
$iOp = $this->iOperator;
|
|
||||||
if ($iOp == \Nominatim\Operator::NONE) {
|
|
||||||
if ($oPosition->isFirstToken()
|
|
||||||
|| $oSearch->hasName()
|
|
||||||
|| $oSearch->getContext()->isBoundedSearch()
|
|
||||||
) {
|
|
||||||
$iOp = \Nominatim\Operator::NAME;
|
|
||||||
$iSearchCost += 3;
|
|
||||||
} else {
|
|
||||||
$iOp = \Nominatim\Operator::NEAR;
|
|
||||||
$iSearchCost += 4;
|
|
||||||
if (!$oPosition->isFirstToken()) {
|
|
||||||
$iSearchCost += 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} elseif ($oPosition->isFirstToken()) {
|
|
||||||
$iSearchCost += 2;
|
|
||||||
} elseif ($oPosition->isLastToken()) {
|
|
||||||
$iSearchCost += 4;
|
|
||||||
} else {
|
|
||||||
$iSearchCost += 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($oSearch->hasHousenumber()) {
|
|
||||||
$iSearchCost ++;
|
|
||||||
}
|
|
||||||
|
|
||||||
$oNewSearch = $oSearch->clone($iSearchCost);
|
|
||||||
$oNewSearch->setPoiSearch($iOp, $this->sClass, $this->sType);
|
|
||||||
|
|
||||||
return array($oNewSearch);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'ID' => $this->iId,
|
|
||||||
'Type' => 'special term',
|
|
||||||
'Info' => array(
|
|
||||||
'class' => $this->sClass,
|
|
||||||
'type' => $this->sType,
|
|
||||||
'operator' => \Nominatim\Operator::toString($this->iOperator)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugCode()
|
|
||||||
{
|
|
||||||
return 'S';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Nominatim\Token;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A standard word token.
|
|
||||||
*/
|
|
||||||
class Word
|
|
||||||
{
|
|
||||||
/// Database word id, if applicable.
|
|
||||||
private $iId;
|
|
||||||
/// Number of appearances in the database.
|
|
||||||
private $iSearchNameCount;
|
|
||||||
/// Number of terms in the word.
|
|
||||||
private $iTermCount;
|
|
||||||
|
|
||||||
public function __construct($iId, $iSearchNameCount, $iTermCount)
|
|
||||||
{
|
|
||||||
$this->iId = $iId;
|
|
||||||
$this->iSearchNameCount = $iSearchNameCount;
|
|
||||||
$this->iTermCount = $iTermCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->iId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the token can be added to the given search.
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return True if the token is compatible with the search configuration
|
|
||||||
* given the position.
|
|
||||||
*/
|
|
||||||
public function isExtendable($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
return !$oPosition->isPhrase('country');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derive new searches by adding this token to an existing search.
|
|
||||||
*
|
|
||||||
* @param object $oSearch Partial search description derived so far.
|
|
||||||
* @param object $oPosition Description of the token position within
|
|
||||||
the query.
|
|
||||||
*
|
|
||||||
* @return SearchDescription[] List of derived search descriptions.
|
|
||||||
*/
|
|
||||||
public function extendSearch($oSearch, $oPosition)
|
|
||||||
{
|
|
||||||
// Full words can only be a name if they appear at the beginning
|
|
||||||
// of the phrase. In structured search the name must forcibly in
|
|
||||||
// the first phrase. In unstructured search it may be in a later
|
|
||||||
// phrase when the first phrase is a house number.
|
|
||||||
if ($oSearch->hasName()
|
|
||||||
|| !($oPosition->isFirstPhrase() || $oPosition->isPhrase(''))
|
|
||||||
) {
|
|
||||||
if ($this->iTermCount > 1
|
|
||||||
&& ($oPosition->isPhrase('') || !$oPosition->isFirstPhrase())
|
|
||||||
) {
|
|
||||||
$oNewSearch = $oSearch->clone(1);
|
|
||||||
$oNewSearch->addAddressToken($this->iId);
|
|
||||||
|
|
||||||
return array($oNewSearch);
|
|
||||||
}
|
|
||||||
} elseif (!$oSearch->hasName(true)) {
|
|
||||||
$oNewSearch = $oSearch->clone(1);
|
|
||||||
$oNewSearch->addNameToken(
|
|
||||||
$this->iId,
|
|
||||||
CONST_Search_NameOnlySearchFrequencyThreshold
|
|
||||||
&& $this->iSearchNameCount
|
|
||||||
< CONST_Search_NameOnlySearchFrequencyThreshold
|
|
||||||
);
|
|
||||||
|
|
||||||
return array($oNewSearch);
|
|
||||||
}
|
|
||||||
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugInfo()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'ID' => $this->iId,
|
|
||||||
'Type' => 'word',
|
|
||||||
'Info' => array(
|
|
||||||
'count' => $this->iSearchNameCount,
|
|
||||||
'terms' => $this->iTermCount
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugCode()
|
|
||||||
{
|
|
||||||
return 'W';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
199
lib-php/cmd.php
199
lib-php/cmd.php
@@ -1,199 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/Shell.php');
|
|
||||||
|
|
||||||
function getCmdOpt($aArg, $aSpec, &$aResult, $bExitOnError = false, $bExitOnUnknown = false)
|
|
||||||
{
|
|
||||||
$aQuick = array();
|
|
||||||
$aCounts = array();
|
|
||||||
|
|
||||||
foreach ($aSpec as $aLine) {
|
|
||||||
if (is_array($aLine)) {
|
|
||||||
if ($aLine[0]) {
|
|
||||||
$aQuick['--'.$aLine[0]] = $aLine;
|
|
||||||
}
|
|
||||||
if ($aLine[1]) {
|
|
||||||
$aQuick['-'.$aLine[1]] = $aLine;
|
|
||||||
}
|
|
||||||
$aCounts[$aLine[0]] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$aResult = array();
|
|
||||||
$bUnknown = false;
|
|
||||||
$iSize = count($aArg);
|
|
||||||
for ($i = 1; $i < $iSize; $i++) {
|
|
||||||
if (isset($aQuick[$aArg[$i]])) {
|
|
||||||
$aLine = $aQuick[$aArg[$i]];
|
|
||||||
$aCounts[$aLine[0]]++;
|
|
||||||
$xVal = null;
|
|
||||||
if ($aLine[4] == $aLine[5]) {
|
|
||||||
if ($aLine[4]) {
|
|
||||||
$xVal = array();
|
|
||||||
for ($n = $aLine[4]; $i < $iSize && $n; $n--) {
|
|
||||||
$i++;
|
|
||||||
if ($i >= $iSize || $aArg[$i][0] == '-') {
|
|
||||||
showUsage($aSpec, $bExitOnError, 'Parameter of \''.$aLine[0].'\' is missing');
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ($aLine[6]) {
|
|
||||||
case 'realpath':
|
|
||||||
$xVal[] = realpath($aArg[$i]);
|
|
||||||
break;
|
|
||||||
case 'realdir':
|
|
||||||
$sPath = realpath(dirname($aArg[$i]));
|
|
||||||
if ($sPath) {
|
|
||||||
$xVal[] = $sPath . '/' . basename($aArg[$i]);
|
|
||||||
} else {
|
|
||||||
$xVal[] = $sPath;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'bool':
|
|
||||||
$xVal[] = (bool)$aArg[$i];
|
|
||||||
break;
|
|
||||||
case 'int':
|
|
||||||
$xVal[] = (int)$aArg[$i];
|
|
||||||
break;
|
|
||||||
case 'float':
|
|
||||||
$xVal[] = (float)$aArg[$i];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$xVal[] = $aArg[$i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($aLine[4] == 1) {
|
|
||||||
$xVal = $xVal[0];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$xVal = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fail('Variable numbers of params not yet supported');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($aLine[3] > 1) {
|
|
||||||
if (!array_key_exists($aLine[0], $aResult)) {
|
|
||||||
$aResult[$aLine[0]] = array();
|
|
||||||
}
|
|
||||||
$aResult[$aLine[0]][] = $xVal;
|
|
||||||
} else {
|
|
||||||
$aResult[$aLine[0]] = $xVal;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$bUnknown = $aArg[$i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (array_key_exists('help', $aResult)) {
|
|
||||||
showUsage($aSpec);
|
|
||||||
}
|
|
||||||
if ($bUnknown && $bExitOnUnknown) {
|
|
||||||
showUsage($aSpec, $bExitOnError, 'Unknown option \''.$bUnknown.'\'');
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($aSpec as $aLine) {
|
|
||||||
if (is_array($aLine)) {
|
|
||||||
if ($aCounts[$aLine[0]] < $aLine[2]) {
|
|
||||||
showUsage($aSpec, $bExitOnError, 'Option \''.$aLine[0].'\' is missing');
|
|
||||||
}
|
|
||||||
if ($aCounts[$aLine[0]] > $aLine[3]) {
|
|
||||||
showUsage($aSpec, $bExitOnError, 'Option \''.$aLine[0].'\' is present too many times');
|
|
||||||
}
|
|
||||||
if ($aLine[6] == 'bool' && !array_key_exists($aLine[0], $aResult)) {
|
|
||||||
$aResult[$aLine[0]] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $bUnknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showUsage($aSpec, $bExit = false, $sError = false)
|
|
||||||
{
|
|
||||||
if ($sError) {
|
|
||||||
echo basename($_SERVER['argv'][0]).': '.$sError."\n";
|
|
||||||
echo 'Try `'.basename($_SERVER['argv'][0]).' --help` for more information.'."\n";
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
echo 'Usage: '.basename($_SERVER['argv'][0])."\n";
|
|
||||||
$bFirst = true;
|
|
||||||
foreach ($aSpec as $aLine) {
|
|
||||||
if (is_array($aLine)) {
|
|
||||||
if ($bFirst) {
|
|
||||||
$bFirst = false;
|
|
||||||
echo "\n";
|
|
||||||
}
|
|
||||||
$aNames = array();
|
|
||||||
if ($aLine[1]) {
|
|
||||||
$aNames[] = '-'.$aLine[1];
|
|
||||||
}
|
|
||||||
if ($aLine[0]) {
|
|
||||||
$aNames[] = '--'.$aLine[0];
|
|
||||||
}
|
|
||||||
$sName = join(', ', $aNames);
|
|
||||||
echo ' '.$sName.str_repeat(' ', 30-strlen($sName)).$aLine[7]."\n";
|
|
||||||
} else {
|
|
||||||
echo $aLine."\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
echo "\n";
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
function info($sMsg)
|
|
||||||
{
|
|
||||||
echo date('Y-m-d H:i:s == ').$sMsg."\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
$aWarnings = array();
|
|
||||||
|
|
||||||
|
|
||||||
function warn($sMsg)
|
|
||||||
{
|
|
||||||
$GLOBALS['aWarnings'][] = $sMsg;
|
|
||||||
echo date('Y-m-d H:i:s == ').'WARNING: '.$sMsg."\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function repeatWarnings()
|
|
||||||
{
|
|
||||||
foreach ($GLOBALS['aWarnings'] as $sMsg) {
|
|
||||||
echo ' * ',$sMsg."\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function setupHTTPProxy()
|
|
||||||
{
|
|
||||||
if (!getSettingBool('HTTP_PROXY')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sProxy = 'tcp://'.getSetting('HTTP_PROXY_HOST').':'.getSetting('HTTP_PROXY_PROT');
|
|
||||||
$aHeaders = array();
|
|
||||||
|
|
||||||
$sLogin = getSetting('HTTP_PROXY_LOGIN');
|
|
||||||
$sPassword = getSetting('HTTP_PROXY_PASSWORD');
|
|
||||||
|
|
||||||
if ($sLogin && $sPassword) {
|
|
||||||
$sAuth = base64_encode($sLogin.':'.$sPassword);
|
|
||||||
$aHeaders = array('Proxy-Authorization: Basic '.$sAuth);
|
|
||||||
}
|
|
||||||
|
|
||||||
$aProxyHeader = array(
|
|
||||||
'proxy' => $sProxy,
|
|
||||||
'request_fulluri' => true,
|
|
||||||
'header' => $aHeaders
|
|
||||||
);
|
|
||||||
|
|
||||||
$aContext = array('http' => $aProxyHeader, 'https' => $aProxyHeader);
|
|
||||||
stream_context_set_default($aContext);
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
require('Symfony/Component/Dotenv/autoload.php');
|
|
||||||
|
|
||||||
function loadDotEnv()
|
|
||||||
{
|
|
||||||
$dotenv = new \Symfony\Component\Dotenv\Dotenv();
|
|
||||||
$dotenv->load(CONST_ConfigDir.'/env.defaults');
|
|
||||||
|
|
||||||
if (file_exists('.env')) {
|
|
||||||
$dotenv->load('.env');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
require_once('init.php');
|
|
||||||
require_once('cmd.php');
|
|
||||||
require_once('DebugNone.php');
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
require_once('init.php');
|
|
||||||
require_once('ParameterParser.php');
|
|
||||||
require_once(CONST_Debug ? 'DebugHtml.php' : 'DebugNone.php');
|
|
||||||
|
|
||||||
/***************************************************************************
|
|
||||||
*
|
|
||||||
* Error handling functions
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
function userError($sMsg)
|
|
||||||
{
|
|
||||||
throw new \Exception($sMsg, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function exception_handler_json($exception)
|
|
||||||
{
|
|
||||||
http_response_code($exception->getCode() == 0 ? 500 : $exception->getCode());
|
|
||||||
header('Content-type: application/json; charset=utf-8');
|
|
||||||
include(CONST_LibDir.'/template/error-json.php');
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
function exception_handler_xml($exception)
|
|
||||||
{
|
|
||||||
http_response_code($exception->getCode() == 0 ? 500 : $exception->getCode());
|
|
||||||
header('Content-type: text/xml; charset=utf-8');
|
|
||||||
echo '<?xml version="1.0" encoding="UTF-8" ?>'."\n";
|
|
||||||
include(CONST_LibDir.'/template/error-xml.php');
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
function shutdown_exception_handler_xml()
|
|
||||||
{
|
|
||||||
$error = error_get_last();
|
|
||||||
if ($error !== null && $error['type'] === E_ERROR) {
|
|
||||||
exception_handler_xml(new \Exception($error['message'], 500));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function shutdown_exception_handler_json()
|
|
||||||
{
|
|
||||||
$error = error_get_last();
|
|
||||||
if ($error !== null && $error['type'] === E_ERROR) {
|
|
||||||
exception_handler_json(new \Exception($error['message'], 500));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function set_exception_handler_by_format($sFormat = null)
|
|
||||||
{
|
|
||||||
// Multiple calls to register_shutdown_function will cause multiple callbacks
|
|
||||||
// to be executed, we only want the last executed. Thus we don't want to register
|
|
||||||
// one by default without an explicit $sFormat set.
|
|
||||||
|
|
||||||
if (!isset($sFormat)) {
|
|
||||||
set_exception_handler('exception_handler_json');
|
|
||||||
} elseif ($sFormat == 'xml') {
|
|
||||||
set_exception_handler('exception_handler_xml');
|
|
||||||
register_shutdown_function('shutdown_exception_handler_xml');
|
|
||||||
} else {
|
|
||||||
set_exception_handler('exception_handler_json');
|
|
||||||
register_shutdown_function('shutdown_exception_handler_json');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// set a default
|
|
||||||
set_exception_handler_by_format();
|
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
|
||||||
* HTTP Reply header setup
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (CONST_NoAccessControl) {
|
|
||||||
header('Access-Control-Allow-Origin: *');
|
|
||||||
header('Access-Control-Allow-Methods: OPTIONS,GET');
|
|
||||||
if (!empty($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
|
|
||||||
header('Access-Control-Allow-Headers: '.$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CONST_Debug) {
|
|
||||||
header('Content-type: text/html; charset=utf-8');
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
require_once(CONST_LibDir.'/lib.php');
|
|
||||||
require_once(CONST_LibDir.'/DB.php');
|
|
||||||
246
lib-php/lib.php
246
lib-php/lib.php
@@ -1,246 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function loadSettings($sProjectDir)
|
|
||||||
{
|
|
||||||
@define('CONST_InstallDir', $sProjectDir);
|
|
||||||
// Temporary hack to set the directory via environment instead of
|
|
||||||
// the installed scripts. Neither setting is part of the official
|
|
||||||
// set of settings.
|
|
||||||
defined('CONST_ConfigDir') or define('CONST_ConfigDir', $_SERVER['NOMINATIM_CONFIGDIR']);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSetting($sConfName, $sDefault = null)
|
|
||||||
{
|
|
||||||
$sValue = $_SERVER['NOMINATIM_'.$sConfName];
|
|
||||||
|
|
||||||
if ($sDefault !== null && !$sValue) {
|
|
||||||
return $sDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSettingBool($sConfName)
|
|
||||||
{
|
|
||||||
$sVal = strtolower(getSetting($sConfName));
|
|
||||||
|
|
||||||
return strcmp($sVal, 'yes') == 0
|
|
||||||
|| strcmp($sVal, 'true') == 0
|
|
||||||
|| strcmp($sVal, '1') == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fail($sError, $sUserError = false)
|
|
||||||
{
|
|
||||||
if (!$sUserError) {
|
|
||||||
$sUserError = $sError;
|
|
||||||
}
|
|
||||||
error_log('ERROR: '.$sError);
|
|
||||||
var_dump($sUserError);
|
|
||||||
echo "\n";
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getProcessorCount()
|
|
||||||
{
|
|
||||||
$sCPU = file_get_contents('/proc/cpuinfo');
|
|
||||||
preg_match_all('#processor\s+: [0-9]+#', $sCPU, $aMatches);
|
|
||||||
return count($aMatches[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getTotalMemoryMB()
|
|
||||||
{
|
|
||||||
$sCPU = file_get_contents('/proc/meminfo');
|
|
||||||
preg_match('#MemTotal: +([0-9]+) kB#', $sCPU, $aMatches);
|
|
||||||
return (int)($aMatches[1]/1024);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getCacheMemoryMB()
|
|
||||||
{
|
|
||||||
$sCPU = file_get_contents('/proc/meminfo');
|
|
||||||
preg_match('#Cached: +([0-9]+) kB#', $sCPU, $aMatches);
|
|
||||||
return (int)($aMatches[1]/1024);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDatabaseDate(&$oDB)
|
|
||||||
{
|
|
||||||
// Find the newest node in the DB
|
|
||||||
$iLastOSMID = $oDB->getOne("select max(osm_id) from place where osm_type = 'N'");
|
|
||||||
// Lookup the timestamp that node was created
|
|
||||||
$sLastNodeURL = 'https://www.openstreetmap.org/api/0.6/node/'.$iLastOSMID.'/1';
|
|
||||||
$sLastNodeXML = file_get_contents($sLastNodeURL);
|
|
||||||
|
|
||||||
if ($sLastNodeXML === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
preg_match('#timestamp="(([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z)"#', $sLastNodeXML, $aLastNodeDate);
|
|
||||||
|
|
||||||
return $aLastNodeDate[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function byImportance($a, $b)
|
|
||||||
{
|
|
||||||
if ($a['importance'] != $b['importance']) {
|
|
||||||
return ($a['importance'] > $b['importance']?-1:1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $a['foundorder'] <=> $b['foundorder'];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function javascript_renderData($xVal, $iOptions = 0)
|
|
||||||
{
|
|
||||||
$sCallback = isset($_GET['json_callback']) ? $_GET['json_callback'] : '';
|
|
||||||
if ($sCallback && !preg_match('/^[$_\p{L}][$_\p{L}\p{Nd}.[\]]*$/u', $sCallback)) {
|
|
||||||
// Unset, we call javascript_renderData again during exception handling
|
|
||||||
unset($_GET['json_callback']);
|
|
||||||
throw new Exception('Invalid json_callback value', 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
$iOptions |= JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
|
|
||||||
if (isset($_GET['pretty']) && in_array(strtolower($_GET['pretty']), array('1', 'true'))) {
|
|
||||||
$iOptions |= JSON_PRETTY_PRINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
$jsonout = json_encode($xVal, $iOptions);
|
|
||||||
|
|
||||||
if ($sCallback) {
|
|
||||||
header('Content-Type: application/javascript; charset=UTF-8');
|
|
||||||
echo $_GET['json_callback'].'('.$jsonout.')';
|
|
||||||
} else {
|
|
||||||
header('Content-Type: application/json; charset=UTF-8');
|
|
||||||
echo $jsonout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addQuotes($s)
|
|
||||||
{
|
|
||||||
return "'".$s."'";
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseLatLon($sQuery)
|
|
||||||
{
|
|
||||||
$sFound = null;
|
|
||||||
$fQueryLat = null;
|
|
||||||
$fQueryLon = null;
|
|
||||||
|
|
||||||
if (preg_match('/\\s*([NS])[\s]+([0-9]+[0-9.]*)[°\s]+([0-9.]+)?[′\']*[,\s]+([EW])[\s]+([0-9]+)[°\s]+([0-9]+[0-9.]*)[′\']*\\s*/', $sQuery, $aData)) {
|
|
||||||
/* 1 2 3 4 5 6
|
|
||||||
* degrees decimal minutes
|
|
||||||
* N 40 26.767, W 79 58.933
|
|
||||||
* N 40°26.767′, W 79°58.933′
|
|
||||||
*/
|
|
||||||
$sFound = $aData[0];
|
|
||||||
$fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60);
|
|
||||||
$fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60);
|
|
||||||
} elseif (preg_match('/\\s*([0-9]+)[°\s]+([0-9]+[0-9.]*)?[′\']*[\s]+([NS])[,\s]+([0-9]+)[°\s]+([0-9]+[0-9.]*)?[′\'\s]+([EW])\\s*/', $sQuery, $aData)) {
|
|
||||||
/* 1 2 3 4 5 6
|
|
||||||
* degrees decimal minutes
|
|
||||||
* 40 26.767 N, 79 58.933 W
|
|
||||||
* 40° 26.767′ N 79° 58.933′ W
|
|
||||||
*/
|
|
||||||
$sFound = $aData[0];
|
|
||||||
$fQueryLat = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60);
|
|
||||||
$fQueryLon = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60);
|
|
||||||
} elseif (preg_match('/\\s*([NS])[\s]+([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+)[″"]*[,\s]+([EW])[\s]+([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+)[″"]*\\s*/', $sQuery, $aData)) {
|
|
||||||
/* 1 2 3 4 5 6 7 8
|
|
||||||
* degrees decimal seconds
|
|
||||||
* N 40 26 46 W 79 58 56
|
|
||||||
* N 40° 26′ 46″, W 79° 58′ 56″
|
|
||||||
*/
|
|
||||||
$sFound = $aData[0];
|
|
||||||
$fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60 + $aData[4]/3600);
|
|
||||||
$fQueryLon = ($aData[5]=='E'?1:-1) * ($aData[6] + $aData[7]/60 + $aData[8]/3600);
|
|
||||||
} elseif (preg_match('/\\s*([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+[0-9.]*)[″"\s]+([NS])[,\s]+([0-9]+)[°\s]+([0-9]+)[′\'\s]+([0-9]+[0-9.]*)[″"\s]+([EW])\\s*/', $sQuery, $aData)) {
|
|
||||||
/* 1 2 3 4 5 6 7 8
|
|
||||||
* degrees decimal seconds
|
|
||||||
* 40 26 46 N 79 58 56 W
|
|
||||||
* 40° 26′ 46″ N, 79° 58′ 56″ W
|
|
||||||
* 40° 26′ 46.78″ N, 79° 58′ 56.89″ W
|
|
||||||
*/
|
|
||||||
$sFound = $aData[0];
|
|
||||||
$fQueryLat = ($aData[4]=='N'?1:-1) * ($aData[1] + $aData[2]/60 + $aData[3]/3600);
|
|
||||||
$fQueryLon = ($aData[8]=='E'?1:-1) * ($aData[5] + $aData[6]/60 + $aData[7]/3600);
|
|
||||||
} elseif (preg_match('/\\s*([NS])[\s]+([0-9]+[0-9]*\\.[0-9]+)[°]*[,\s]+([EW])[\s]+([0-9]+[0-9]*\\.[0-9]+)[°]*\\s*/', $sQuery, $aData)) {
|
|
||||||
/* 1 2 3 4
|
|
||||||
* degrees decimal
|
|
||||||
* N 40.446° W 79.982°
|
|
||||||
*/
|
|
||||||
$sFound = $aData[0];
|
|
||||||
$fQueryLat = ($aData[1]=='N'?1:-1) * ($aData[2]);
|
|
||||||
$fQueryLon = ($aData[3]=='E'?1:-1) * ($aData[4]);
|
|
||||||
} elseif (preg_match('/\\s*([0-9]+[0-9]*\\.[0-9]+)[°\s]+([NS])[,\s]+([0-9]+[0-9]*\\.[0-9]+)[°\s]+([EW])\\s*/', $sQuery, $aData)) {
|
|
||||||
/* 1 2 3 4
|
|
||||||
* degrees decimal
|
|
||||||
* 40.446° N 79.982° W
|
|
||||||
*/
|
|
||||||
$sFound = $aData[0];
|
|
||||||
$fQueryLat = ($aData[2]=='N'?1:-1) * ($aData[1]);
|
|
||||||
$fQueryLon = ($aData[4]=='E'?1:-1) * ($aData[3]);
|
|
||||||
} elseif (preg_match('/(\\s*\\[|^\\s*|\\s*)(-?[0-9]+[0-9]*\\.[0-9]+)[,\s]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]\\s*|\\s*$|\\s*)/', $sQuery, $aData)) {
|
|
||||||
/* 1 2 3 4
|
|
||||||
* degrees decimal
|
|
||||||
* 12.34, 56.78
|
|
||||||
* 12.34 56.78
|
|
||||||
* [12.456,-78.90]
|
|
||||||
*/
|
|
||||||
$sFound = $aData[0];
|
|
||||||
$fQueryLat = $aData[2];
|
|
||||||
$fQueryLon = $aData[3];
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return array($sFound, $fQueryLat, $fQueryLon);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addressRankToGeocodeJsonType($iAddressRank)
|
|
||||||
{
|
|
||||||
if ($iAddressRank >= 29 && $iAddressRank <= 30) {
|
|
||||||
return 'house';
|
|
||||||
}
|
|
||||||
if ($iAddressRank >= 26 && $iAddressRank < 28) {
|
|
||||||
return 'street';
|
|
||||||
}
|
|
||||||
if ($iAddressRank >= 22 && $iAddressRank < 26) {
|
|
||||||
return 'locality';
|
|
||||||
}
|
|
||||||
if ($iAddressRank >= 17 && $iAddressRank < 22) {
|
|
||||||
return 'district';
|
|
||||||
}
|
|
||||||
if ($iAddressRank >= 13 && $iAddressRank < 17) {
|
|
||||||
return 'city';
|
|
||||||
}
|
|
||||||
if ($iAddressRank >= 10 && $iAddressRank < 13) {
|
|
||||||
return 'county';
|
|
||||||
}
|
|
||||||
if ($iAddressRank >= 5 && $iAddressRank < 10) {
|
|
||||||
return 'state';
|
|
||||||
}
|
|
||||||
if ($iAddressRank >= 4 && $iAddressRank < 5) {
|
|
||||||
return 'country';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'locality';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!function_exists('array_key_last')) {
|
|
||||||
function array_key_last(array $array)
|
|
||||||
{
|
|
||||||
if (!empty($array)) {
|
|
||||||
return key(array_slice($array, -1, 1, true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
104
lib-php/log.php
104
lib-php/log.php
@@ -1,104 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
function logStart(&$oDB, $sType = '', $sQuery = '', $aLanguageList = array())
|
|
||||||
{
|
|
||||||
$fStartTime = microtime(true);
|
|
||||||
$aStartTime = explode('.', $fStartTime);
|
|
||||||
if (!isset($aStartTime[1])) {
|
|
||||||
$aStartTime[1] = '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
$sOutputFormat = '';
|
|
||||||
if (isset($_GET['format'])) {
|
|
||||||
$sOutputFormat = $_GET['format'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($sType == 'reverse') {
|
|
||||||
$sOutQuery = (isset($_GET['lat'])?$_GET['lat']:'').'/';
|
|
||||||
if (isset($_GET['lon'])) {
|
|
||||||
$sOutQuery .= $_GET['lon'];
|
|
||||||
}
|
|
||||||
if (isset($_GET['zoom'])) {
|
|
||||||
$sOutQuery .= '/'.$_GET['zoom'];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$sOutQuery = $sQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
$hLog = array(
|
|
||||||
date('Y-m-d H:i:s', $aStartTime[0]).'.'.$aStartTime[1],
|
|
||||||
$_SERVER['REMOTE_ADDR'],
|
|
||||||
$_SERVER['QUERY_STRING'],
|
|
||||||
$sOutQuery,
|
|
||||||
$sType,
|
|
||||||
$fStartTime
|
|
||||||
);
|
|
||||||
|
|
||||||
if (CONST_Log_DB) {
|
|
||||||
if (isset($_GET['email'])) {
|
|
||||||
$sUserAgent = $_GET['email'];
|
|
||||||
} elseif (isset($_SERVER['HTTP_REFERER'])) {
|
|
||||||
$sUserAgent = $_SERVER['HTTP_REFERER'];
|
|
||||||
} elseif (isset($_SERVER['HTTP_USER_AGENT'])) {
|
|
||||||
$sUserAgent = $_SERVER['HTTP_USER_AGENT'];
|
|
||||||
} else {
|
|
||||||
$sUserAgent = '';
|
|
||||||
}
|
|
||||||
$sSQL = 'insert into new_query_log (type,starttime,query,ipaddress,useragent,language,format,searchterm)';
|
|
||||||
$sSQL .= ' values (';
|
|
||||||
$sSQL .= join(',', $oDB->getDBQuotedList(array(
|
|
||||||
$sType,
|
|
||||||
$hLog[0],
|
|
||||||
$hLog[2],
|
|
||||||
$hLog[1],
|
|
||||||
$sUserAgent,
|
|
||||||
join(',', $aLanguageList),
|
|
||||||
$sOutputFormat,
|
|
||||||
$hLog[3]
|
|
||||||
)));
|
|
||||||
$sSQL .= ')';
|
|
||||||
$oDB->exec($sSQL);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $hLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
function logEnd(&$oDB, $hLog, $iNumResults)
|
|
||||||
{
|
|
||||||
$fEndTime = microtime(true);
|
|
||||||
|
|
||||||
if (CONST_Log_DB) {
|
|
||||||
$aEndTime = explode('.', $fEndTime);
|
|
||||||
if (!isset($aEndTime[1])) {
|
|
||||||
$aEndTime[1] = '0';
|
|
||||||
}
|
|
||||||
$sEndTime = date('Y-m-d H:i:s', $aEndTime[0]).'.'.$aEndTime[1];
|
|
||||||
|
|
||||||
$sSQL = 'update new_query_log set endtime = '.$oDB->getDBQuoted($sEndTime).', results = '.$iNumResults;
|
|
||||||
$sSQL .= ' where starttime = '.$oDB->getDBQuoted($hLog[0]);
|
|
||||||
$sSQL .= ' and ipaddress = '.$oDB->getDBQuoted($hLog[1]);
|
|
||||||
$sSQL .= ' and query = '.$oDB->getDBQuoted($hLog[2]);
|
|
||||||
$oDB->exec($sSQL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CONST_Log_File) {
|
|
||||||
$aOutdata = sprintf(
|
|
||||||
"[%s] %.4f %d %s \"%s\"\n",
|
|
||||||
$hLog[0],
|
|
||||||
$fEndTime-$hLog[5],
|
|
||||||
$iNumResults,
|
|
||||||
$hLog[4],
|
|
||||||
$hLog[2]
|
|
||||||
);
|
|
||||||
file_put_contents(CONST_Log_File, $aOutdata, FILE_APPEND | LOCK_EX);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
function formatOSMType($sType, $bIncludeExternal = true)
|
|
||||||
{
|
|
||||||
if ($sType == 'N') {
|
|
||||||
return 'node';
|
|
||||||
}
|
|
||||||
if ($sType == 'W') {
|
|
||||||
return 'way';
|
|
||||||
}
|
|
||||||
if ($sType == 'R') {
|
|
||||||
return 'relation';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$bIncludeExternal) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($sType == 'T') {
|
|
||||||
return 'way';
|
|
||||||
}
|
|
||||||
if ($sType == 'I') {
|
|
||||||
return 'way';
|
|
||||||
}
|
|
||||||
|
|
||||||
// not handled: P, L
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function getOsm2pgsqlBinary()
|
|
||||||
{
|
|
||||||
$sBinary = getSetting('OSM2PGSQL_BINARY');
|
|
||||||
|
|
||||||
return $sBinary ? $sBinary : CONST_Default_Osm2pgsql;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getImportStyle()
|
|
||||||
{
|
|
||||||
$sStyle = getSetting('IMPORT_STYLE');
|
|
||||||
|
|
||||||
if (in_array($sStyle, array('admin', 'street', 'address', 'full', 'extratags'))) {
|
|
||||||
return CONST_ConfigDir.'/import-'.$sStyle.'.style';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sStyle;
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// https://github.com/geocoders/geocodejson-spec/
|
|
||||||
|
|
||||||
$aFilteredPlaces = array();
|
|
||||||
|
|
||||||
if (empty($aPlace)) {
|
|
||||||
if (isset($sError)) {
|
|
||||||
$aFilteredPlaces['error'] = $sError;
|
|
||||||
} else {
|
|
||||||
$aFilteredPlaces['error'] = 'Unable to geocode';
|
|
||||||
}
|
|
||||||
javascript_renderData($aFilteredPlaces);
|
|
||||||
} else {
|
|
||||||
$aFilteredPlaces = array(
|
|
||||||
'type' => 'Feature',
|
|
||||||
'properties' => array(
|
|
||||||
'geocoding' => array()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isset($aPlace['place_id'])) {
|
|
||||||
$aFilteredPlaces['properties']['geocoding']['place_id'] = $aPlace['place_id'];
|
|
||||||
}
|
|
||||||
$sOSMType = formatOSMType($aPlace['osm_type']);
|
|
||||||
if ($sOSMType) {
|
|
||||||
$aFilteredPlaces['properties']['geocoding']['osm_type'] = $sOSMType;
|
|
||||||
$aFilteredPlaces['properties']['geocoding']['osm_id'] = $aPlace['osm_id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$aFilteredPlaces['properties']['geocoding']['osm_key'] = $aPlace['class'];
|
|
||||||
$aFilteredPlaces['properties']['geocoding']['osm_value'] = $aPlace['type'];
|
|
||||||
|
|
||||||
$aFilteredPlaces['properties']['geocoding']['type'] = addressRankToGeocodeJsonType($aPlace['rank_address']);
|
|
||||||
|
|
||||||
$aFilteredPlaces['properties']['geocoding']['accuracy'] = (int) $fDistance;
|
|
||||||
|
|
||||||
$aFilteredPlaces['properties']['geocoding']['label'] = $aPlace['langaddress'];
|
|
||||||
|
|
||||||
if ($aPlace['placename'] !== null) {
|
|
||||||
$aFilteredPlaces['properties']['geocoding']['name'] = $aPlace['placename'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($aPlace['address'])) {
|
|
||||||
$aPlace['address']->addGeocodeJsonAddressParts(
|
|
||||||
$aFilteredPlaces['properties']['geocoding']
|
|
||||||
);
|
|
||||||
|
|
||||||
$aFilteredPlaces['properties']['geocoding']['admin']
|
|
||||||
= $aPlace['address']->getAdminLevels();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($aPlace['asgeojson'])) {
|
|
||||||
$aFilteredPlaces['geometry'] = json_decode($aPlace['asgeojson'], true);
|
|
||||||
} else {
|
|
||||||
$aFilteredPlaces['geometry'] = array(
|
|
||||||
'type' => 'Point',
|
|
||||||
'coordinates' => array(
|
|
||||||
(float) $aPlace['lon'],
|
|
||||||
(float) $aPlace['lat']
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
javascript_renderData(array(
|
|
||||||
'type' => 'FeatureCollection',
|
|
||||||
'geocoding' => array(
|
|
||||||
'version' => '0.1.0',
|
|
||||||
'attribution' => 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright',
|
|
||||||
'licence' => 'ODbL',
|
|
||||||
'query' => $sQuery
|
|
||||||
),
|
|
||||||
'features' => array($aFilteredPlaces)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$aFilteredPlaces = array();
|
|
||||||
|
|
||||||
if (empty($aPlace)) {
|
|
||||||
if (isset($sError)) {
|
|
||||||
$aFilteredPlaces['error'] = $sError;
|
|
||||||
} else {
|
|
||||||
$aFilteredPlaces['error'] = 'Unable to geocode';
|
|
||||||
}
|
|
||||||
javascript_renderData($aFilteredPlaces);
|
|
||||||
} else {
|
|
||||||
$aFilteredPlaces = array(
|
|
||||||
'type' => 'Feature',
|
|
||||||
'properties' => array()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isset($aPlace['place_id'])) {
|
|
||||||
$aFilteredPlaces['properties']['place_id'] = $aPlace['place_id'];
|
|
||||||
}
|
|
||||||
$sOSMType = formatOSMType($aPlace['osm_type']);
|
|
||||||
if ($sOSMType) {
|
|
||||||
$aFilteredPlaces['properties']['osm_type'] = $sOSMType;
|
|
||||||
$aFilteredPlaces['properties']['osm_id'] = $aPlace['osm_id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$aFilteredPlaces['properties']['place_rank'] = $aPlace['rank_search'];
|
|
||||||
|
|
||||||
$aFilteredPlaces['properties']['category'] = $aPlace['class'];
|
|
||||||
$aFilteredPlaces['properties']['type'] = $aPlace['type'];
|
|
||||||
|
|
||||||
$aFilteredPlaces['properties']['importance'] = $aPlace['importance'];
|
|
||||||
|
|
||||||
$aFilteredPlaces['properties']['addresstype'] = strtolower($aPlace['addresstype']);
|
|
||||||
|
|
||||||
$aFilteredPlaces['properties']['name'] = $aPlace['placename'];
|
|
||||||
|
|
||||||
$aFilteredPlaces['properties']['display_name'] = $aPlace['langaddress'];
|
|
||||||
|
|
||||||
if (isset($aPlace['address'])) {
|
|
||||||
$aFilteredPlaces['properties']['address'] = $aPlace['address']->getAddressNames();
|
|
||||||
}
|
|
||||||
if (isset($aPlace['sExtraTags'])) {
|
|
||||||
$aFilteredPlaces['properties']['extratags'] = $aPlace['sExtraTags'];
|
|
||||||
}
|
|
||||||
if (isset($aPlace['sNameDetails'])) {
|
|
||||||
$aFilteredPlaces['properties']['namedetails'] = $aPlace['sNameDetails'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($aPlace['aBoundingBox'])) {
|
|
||||||
$aFilteredPlaces['bbox'] = array(
|
|
||||||
(float) $aPlace['aBoundingBox'][2], // minlon
|
|
||||||
(float) $aPlace['aBoundingBox'][0], // minlat
|
|
||||||
(float) $aPlace['aBoundingBox'][3], // maxlon
|
|
||||||
(float) $aPlace['aBoundingBox'][1] // maxlat
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($aPlace['asgeojson'])) {
|
|
||||||
$aFilteredPlaces['geometry'] = json_decode($aPlace['asgeojson'], true);
|
|
||||||
} else {
|
|
||||||
$aFilteredPlaces['geometry'] = array(
|
|
||||||
'type' => 'Point',
|
|
||||||
'coordinates' => array(
|
|
||||||
(float) $aPlace['lon'],
|
|
||||||
(float) $aPlace['lat']
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
javascript_renderData(array(
|
|
||||||
'type' => 'FeatureCollection',
|
|
||||||
'licence' => 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright',
|
|
||||||
'features' => array($aFilteredPlaces)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
*
|
|
||||||
* This file is part of Nominatim. (https://nominatim.org)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2022 by the Nominatim developer community.
|
|
||||||
* For a full list of authors see the git log.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$aFilteredPlaces = array();
|
|
||||||
|
|
||||||
if (empty($aPlace)) {
|
|
||||||
if (isset($sError)) {
|
|
||||||
$aFilteredPlaces['error'] = $sError;
|
|
||||||
} else {
|
|
||||||
$aFilteredPlaces['error'] = 'Unable to geocode';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isset($aPlace['place_id'])) {
|
|
||||||
$aFilteredPlaces['place_id'] = $aPlace['place_id'];
|
|
||||||
}
|
|
||||||
$aFilteredPlaces['licence'] = 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright';
|
|
||||||
$sOSMType = formatOSMType($aPlace['osm_type']);
|
|
||||||
if ($sOSMType) {
|
|
||||||
$aFilteredPlaces['osm_type'] = $sOSMType;
|
|
||||||
$aFilteredPlaces['osm_id'] = $aPlace['osm_id'];
|
|
||||||
}
|
|
||||||
if (isset($aPlace['lat'])) {
|
|
||||||
$aFilteredPlaces['lat'] = $aPlace['lat'];
|
|
||||||
}
|
|
||||||
if (isset($aPlace['lon'])) {
|
|
||||||
$aFilteredPlaces['lon'] = $aPlace['lon'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($sOutputFormat == 'jsonv2' || $sOutputFormat == 'geojson') {
|
|
||||||
$aFilteredPlaces['place_rank'] = $aPlace['rank_search'];
|
|
||||||
|
|
||||||
$aFilteredPlaces['category'] = $aPlace['class'];
|
|
||||||
$aFilteredPlaces['type'] = $aPlace['type'];
|
|
||||||
|
|
||||||
$aFilteredPlaces['importance'] = $aPlace['importance'];
|
|
||||||
|
|
||||||
$aFilteredPlaces['addresstype'] = strtolower($aPlace['addresstype']);
|
|
||||||
|
|
||||||
$aFilteredPlaces['name'] = $aPlace['placename'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$aFilteredPlaces['display_name'] = $aPlace['langaddress'];
|
|
||||||
|
|
||||||
if (isset($aPlace['address'])) {
|
|
||||||
$aFilteredPlaces['address'] = $aPlace['address']->getAddressNames();
|
|
||||||
}
|
|
||||||
if (isset($aPlace['sExtraTags'])) {
|
|
||||||
$aFilteredPlaces['extratags'] = $aPlace['sExtraTags'];
|
|
||||||
}
|
|
||||||
if (isset($aPlace['sNameDetails'])) {
|
|
||||||
$aFilteredPlaces['namedetails'] = $aPlace['sNameDetails'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($aPlace['aBoundingBox'])) {
|
|
||||||
$aFilteredPlaces['boundingbox'] = $aPlace['aBoundingBox'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($aPlace['asgeojson'])) {
|
|
||||||
$aFilteredPlaces['geojson'] = json_decode($aPlace['asgeojson'], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($aPlace['assvg'])) {
|
|
||||||
$aFilteredPlaces['svg'] = $aPlace['assvg'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($aPlace['astext'])) {
|
|
||||||
$aFilteredPlaces['geotext'] = $aPlace['astext'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($aPlace['askml'])) {
|
|
||||||
$aFilteredPlaces['geokml'] = $aPlace['askml'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
javascript_renderData($aFilteredPlaces);
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user