mirror of
https://github.com/osm-search/Nominatim.git
synced 2026-02-14 10:27:57 +00:00
Compare commits
1398 Commits
v3.0.1
...
docs-3.6.x
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b20a1531c1 | ||
|
|
9610664fc5 | ||
|
|
eed2e3f2a8 | ||
|
|
1b62162e08 | ||
|
|
5cf573c340 | ||
|
|
76cd8bf258 | ||
|
|
31ba7e7cf0 | ||
|
|
a338ba695b | ||
|
|
9f6e2de4ed | ||
|
|
1b3acc4f8f | ||
|
|
56cf3b362e | ||
|
|
b59d01fe85 | ||
|
|
65d8770b28 | ||
|
|
38582c3e52 | ||
|
|
5f6fe6cab7 | ||
|
|
34f8e6ddf7 | ||
|
|
603367dced | ||
|
|
eaee87e73d | ||
|
|
ea844db847 | ||
|
|
3ca8cc0344 | ||
|
|
f67c06f128 | ||
|
|
7889509856 | ||
|
|
67c995aef6 | ||
|
|
e20defeebd | ||
|
|
cca646a19e | ||
|
|
ddc2b4b806 | ||
|
|
0334915067 | ||
|
|
987d60ccda | ||
|
|
7d520bf448 | ||
|
|
dc53288e6b | ||
|
|
db02673b60 | ||
|
|
cf9b248f29 | ||
|
|
df12954312 | ||
|
|
a9357b4dce | ||
|
|
63544db8f9 | ||
|
|
7295cad715 | ||
|
|
ff85da0a31 | ||
|
|
75b2d7ca99 | ||
|
|
3c9eeb11fa | ||
|
|
63bacaee2e | ||
|
|
5016eace34 | ||
|
|
2e5c8b5cd3 | ||
|
|
bf0f81adcb | ||
|
|
2db751700e | ||
|
|
62bee4ed37 | ||
|
|
1f07d63dc5 | ||
|
|
cc1af99dbd | ||
|
|
c5d98effc0 | ||
|
|
b68b2ff6b8 | ||
|
|
57f0d55c2e | ||
|
|
3cf763475f | ||
|
|
0f87da017f | ||
|
|
22800d7d59 | ||
|
|
f21853ea9d | ||
|
|
1e76d668bd | ||
|
|
b4b50eef15 | ||
|
|
a9ad390b9e | ||
|
|
2e9e961fff | ||
|
|
13180989d9 | ||
|
|
a4f1e40b72 | ||
|
|
04d485c550 | ||
|
|
a92bd1e2db | ||
|
|
f89e71a861 | ||
|
|
dcc075b34b | ||
|
|
49083c2597 | ||
|
|
29785ba166 | ||
|
|
ffb2c93ba3 | ||
|
|
30a6b6bdac | ||
|
|
cc345f531a | ||
|
|
9ede048769 | ||
|
|
d23bf6e659 | ||
|
|
6b60f0ab03 | ||
|
|
aa9923bf07 | ||
|
|
9160cce6d8 | ||
|
|
885dc0a8e1 | ||
|
|
7324431b12 | ||
|
|
021f2bef4c | ||
|
|
6260fef2e8 | ||
|
|
c7472662a6 | ||
|
|
fecfe62fc6 | ||
|
|
21b0430e46 | ||
|
|
66595c2d2b | ||
|
|
4ac29fb525 | ||
|
|
04d50a271d | ||
|
|
cbbda1ddf0 | ||
|
|
928c6245c9 | ||
|
|
ac9be161f6 | ||
|
|
33378dcf6e | ||
|
|
e31a1f7ef1 | ||
|
|
fa574ae9fd | ||
|
|
b2ebf4b4b7 | ||
|
|
0f5615b618 | ||
|
|
f050f898bc | ||
|
|
ce1c3bab6d | ||
|
|
5cdabc5173 | ||
|
|
42775f959b | ||
|
|
7f55dcef3a | ||
|
|
ba8accf4bb | ||
|
|
e62db51b06 | ||
|
|
b81894d3d5 | ||
|
|
d86cf6801f | ||
|
|
c0d21d0bd3 | ||
|
|
b838f66dd7 | ||
|
|
db9cc270b3 | ||
|
|
4147e04319 | ||
|
|
a888f6ff93 | ||
|
|
bd04c49bc1 | ||
|
|
5872b81232 | ||
|
|
abd20d3ca6 | ||
|
|
95f83b90d2 | ||
|
|
7a16909219 | ||
|
|
ee4684e6a9 | ||
|
|
a2b1a1eb33 | ||
|
|
7bc0fc9611 | ||
|
|
e0e18e2b6f | ||
|
|
788ba6d985 | ||
|
|
b012e15245 | ||
|
|
ba31456278 | ||
|
|
b661c66c00 | ||
|
|
f0372736aa | ||
|
|
e286d3f23d | ||
|
|
6c8a7b0a1a | ||
|
|
3107dc208a | ||
|
|
bf4d75458c | ||
|
|
3604d0d913 | ||
|
|
dd06638dec | ||
|
|
73a0ec22a3 | ||
|
|
b0ef84caae | ||
|
|
64899ef54b | ||
|
|
1064a9264e | ||
|
|
acfa7bec9c | ||
|
|
62b94e838b | ||
|
|
5236e7a03e | ||
|
|
7e9412a044 | ||
|
|
e47c19beb9 | ||
|
|
2fe3c654fc | ||
|
|
5ec48c66cb | ||
|
|
4e7ec92d6f | ||
|
|
e115de47fc | ||
|
|
bb5ffd3904 | ||
|
|
887ae7fcab | ||
|
|
abaaf942cb | ||
|
|
ff47f6f65d | ||
|
|
ca680fc9fc | ||
|
|
b04463bb2d | ||
|
|
c66f701232 | ||
|
|
3b23144ae6 | ||
|
|
f7a9462337 | ||
|
|
17f24d0061 | ||
|
|
eb8bce22b8 | ||
|
|
6a31691121 | ||
|
|
25c4bf6ed4 | ||
|
|
ec5743bcc0 | ||
|
|
7cd330ee55 | ||
|
|
7d2b6879c8 | ||
|
|
a40684162a | ||
|
|
cd997ff058 | ||
|
|
5391bb6cf7 | ||
|
|
6b8ce1ee74 | ||
|
|
a37d46ec25 | ||
|
|
b5c8237118 | ||
|
|
eeffe2caf4 | ||
|
|
c5c7a6a453 | ||
|
|
b969392f40 | ||
|
|
40bc1752c2 | ||
|
|
851c3779b5 | ||
|
|
2a717da850 | ||
|
|
f8694da3c9 | ||
|
|
eeb2b1f998 | ||
|
|
1db8f7e353 | ||
|
|
6625e93be6 | ||
|
|
56053988bf | ||
|
|
d9325dc11a | ||
|
|
d3ca9dd3f7 | ||
|
|
e552f6bce5 | ||
|
|
cf23e10382 | ||
|
|
c84e7e72f1 | ||
|
|
f2ff351da4 | ||
|
|
c5c242d193 | ||
|
|
72193a1c23 | ||
|
|
248d6b413a | ||
|
|
d04e87fb80 | ||
|
|
cc61b74cde | ||
|
|
915d362b11 | ||
|
|
f8a5f2964f | ||
|
|
a8dfbcef44 | ||
|
|
caea14d035 | ||
|
|
c5fc12e04b | ||
|
|
731c620e31 | ||
|
|
0dad470eb9 | ||
|
|
6947ab3a65 | ||
|
|
b219374d36 | ||
|
|
576ee5aaab | ||
|
|
4c9cfe2532 | ||
|
|
7fb62ea904 | ||
|
|
fe250d3ee8 | ||
|
|
6f55c67d16 | ||
|
|
3aa6e6a365 | ||
|
|
b8ced5d96b | ||
|
|
9e21c6a862 | ||
|
|
fe8566928e | ||
|
|
3600709116 | ||
|
|
df115c73b2 | ||
|
|
dbe025fe40 | ||
|
|
2b11a47a2f | ||
|
|
fb63a7418e | ||
|
|
2029931b95 | ||
|
|
91219bb3dd | ||
|
|
ab4fe4d58a | ||
|
|
8ff1f16b7f | ||
|
|
cf4f62c82c | ||
|
|
1d2f4264a2 | ||
|
|
9d506a4afa | ||
|
|
7ac22e9227 | ||
|
|
47f2fe724f | ||
|
|
130571fb29 | ||
|
|
da7218350b | ||
|
|
b6078de6f8 | ||
|
|
07430b0194 | ||
|
|
fae02fab00 | ||
|
|
a68cdc40be | ||
|
|
76b307f42a | ||
|
|
6e4b7eb966 | ||
|
|
770754ae2c | ||
|
|
a932855f6f | ||
|
|
72ee1abc90 | ||
|
|
9e1909643c | ||
|
|
77a1329285 | ||
|
|
be6ecc388c | ||
|
|
13dba94307 | ||
|
|
d51440bb5d | ||
|
|
d730e179bf | ||
|
|
559fe513fa | ||
|
|
5b20fa7e38 | ||
|
|
d16e75de91 | ||
|
|
6fd9994590 | ||
|
|
b9729f3b66 | ||
|
|
d6ff7475f1 | ||
|
|
984979d9bf | ||
|
|
73c449b97b | ||
|
|
1529666232 | ||
|
|
3816b86a9e | ||
|
|
a4b30fc649 | ||
|
|
fc50eb8688 | ||
|
|
071db1fae7 | ||
|
|
a163ea63c5 | ||
|
|
e21a707166 | ||
|
|
1b484fa90d | ||
|
|
06aa0f0b76 | ||
|
|
fb8bb30144 | ||
|
|
7429a33818 | ||
|
|
5b9f61cff8 | ||
|
|
4ff0e20e1f | ||
|
|
a692bfa8f9 | ||
|
|
49dd927406 | ||
|
|
4b21cc1737 | ||
|
|
7ae16f7302 | ||
|
|
73566a9f15 | ||
|
|
fbdf205ab4 | ||
|
|
83b2b4970d | ||
|
|
f29dc7d7ac | ||
|
|
4d5db74c18 | ||
|
|
8c7d285e03 | ||
|
|
1347abb1e7 | ||
|
|
2cb85e48b4 | ||
|
|
5be084e0f5 | ||
|
|
2323923bec | ||
|
|
0f54d42863 | ||
|
|
8201c7f46c | ||
|
|
ed22d640f4 | ||
|
|
01b009ff24 | ||
|
|
665b90bf5a | ||
|
|
4e1f245331 | ||
|
|
f8e1d39208 | ||
|
|
955dae5d4b | ||
|
|
b78cd3f4c9 | ||
|
|
6a3eb7edf2 | ||
|
|
0a710c0762 | ||
|
|
9a204f6284 | ||
|
|
7837970303 | ||
|
|
8cd9550295 | ||
|
|
840c692d5b | ||
|
|
05e0d3e2d4 | ||
|
|
7429ff9dce | ||
|
|
29602fe0cf | ||
|
|
1b95ec5591 | ||
|
|
3efe0dc8dc | ||
|
|
241e4af1b0 | ||
|
|
cac8a8df18 | ||
|
|
1181ceb735 | ||
|
|
f376f45277 | ||
|
|
d364afdf3b | ||
|
|
7ecfcf7eaa | ||
|
|
709c9bbe88 | ||
|
|
db175f606e | ||
|
|
3a664dc676 | ||
|
|
5f8d5f10a6 | ||
|
|
f02d4d9677 | ||
|
|
1889643eca | ||
|
|
4fc5c2024b | ||
|
|
354487d7f4 | ||
|
|
6478058946 | ||
|
|
8b8dcea3de | ||
|
|
64ace51e02 | ||
|
|
4cb5c67a44 | ||
|
|
2edefd9e80 | ||
|
|
fa8b16e7e7 | ||
|
|
2a953700e2 | ||
|
|
c1dc835b5c | ||
|
|
214f92c428 | ||
|
|
95fc680af9 | ||
|
|
22d0c6b5e1 | ||
|
|
ff1be13d0e | ||
|
|
a3201be7e7 | ||
|
|
37f0b51dff | ||
|
|
95d2dd74ad | ||
|
|
a175a25e6c | ||
|
|
6c406124dd | ||
|
|
0f17529486 | ||
|
|
dd10c867db | ||
|
|
8335dd3aa5 | ||
|
|
cd73ac7038 | ||
|
|
7cc33a839c | ||
|
|
828da6a425 | ||
|
|
2e5bdb8794 | ||
|
|
f56bac350b | ||
|
|
d373f16c81 | ||
|
|
ebffa15c7c | ||
|
|
6e4ee160ee | ||
|
|
a5697c5279 | ||
|
|
5abec720d8 | ||
|
|
84403b47cb | ||
|
|
4342a539af | ||
|
|
f4e744ade5 | ||
|
|
155d4c5591 | ||
|
|
f5cbe0e6ba | ||
|
|
a0e7d80daf | ||
|
|
d7e2f61e13 | ||
|
|
96ed4b02d7 | ||
|
|
cffc7c0121 | ||
|
|
d89000cc3d | ||
|
|
3661c75b39 | ||
|
|
3b20b11a9f | ||
|
|
cca366196d | ||
|
|
e09d444068 | ||
|
|
4956f5e710 | ||
|
|
5bebdfa434 | ||
|
|
aea915aa8d | ||
|
|
e0d29f398e | ||
|
|
c43b39bd88 | ||
|
|
8218da27b3 | ||
|
|
aa4bd00631 | ||
|
|
af6b9fdb39 | ||
|
|
c1b6493373 | ||
|
|
c386cca73f | ||
|
|
cadbdaff18 | ||
|
|
57510f517a | ||
|
|
3a2ddbe2e0 | ||
|
|
859347523f | ||
|
|
528fe6553f | ||
|
|
1faa0f4d41 | ||
|
|
82a11cae2d | ||
|
|
431948d768 | ||
|
|
f69c3d2b66 | ||
|
|
08b05964fa | ||
|
|
bd7f597682 | ||
|
|
6d4fbc9d32 | ||
|
|
124410a17b | ||
|
|
a543d57cbd | ||
|
|
8c3a0efe8b | ||
|
|
9e2841ad44 | ||
|
|
233e5f7c0e | ||
|
|
d5d9445cfd | ||
|
|
7be7417b5b | ||
|
|
0a14142156 | ||
|
|
a5e3785843 | ||
|
|
fc19ebb218 | ||
|
|
b45411f988 | ||
|
|
42f6371e47 | ||
|
|
be2aa6ab3a | ||
|
|
6e39ed9573 | ||
|
|
daf45a2993 | ||
|
|
d351b10fde | ||
|
|
0b21050904 | ||
|
|
644a7f524c | ||
|
|
53949ace36 | ||
|
|
14dba39157 | ||
|
|
43fd2a7423 | ||
|
|
4b0ac5356e | ||
|
|
c2f0d8e5ba | ||
|
|
0fb93b1e8a | ||
|
|
f94828c3f4 | ||
|
|
0e1e7c7df2 | ||
|
|
06110ba358 | ||
|
|
bae69f0102 | ||
|
|
77e7f4696b | ||
|
|
47fb2c9126 | ||
|
|
2ab9e4acd3 | ||
|
|
65ee7a8002 | ||
|
|
a5d0657d9b | ||
|
|
b8f01f91ca | ||
|
|
6cc6cf950c | ||
|
|
0b0349f746 | ||
|
|
2740974a13 | ||
|
|
97a9a262bb | ||
|
|
207efe700f | ||
|
|
e33315eaa6 | ||
|
|
5469d02d03 | ||
|
|
42c80893cb | ||
|
|
5c56ea3198 | ||
|
|
42f86329a9 | ||
|
|
08e273c0c7 | ||
|
|
5f8f98fa03 | ||
|
|
08c53ae27d | ||
|
|
f4f369895c | ||
|
|
38c21de0ee | ||
|
|
43cf36e0c7 | ||
|
|
9a9ff95989 | ||
|
|
fc40939775 | ||
|
|
553d8a828c | ||
|
|
80f7392fb1 | ||
|
|
61535c9972 | ||
|
|
b443c92a7a | ||
|
|
22da6c541d | ||
|
|
cd96354bc7 | ||
|
|
c6d859a08a | ||
|
|
ef47515420 | ||
|
|
a471a3d1b0 | ||
|
|
79a68fc2db | ||
|
|
93ddd46231 | ||
|
|
f5f0c197be | ||
|
|
4a30ec28b9 | ||
|
|
37ef9bb3d3 | ||
|
|
3e1d4a87fa | ||
|
|
320d46cc96 | ||
|
|
a06ceeef4c | ||
|
|
def573d7b4 | ||
|
|
7f7d29fdd1 | ||
|
|
c611d49941 | ||
|
|
11cd648699 | ||
|
|
98be5bf637 | ||
|
|
29df9771bb | ||
|
|
e68c1132da | ||
|
|
1047b1c191 | ||
|
|
9431e80eb4 | ||
|
|
178501de61 | ||
|
|
81c7f618fb | ||
|
|
eb2d816f2a | ||
|
|
244cb0e98c | ||
|
|
300ac4b77b | ||
|
|
0d189ac5df | ||
|
|
fed2c307a7 | ||
|
|
7aa2df5389 | ||
|
|
975ef0b305 | ||
|
|
e59146a733 | ||
|
|
8150c3602b | ||
|
|
ca8d776724 | ||
|
|
fdc40d5169 | ||
|
|
d0a97056c4 | ||
|
|
e98619f801 | ||
|
|
86eebc4305 | ||
|
|
4930f776fe | ||
|
|
19948c378a | ||
|
|
b3215b802d | ||
|
|
ed16d5b6aa | ||
|
|
7a94872413 | ||
|
|
98750922eb | ||
|
|
60c4c9ef2c | ||
|
|
101f04bbf2 | ||
|
|
4c593fa859 | ||
|
|
6603ad4006 | ||
|
|
d56c69dd01 | ||
|
|
e26a300c2f | ||
|
|
405482ede4 | ||
|
|
3db2b05069 | ||
|
|
ce5870223a | ||
|
|
9c1bb87493 | ||
|
|
1f7394dd54 | ||
|
|
bb569aa484 | ||
|
|
b0a350db37 | ||
|
|
78526a33b4 | ||
|
|
ab997b7fb1 | ||
|
|
6d431aebb7 | ||
|
|
a00ea23847 | ||
|
|
53ca751a02 | ||
|
|
8c444378bc | ||
|
|
55fdf0abda | ||
|
|
acd8ca2ebd | ||
|
|
06fdfad89e | ||
|
|
00ca493f33 | ||
|
|
b00d16fd7d | ||
|
|
03c373a4b3 | ||
|
|
bdaa39573f | ||
|
|
8a4c7f6e2b | ||
|
|
84ea0753d8 | ||
|
|
c1ef56c870 | ||
|
|
0e3252f045 | ||
|
|
65df218f91 | ||
|
|
5220a92be4 | ||
|
|
d643ca8dee | ||
|
|
de45152028 | ||
|
|
7fd9d0eeef | ||
|
|
d35a0b392e | ||
|
|
cbddfcde5b | ||
|
|
02ffa752ea | ||
|
|
6189e0c79b | ||
|
|
6ed6a0d447 | ||
|
|
484892ae97 | ||
|
|
027d9e938c | ||
|
|
e171f90d81 | ||
|
|
92c5d3b720 | ||
|
|
2a6e8ad68e | ||
|
|
55c8a0ac08 | ||
|
|
6073d948e6 | ||
|
|
b9171dd10b | ||
|
|
97b892fac2 | ||
|
|
b3fdf19b85 | ||
|
|
8c89e16ad2 | ||
|
|
960409c701 | ||
|
|
d1eeaa59a6 | ||
|
|
882f496e0a | ||
|
|
8b8aa1b4e6 | ||
|
|
932ac23f18 | ||
|
|
6c6560ca53 | ||
|
|
0698757e6e | ||
|
|
3a3f9b3496 | ||
|
|
97d87895bf | ||
|
|
c36fd72f99 | ||
|
|
57ae3d03a1 | ||
|
|
3737712044 | ||
|
|
8531339b4e | ||
|
|
540b12537a | ||
|
|
9e2fb45783 | ||
|
|
e7c128b973 | ||
|
|
d4a3470c9e | ||
|
|
4165b8c011 | ||
|
|
357ba2f64d | ||
|
|
3ce8818045 | ||
|
|
76082ac7cb | ||
|
|
4a451671d3 | ||
|
|
a3728b7188 | ||
|
|
a8711ab013 | ||
|
|
3e4754febd | ||
|
|
da1d661fa0 | ||
|
|
1801db523b | ||
|
|
2979c39628 | ||
|
|
bb9bb40287 | ||
|
|
8f6fdfeb0b | ||
|
|
b4e6d72fde | ||
|
|
a338ebfce0 | ||
|
|
4144364a15 | ||
|
|
11c0dd235b | ||
|
|
4a9502bf88 | ||
|
|
6c0d6d3178 | ||
|
|
0a26ca7104 | ||
|
|
2a15b2522f | ||
|
|
c11d1d78e9 | ||
|
|
7e51aa4cef | ||
|
|
9abb96fa6b | ||
|
|
879aafc916 | ||
|
|
5ec25122f6 | ||
|
|
9371b1aeb9 | ||
|
|
6f6d116451 | ||
|
|
3ff6eccfd7 | ||
|
|
5d1fa597ea | ||
|
|
3b6c2c9155 | ||
|
|
f863040b38 | ||
|
|
1033f8bce7 | ||
|
|
cf4dbbd681 | ||
|
|
6dccc693d0 | ||
|
|
c3dc66ce9c | ||
|
|
4856f56d61 | ||
|
|
2edc15dfb8 | ||
|
|
69e31baf68 | ||
|
|
586ff0c364 | ||
|
|
f3ba358d50 | ||
|
|
54bf4c3339 | ||
|
|
acda4344de | ||
|
|
6b0afd5d9b | ||
|
|
850910ed9e | ||
|
|
4ffa11a26c | ||
|
|
f5e60f8c40 | ||
|
|
ddaf1b79d4 | ||
|
|
d732dc3bb2 | ||
|
|
f0af5c5643 | ||
|
|
7a194789bc | ||
|
|
827d7a9a62 | ||
|
|
dae2761137 | ||
|
|
4304c1a7bb | ||
|
|
c537ea18a4 | ||
|
|
28fa7be75a | ||
|
|
f1a5862f3d | ||
|
|
4088e4e371 | ||
|
|
0ef6425847 | ||
|
|
7489deb1b7 | ||
|
|
2059e18e8b | ||
|
|
d11ee4c6d9 | ||
|
|
c74cbde329 | ||
|
|
20d541af06 | ||
|
|
2c163b3959 | ||
|
|
c6a7ef5574 | ||
|
|
7005c6af12 | ||
|
|
256986f01f | ||
|
|
33d322df9d | ||
|
|
631013be02 | ||
|
|
89a990e000 | ||
|
|
ccddd9d1de | ||
|
|
9587fc9909 | ||
|
|
22b7aed769 | ||
|
|
7db0da40ad | ||
|
|
2be70b2c36 | ||
|
|
0e03668cf2 | ||
|
|
28b89daa22 | ||
|
|
2bfa2f4292 | ||
|
|
3b22b9911b | ||
|
|
bc68ff1e43 | ||
|
|
f59af7483b | ||
|
|
394f85a96b | ||
|
|
626e3238f2 | ||
|
|
2051a84a09 | ||
|
|
f8bd4f5133 | ||
|
|
a4e514033d | ||
|
|
95f20ed7ab | ||
|
|
bfe92ea191 | ||
|
|
d3bacf475a | ||
|
|
3474464894 | ||
|
|
5b25bff2d8 | ||
|
|
c36896c524 | ||
|
|
d9fe25ac2e | ||
|
|
0bd006eef8 | ||
|
|
081d1f9779 | ||
|
|
546c975e28 | ||
|
|
05fb037edb | ||
|
|
5cdc196df1 | ||
|
|
0896c07972 | ||
|
|
be9f54d0a9 | ||
|
|
88fab44006 | ||
|
|
000fe3ddff | ||
|
|
f180f99a95 | ||
|
|
8d9aa9bf33 | ||
|
|
9fed91a47f | ||
|
|
12f830fbbb | ||
|
|
6d764a05b0 | ||
|
|
cd3ddec746 | ||
|
|
e4555a208d | ||
|
|
d53af96aa4 | ||
|
|
d1a9dc0f24 | ||
|
|
a1bcb28cea | ||
|
|
3fbba8b9db | ||
|
|
5fb850982a | ||
|
|
6f2e767c77 | ||
|
|
1ead5b0f3f | ||
|
|
eb6681d486 | ||
|
|
7503987630 | ||
|
|
1d337e8a76 | ||
|
|
f42e40712e | ||
|
|
b5fb8608ba | ||
|
|
dea1d67d03 | ||
|
|
9e6fc8f073 | ||
|
|
db6da75683 | ||
|
|
9cfd891fb9 | ||
|
|
f985680d2c | ||
|
|
8ae317e002 | ||
|
|
36e99c43ce | ||
|
|
eeb26aaa6f | ||
|
|
260dbe302a | ||
|
|
c297584726 | ||
|
|
5930383404 | ||
|
|
7395eb9791 | ||
|
|
e0de838b13 | ||
|
|
5292239714 | ||
|
|
b54fca4f7e | ||
|
|
26f47d2eb7 | ||
|
|
233e064f0b | ||
|
|
72c0898409 | ||
|
|
92d1f5122b | ||
|
|
314de3c3c0 | ||
|
|
544db43026 | ||
|
|
4aca3700b2 | ||
|
|
af833ff042 | ||
|
|
96c1a0a101 | ||
|
|
203e210d3a | ||
|
|
ff1c78fef5 | ||
|
|
d3a731dae4 | ||
|
|
73a4433d8e | ||
|
|
3b4ffea690 | ||
|
|
05d7f91392 | ||
|
|
e3e9f69654 | ||
|
|
34a4a9b08f | ||
|
|
e0836664e5 | ||
|
|
8d7499342f | ||
|
|
a7b24627b5 | ||
|
|
452324cf01 | ||
|
|
15c5c8db24 | ||
|
|
423efd54e4 | ||
|
|
5e45e0b3d7 | ||
|
|
a60e7f2376 | ||
|
|
ac7f0f7581 | ||
|
|
9c872345d6 | ||
|
|
bd312fa747 | ||
|
|
573fba55af | ||
|
|
39787f7d62 | ||
|
|
f4c067d527 | ||
|
|
8d3595c3e2 | ||
|
|
b81a57f1e4 | ||
|
|
a624f8b599 | ||
|
|
74f49a9d89 | ||
|
|
b7b89b30ea | ||
|
|
fb012504b2 | ||
|
|
7ed9ecf350 | ||
|
|
3af1520461 | ||
|
|
a7edda32ba | ||
|
|
7b09e320a8 | ||
|
|
46e077c40b | ||
|
|
7753ba6019 | ||
|
|
511204c158 | ||
|
|
65daef70c1 | ||
|
|
7ab373e86d | ||
|
|
79b81d39d8 | ||
|
|
2bbe5017d4 | ||
|
|
765a932561 | ||
|
|
4a2c9431ee | ||
|
|
de15d10f86 | ||
|
|
55d414bd72 | ||
|
|
1560685020 | ||
|
|
0e44659033 | ||
|
|
3b39cfb1cf | ||
|
|
15bca71b0d | ||
|
|
3c12455c5b | ||
|
|
927b4c928e | ||
|
|
be47cd2549 | ||
|
|
a4a17f93f5 | ||
|
|
745e52b798 | ||
|
|
bbc2da2a4b | ||
|
|
4c1793b4e3 | ||
|
|
d1ca73f813 | ||
|
|
cdc7d0fe0e | ||
|
|
a27a271034 | ||
|
|
6c097d24b1 | ||
|
|
0115b655bd | ||
|
|
e8f1463cc2 | ||
|
|
e164d53fcc | ||
|
|
b8f7b3cc8d | ||
|
|
b0e6fb73c6 | ||
|
|
dd50f1737b | ||
|
|
38a99856c0 | ||
|
|
09e7f0d013 | ||
|
|
e05e413cc4 | ||
|
|
2c21cbb5e6 | ||
|
|
3bc4b4bf9f | ||
|
|
a09f2a6987 | ||
|
|
1f57d730df | ||
|
|
eebc72b2bc | ||
|
|
2f3cf19afa | ||
|
|
10fbda702b | ||
|
|
17f130550e | ||
|
|
251f335fe3 | ||
|
|
ed2fb84e82 | ||
|
|
634684236c | ||
|
|
11e0d9ec14 | ||
|
|
5fd8f5aa27 | ||
|
|
c05ddb6119 | ||
|
|
ec86a972a2 | ||
|
|
62da8a34f3 | ||
|
|
6511ec3aa8 | ||
|
|
1707157c4d | ||
|
|
ee49ab84a4 | ||
|
|
b92a55f5fe | ||
|
|
7d3b16f24c | ||
|
|
b612b99421 | ||
|
|
5a5b3de79a | ||
|
|
0862e21a1b | ||
|
|
c148b768f4 | ||
|
|
fab9f684af | ||
|
|
0af48fe802 | ||
|
|
a9ae2c7457 | ||
|
|
87c0049e75 | ||
|
|
e5eb7ecdc1 | ||
|
|
a7e7823535 | ||
|
|
33ff96fd83 | ||
|
|
58852b3eeb | ||
|
|
403ee260f6 | ||
|
|
84149f26df | ||
|
|
2ab836c11c | ||
|
|
7d9dbd62c7 | ||
|
|
c9a6350894 | ||
|
|
2a4198f94d | ||
|
|
850ab6999c | ||
|
|
2946e81995 | ||
|
|
c78a64ec9b | ||
|
|
61386c5b4d | ||
|
|
279eae4b92 | ||
|
|
37f7af56e4 | ||
|
|
ec2d491dc8 | ||
|
|
890d415e1f | ||
|
|
75f951d254 | ||
|
|
d4b633bfc5 | ||
|
|
b20a534e0c | ||
|
|
64f7b13888 | ||
|
|
7523359aba | ||
|
|
eae9e1cbfa | ||
|
|
178cb98795 | ||
|
|
8f0c628310 | ||
|
|
16794a84de | ||
|
|
189da9afb3 | ||
|
|
af97682cca | ||
|
|
bdd64093e5 | ||
|
|
1dc5a6e5f8 | ||
|
|
89a888de76 | ||
|
|
3be797c759 | ||
|
|
ad585771e7 | ||
|
|
853b536394 | ||
|
|
bfb20aaa47 | ||
|
|
cf4dcb12ed | ||
|
|
3811d916b9 | ||
|
|
db6d3ba486 | ||
|
|
dd5315cbaa | ||
|
|
f8ac0ef0b9 | ||
|
|
b56f7e8ad2 | ||
|
|
57ca1e0cf6 | ||
|
|
d9e0ef0ebf | ||
|
|
8237aba840 | ||
|
|
63781c4953 | ||
|
|
c822012aad | ||
|
|
7d192ace6d | ||
|
|
58e461e4c7 | ||
|
|
5dc10bd5a2 | ||
|
|
f9a098743b | ||
|
|
f1fe70656f | ||
|
|
e24ea7c1bb | ||
|
|
8e2e852b89 | ||
|
|
4c10294a29 | ||
|
|
31bf7443a6 | ||
|
|
e6d18fc948 | ||
|
|
a90ace7fa1 | ||
|
|
caa8210112 | ||
|
|
1c85edbda9 | ||
|
|
72d19cd523 | ||
|
|
cc17aa8d6b | ||
|
|
181e238b55 | ||
|
|
7d74bf781c | ||
|
|
a0fde50c08 | ||
|
|
8224cc34ea | ||
|
|
411f361fcb | ||
|
|
e080bdff0f | ||
|
|
fae8da2bcb | ||
|
|
2d1337e190 | ||
|
|
ec4e3c36af | ||
|
|
1489e6c00e | ||
|
|
61769a1bad | ||
|
|
56839ba50f | ||
|
|
fe6a2e9f14 | ||
|
|
11c91e3b8d | ||
|
|
e70f405abd | ||
|
|
8b8ee00725 | ||
|
|
121126cb50 | ||
|
|
9a13086122 | ||
|
|
c68833cd7f | ||
|
|
d4fa528d5c | ||
|
|
8e19336f49 | ||
|
|
52178caa98 | ||
|
|
e5cb5d439d | ||
|
|
e28fa6c787 | ||
|
|
313574ce97 | ||
|
|
96a84294f4 | ||
|
|
7611aa2f65 | ||
|
|
97fa7e0817 | ||
|
|
417b5b031b | ||
|
|
a7e26e8f59 | ||
|
|
7665e5a035 | ||
|
|
c9a553fdb4 | ||
|
|
5e072dabc3 | ||
|
|
e5b7424592 | ||
|
|
e99dc2a3da | ||
|
|
211214a8d3 | ||
|
|
e10d11c6c7 | ||
|
|
f0088ca2be | ||
|
|
3cd3d1f5ae | ||
|
|
14cef94e61 | ||
|
|
71ef94dae6 | ||
|
|
fc99954b2e | ||
|
|
1526501ed7 | ||
|
|
5d98c09ee9 | ||
|
|
7da5196bac | ||
|
|
7fd40cb0e6 | ||
|
|
b6b1c23575 | ||
|
|
409ded385f | ||
|
|
e2d0c9f3c1 | ||
|
|
9cf85f90fb | ||
|
|
fb796d14ec | ||
|
|
43c2eb383e | ||
|
|
2bc46b8f21 | ||
|
|
c84648c157 | ||
|
|
b15441df1c | ||
|
|
85f32d6c0f | ||
|
|
9908c93d4c | ||
|
|
388c7f706d | ||
|
|
36398eedca | ||
|
|
aa41b813b8 | ||
|
|
4e2fe6427c | ||
|
|
7f0a0ce5e5 | ||
|
|
2a39bc6e68 | ||
|
|
07c47eed54 | ||
|
|
a165072915 | ||
|
|
c5109d39d0 | ||
|
|
e4a51e460e | ||
|
|
2467e9996e | ||
|
|
3afd12f977 | ||
|
|
f45b3fa3f2 | ||
|
|
fc6b08c8ab | ||
|
|
441cd27a53 | ||
|
|
c73737f77f | ||
|
|
ecd92d5e71 | ||
|
|
d1143b4580 | ||
|
|
09595697cc | ||
|
|
1f887a6ca0 | ||
|
|
eba6e46c74 | ||
|
|
f0daf11375 | ||
|
|
71341a623a | ||
|
|
e2a7a795d4 | ||
|
|
ed7d7a9ad9 | ||
|
|
9b69bde613 | ||
|
|
119ffbab40 | ||
|
|
e92b54b869 | ||
|
|
0273e128f4 | ||
|
|
a948050015 | ||
|
|
a0dbeabed1 | ||
|
|
eb615347d2 | ||
|
|
2d4063234a | ||
|
|
4fcb66df92 | ||
|
|
a9bdac836c | ||
|
|
bb696f3fd0 | ||
|
|
bc26244114 | ||
|
|
08c2f03ccc | ||
|
|
9e35e5c2b0 | ||
|
|
d10f63b666 | ||
|
|
aa6ac5a751 | ||
|
|
42e79bfab9 | ||
|
|
a3b4f80c99 | ||
|
|
b2f3cfde0b | ||
|
|
c036480ce2 | ||
|
|
043f9d8298 | ||
|
|
627a487fcf | ||
|
|
b0d0d046e7 | ||
|
|
e09c42a78a | ||
|
|
28d7e11e4f | ||
|
|
a5d35d6e84 | ||
|
|
e8982068b6 | ||
|
|
6e3670bce5 | ||
|
|
14aca11dcd | ||
|
|
e7b738fe35 | ||
|
|
1ee636461c | ||
|
|
c7e7d5e980 | ||
|
|
57bf76a0e1 | ||
|
|
c80c80200c | ||
|
|
5859f9a3cb | ||
|
|
a825414558 | ||
|
|
6577be3744 | ||
|
|
e936041713 | ||
|
|
a8b31090a1 | ||
|
|
14e708f366 | ||
|
|
3502ff837f | ||
|
|
84cfe5db53 | ||
|
|
26bd8304c6 | ||
|
|
ee3973f507 | ||
|
|
55fa051d3a | ||
|
|
4d38833170 | ||
|
|
e47646ddba | ||
|
|
7081e2ab66 | ||
|
|
e286536959 | ||
|
|
88374c2522 | ||
|
|
3fcfab9772 | ||
|
|
de8888ec58 | ||
|
|
3af8dc9580 | ||
|
|
71ae7f10f7 | ||
|
|
38304136d3 | ||
|
|
ff4b1758e1 | ||
|
|
a9b4894b07 | ||
|
|
28ba5fc8a4 | ||
|
|
c7a4c2c88a | ||
|
|
0617768ee2 | ||
|
|
62b60c70e6 | ||
|
|
0394f32438 | ||
|
|
569184a5b0 | ||
|
|
41c4b51be5 | ||
|
|
513bf485f2 | ||
|
|
263240919c | ||
|
|
d2b9493d72 | ||
|
|
60cea6c8bf | ||
|
|
9b7f0627ea | ||
|
|
2e9cebf025 | ||
|
|
48d4ea5542 | ||
|
|
9bdbbec0c8 | ||
|
|
8f0b3cb00f | ||
|
|
646fa53b44 | ||
|
|
7f10264fb6 | ||
|
|
7e0fdf5928 | ||
|
|
4a28d28c08 | ||
|
|
bfea79f1e4 | ||
|
|
87d78e87d2 | ||
|
|
c712c6e55e | ||
|
|
510054492c | ||
|
|
12db7a9af2 | ||
|
|
fcab682231 | ||
|
|
0981a6403d | ||
|
|
37bd4dfd83 | ||
|
|
057d77fcd4 | ||
|
|
713ea080d2 | ||
|
|
bf5063de9a | ||
|
|
13efedde34 | ||
|
|
c1beefd543 | ||
|
|
1d81c17335 | ||
|
|
b8b87716db | ||
|
|
1108bf7d86 | ||
|
|
62747c934d | ||
|
|
81b90c9f15 | ||
|
|
5a772a5770 | ||
|
|
3433ec306e | ||
|
|
03039ebfa8 | ||
|
|
271c23f459 | ||
|
|
0892eab1d3 | ||
|
|
d68996127d | ||
|
|
83270557a7 | ||
|
|
0e4f80bf1b | ||
|
|
b7abc8566e | ||
|
|
92f86de938 | ||
|
|
b17019a21c | ||
|
|
be58b929f2 | ||
|
|
25baaf530d | ||
|
|
320d488627 | ||
|
|
879f818d81 | ||
|
|
80a6751c51 | ||
|
|
05bef92f0f | ||
|
|
01d5ecb86b | ||
|
|
f50f46c1ce | ||
|
|
adae49b184 | ||
|
|
2bd7c75a35 | ||
|
|
9955155ce0 | ||
|
|
b0e0f7ae76 | ||
|
|
0315b87664 | ||
|
|
ac29f8bc91 | ||
|
|
ec6a427e0a | ||
|
|
09b59bd56a | ||
|
|
d0548caa76 | ||
|
|
96d2a331a1 | ||
|
|
1d7ed6737e | ||
|
|
97b6656182 | ||
|
|
f108eac527 | ||
|
|
144c3b3eb2 | ||
|
|
426e108b34 | ||
|
|
229ad01042 | ||
|
|
71e3d8b1de | ||
|
|
a5750a6ef6 | ||
|
|
d5e39260b3 | ||
|
|
0996fdfb55 | ||
|
|
07eb108a6d | ||
|
|
41249377d2 | ||
|
|
be091b17d9 | ||
|
|
398467b2f4 | ||
|
|
796f069d74 | ||
|
|
82b6245aaa | ||
|
|
f0e5cf53b8 | ||
|
|
7585d37818 | ||
|
|
ee54ebfe77 | ||
|
|
43ee4a8faf | ||
|
|
ab5bcd6d2f | ||
|
|
6b8c99a275 | ||
|
|
073221d321 | ||
|
|
dfb9579a73 | ||
|
|
26bc83c984 | ||
|
|
8139a079f8 | ||
|
|
0d341c256b | ||
|
|
5a17bfc9c9 | ||
|
|
1d981b3171 | ||
|
|
743ec43460 | ||
|
|
87ee3a6f58 | ||
|
|
10897787af | ||
|
|
4d073b0350 | ||
|
|
625018b654 | ||
|
|
5f2410119d | ||
|
|
424c0d0ebb | ||
|
|
71c9adfdba | ||
|
|
723bb4d0b9 | ||
|
|
cb76635da7 | ||
|
|
237e31b3ce | ||
|
|
d0741f21b1 | ||
|
|
a376608344 | ||
|
|
7a964efb3a | ||
|
|
1d0da944a6 | ||
|
|
d0880694eb | ||
|
|
1f689bdaae | ||
|
|
6a0361d0c6 | ||
|
|
f29c7bf910 | ||
|
|
2cc4c73b64 | ||
|
|
8841a328c8 | ||
|
|
c555b60b36 | ||
|
|
b30e2ab5dc | ||
|
|
115792d1db | ||
|
|
7075a5828e | ||
|
|
bd04ce62e0 | ||
|
|
3bb6ecdc3b | ||
|
|
a885e7309a | ||
|
|
6706a23fb5 | ||
|
|
c7faab4d7c | ||
|
|
080ba00956 | ||
|
|
2613ebfa01 | ||
|
|
53c526c01d | ||
|
|
dc371618ba | ||
|
|
59288417f0 | ||
|
|
1dd401b570 | ||
|
|
ee194ab369 | ||
|
|
b8113abd93 | ||
|
|
5182da9f45 | ||
|
|
14c25717ab | ||
|
|
3087ac1145 | ||
|
|
cdbde5b88d | ||
|
|
7a1ee99345 | ||
|
|
7a31a3d106 | ||
|
|
fe3dba3fd7 | ||
|
|
31c7f25541 | ||
|
|
45aef06d00 | ||
|
|
0eb71cdce8 | ||
|
|
45bc511955 | ||
|
|
dedf56b5f8 | ||
|
|
1e28f2478c | ||
|
|
3cdbcbff8f | ||
|
|
9a3bc9cc1e | ||
|
|
6bd905ff43 | ||
|
|
ae83ceab5e | ||
|
|
28ee59dd64 | ||
|
|
b4ef3d91ab | ||
|
|
efac4a135a | ||
|
|
984e91e519 | ||
|
|
49dc62201c | ||
|
|
4791fc341e | ||
|
|
4743a5e166 | ||
|
|
908c66ca84 | ||
|
|
62719f58c9 | ||
|
|
d183cd3c78 | ||
|
|
ac5a901daf | ||
|
|
aaee03d502 | ||
|
|
329948e685 | ||
|
|
f6a76ebcd5 | ||
|
|
1cb87164d9 | ||
|
|
64fa70ac0a | ||
|
|
2c42bda9ce | ||
|
|
1787892d32 | ||
|
|
7cc8f63125 | ||
|
|
2b6515e704 | ||
|
|
27bc8d4f7b | ||
|
|
90d531c640 | ||
|
|
2c24b9da5a | ||
|
|
d79a2bb17e | ||
|
|
4ac1bf2d47 | ||
|
|
8b4b647d77 | ||
|
|
6495717036 | ||
|
|
d23fa84471 | ||
|
|
937c8ba231 | ||
|
|
88beeb7916 | ||
|
|
34a27c7cab | ||
|
|
c04541b4da | ||
|
|
8d4a86635f | ||
|
|
2dc6ee7e1c | ||
|
|
ccab565a4a | ||
|
|
5c8fbe8186 | ||
|
|
7fd46dcee9 | ||
|
|
3ef4c4fbe7 | ||
|
|
47258f40ea | ||
|
|
123a3c0347 | ||
|
|
d60a2e693c | ||
|
|
db524a35c6 | ||
|
|
f23a860b33 | ||
|
|
df008d99f5 | ||
|
|
fd920fba9b | ||
|
|
d9cd8c6fff | ||
|
|
b303c785e9 | ||
|
|
36fa21d7ce | ||
|
|
f1a388700f | ||
|
|
2b66a7a39a | ||
|
|
4daa584b7d | ||
|
|
de9507bc63 | ||
|
|
2b48d09286 | ||
|
|
97741aaf1c | ||
|
|
146779340c | ||
|
|
d1f6fab68a | ||
|
|
c835918123 | ||
|
|
fd9345cda3 | ||
|
|
8a615ad969 | ||
|
|
eaaa4a7b31 | ||
|
|
c3e5654113 | ||
|
|
ff2a40b109 | ||
|
|
7e00a6e2ff | ||
|
|
8ee36fb78c | ||
|
|
c3483747eb | ||
|
|
3fda792929 | ||
|
|
ba57a9ba07 | ||
|
|
a489ac07cd | ||
|
|
3505417e3f | ||
|
|
29e78780e5 | ||
|
|
868caeaf1b | ||
|
|
7f72c7b5fc | ||
|
|
e428019170 | ||
|
|
e9407cd48d | ||
|
|
5042be1b72 | ||
|
|
315713ff9a | ||
|
|
7b3fb23216 | ||
|
|
1d6861667b | ||
|
|
ae1df044e2 | ||
|
|
9d2de46c47 | ||
|
|
dfa74daf52 | ||
|
|
d4110eef7e | ||
|
|
86833454a4 | ||
|
|
b8f7563da9 | ||
|
|
e080ccbcf8 | ||
|
|
13469e1576 | ||
|
|
8f23ba076b | ||
|
|
2cf1ff41c0 | ||
|
|
118517b076 | ||
|
|
45abcbc301 | ||
|
|
d5df1c8ae3 | ||
|
|
9712decefe | ||
|
|
6ba87c37d6 | ||
|
|
b06bc799bc | ||
|
|
a36b316079 | ||
|
|
c54fc44b33 | ||
|
|
c7b903f4b0 | ||
|
|
cdfa31c390 | ||
|
|
3d51c2a4e7 | ||
|
|
b94229fb8e | ||
|
|
637c5c2936 | ||
|
|
cbaabe7c24 | ||
|
|
35c7269bac | ||
|
|
ed85388de5 | ||
|
|
f79434f49d | ||
|
|
fcba2eabc4 | ||
|
|
e523b34db9 | ||
|
|
96b5f8786b | ||
|
|
1a1e0ef138 | ||
|
|
2d3ea552c4 | ||
|
|
c4e72e6ca9 | ||
|
|
9f6f3dd75d | ||
|
|
6b994cb5ff | ||
|
|
c44324fda5 | ||
|
|
8d91a88b22 | ||
|
|
f7258e314d | ||
|
|
6a3c6c43ea | ||
|
|
6c1977b448 | ||
|
|
71602afcad | ||
|
|
0c053431f5 | ||
|
|
185a983c9d | ||
|
|
adbbb1ce02 | ||
|
|
f78d094483 | ||
|
|
7eeb79ce67 | ||
|
|
7caa67d8ec | ||
|
|
919b1b42fa | ||
|
|
760807c5e0 | ||
|
|
9ac401267a | ||
|
|
a71200a57a | ||
|
|
b062e7e774 | ||
|
|
d42aa08705 | ||
|
|
282c6777ee | ||
|
|
9981d74ee1 | ||
|
|
1a4506f6ab | ||
|
|
914caab43d | ||
|
|
5eb11800a7 | ||
|
|
1424e8e29b | ||
|
|
42f079c355 | ||
|
|
8f884d7f23 | ||
|
|
2cf21a3008 | ||
|
|
2361ca2c71 | ||
|
|
3cee2d185d | ||
|
|
cc785ccad0 | ||
|
|
da4a2b7b6e | ||
|
|
f17be2403f | ||
|
|
47bb49384e | ||
|
|
8eed1a8bec | ||
|
|
fcf7fcee03 | ||
|
|
5c18d6865d | ||
|
|
cdf8c67898 | ||
|
|
00265af528 | ||
|
|
77b76ae51b | ||
|
|
9ef2370a2a | ||
|
|
c700421aa7 | ||
|
|
77abe882ab | ||
|
|
023f94b066 | ||
|
|
7ea1ef3feb | ||
|
|
df463f4ea6 | ||
|
|
3da4c9c384 | ||
|
|
97bc185152 | ||
|
|
c8780da19c | ||
|
|
c02bf4986f | ||
|
|
9a5d5d9aec | ||
|
|
2c62a8dbbc | ||
|
|
55629a4891 | ||
|
|
907133a38c | ||
|
|
86c0858130 | ||
|
|
30511fd3ab | ||
|
|
614a6ab861 | ||
|
|
4bff2814a9 | ||
|
|
8e0ffde3e0 | ||
|
|
795153b213 | ||
|
|
fd08d41962 | ||
|
|
75e35f3832 | ||
|
|
16268f92cc | ||
|
|
d72c863353 | ||
|
|
96b6a1a418 | ||
|
|
0067555c38 | ||
|
|
77d4453334 | ||
|
|
c563c2bfec | ||
|
|
266153f218 | ||
|
|
73e737d775 | ||
|
|
5029101048 | ||
|
|
0c9a241487 | ||
|
|
41d2cd318d | ||
|
|
0d2cdb5c2f | ||
|
|
f8d55b5448 | ||
|
|
00a3a8834b | ||
|
|
32f6ddf6db | ||
|
|
1220ff5da6 | ||
|
|
89c576fbe1 | ||
|
|
e276ec2e94 | ||
|
|
bafbf679b6 | ||
|
|
8e2ef2842e | ||
|
|
218b70fd96 | ||
|
|
e3323e8888 | ||
|
|
eacaf3489e | ||
|
|
2deac34648 | ||
|
|
e8c52c6780 | ||
|
|
e7e7ae0104 | ||
|
|
0d4c1e8460 | ||
|
|
cdabea7c76 | ||
|
|
749091bf3a | ||
|
|
28810e6ce0 | ||
|
|
f2c15b73ad | ||
|
|
a88527b2a0 | ||
|
|
b1e8db7ca7 | ||
|
|
06657b3e10 | ||
|
|
81a7ea36db | ||
|
|
af74c037f4 | ||
|
|
6796749136 | ||
|
|
e67a6dc321 | ||
|
|
15a215729e | ||
|
|
ce95c55d65 | ||
|
|
8eb066c692 | ||
|
|
a0de20e9bc | ||
|
|
2dbf58d461 | ||
|
|
9a47e1834f | ||
|
|
61ed3b8ab3 | ||
|
|
bb1552be29 | ||
|
|
5614ece9a1 | ||
|
|
3546b30473 | ||
|
|
cf32da3748 | ||
|
|
909b0c7462 | ||
|
|
15cd5c777b | ||
|
|
37c653396b | ||
|
|
8c4bcd36ea | ||
|
|
88610b1b74 | ||
|
|
9aeb111fba | ||
|
|
7ca5219297 | ||
|
|
f4a00eba26 | ||
|
|
86a8900e21 | ||
|
|
67bb885900 | ||
|
|
e55ac77c94 | ||
|
|
f9205caf22 | ||
|
|
3c9af7f151 | ||
|
|
5e54e78176 | ||
|
|
caf018538f | ||
|
|
ccae2c733b | ||
|
|
5673c4cf91 | ||
|
|
ec8af1dd40 | ||
|
|
50c5abf6bb | ||
|
|
a44377c7b0 | ||
|
|
5237f44c4a | ||
|
|
99e9abe843 | ||
|
|
5b4bbab9be | ||
|
|
413c69ddc9 | ||
|
|
53f8459e97 | ||
|
|
3714b7ea7d | ||
|
|
0ecb920866 | ||
|
|
563099f7fa | ||
|
|
ce76a25101 | ||
|
|
57dc0304b5 | ||
|
|
872e73314e | ||
|
|
727bd73d0b | ||
|
|
a2a1901b09 | ||
|
|
43869b9938 | ||
|
|
9a86c0cebc | ||
|
|
d0cc4006d3 | ||
|
|
dc2911ae72 | ||
|
|
16053e81bf | ||
|
|
d59d57957c | ||
|
|
4e792546e8 | ||
|
|
79aa74b771 | ||
|
|
bdec4e6488 | ||
|
|
71ddeb40a6 | ||
|
|
80ef6cbaab | ||
|
|
15dbb6383c | ||
|
|
3e9fb0dc84 |
4
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
4
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
contact_links:
|
||||
- name: Nominatim Discussions
|
||||
url: https://github.com/osm-search/Nominatim/discussions
|
||||
about: Ask questions, get support, share ideas and discuss with community members.
|
||||
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Before opening a new feature request, please search through the open issue to check that your request hasn't been reported already.
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
36
.github/ISSUE_TEMPLATE/report-issues-with-search-results.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/report-issues-with-search-results.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: Report issues with search results
|
||||
about: You have searched something with Nominatim and did not get the expected result.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## What did you search for?
|
||||
|
||||
Please try to provide a link to your search. You can go to https://nominatim.openstreetmap.org and repeat your search there. If you originally found the issue somewhere else, please tell us what software/website you were using.
|
||||
|
||||
## What result did you get?
|
||||
|
||||
## What result did you expect?
|
||||
|
||||
**Is the result in the right place and just named wrongly?**
|
||||
|
||||
Please tell us the display name you expected.
|
||||
|
||||
**Is the result 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.
|
||||
|
||||
To get the link to the OSM object, you can try the following:
|
||||
|
||||
* Go to [https://openstreetmap.org](https://openstreetmap.org).
|
||||
* Move to the area of the map where you expect the result and then zoom in as much as possible.
|
||||
* Click on the question mark on the right side of the map. You get a question cursor. Use it to click on the map where your object is located.
|
||||
* Find the object of interest in the list that appears on the left side.
|
||||
* Click on the object and report back the URL that the browser shows.
|
||||
|
||||
## Further details
|
||||
|
||||
Anything else we should know about the search. Particularities with addresses in the area etc.
|
||||
31
.github/ISSUE_TEMPLATE/report-problems-with-the-software.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE/report-problems-with-the-software.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: Report problems with the software
|
||||
about: You have your own installation of Nominatim and found a bug.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
___Note: if you are installing Nominatim through a docker image, you should report issues with the installation process with the docker repository first.___
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Please describe what you did to get to the issue.
|
||||
|
||||
**Software Environment (please complete the following information):**
|
||||
- Nominatim version:
|
||||
- Postgresql version:
|
||||
- Postgis version:
|
||||
- OS:
|
||||
|
||||
**Hardware Configuration (please complete the following information):**
|
||||
- RAM:
|
||||
- number of CPUs:
|
||||
- type and size of disks:
|
||||
- bare metal/AWS/other cloud service:
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
28
.github/actions/build-nominatim/action.yml
vendored
Normal file
28
.github/actions/build-nominatim/action.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: 'Build Nominatim'
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
|
||||
steps:
|
||||
- name: Install prerequisits
|
||||
run: sudo apt-get install -y -qq libboost-system-dev libboost-filesystem-dev libexpat1-dev zlib1g-dev libbz2-dev libpq-dev libproj-dev python3-psycopg2 python3-pyosmium
|
||||
shell: bash
|
||||
|
||||
- name: Configure
|
||||
run: mkdir build && cd build && cmake ..
|
||||
shell: bash
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
make -j2 all
|
||||
./utils/setup.php --setup-website
|
||||
shell: bash
|
||||
working-directory: build
|
||||
|
||||
- name: Download dependencies
|
||||
run: |
|
||||
if [ ! -f data/country_osm_grid.sql.gz ]; then
|
||||
wget --no-verbose -O data/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
45
.github/actions/setup-postgresql/action.yml
vendored
Normal file
45
.github/actions/setup-postgresql/action.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: 'Setup Postgresql and Postgis'
|
||||
|
||||
inputs:
|
||||
postgresql-version:
|
||||
description: 'Version of PostgreSQL to install'
|
||||
required: true
|
||||
postgis-version:
|
||||
description: 'Version of Postgis to install'
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
|
||||
steps:
|
||||
- name: Remove existing PostgreSQL
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get purge -yq postgresql*
|
||||
shell: bash
|
||||
|
||||
- name: Install PostgreSQL
|
||||
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} postgresql-server-dev-${PGVER}
|
||||
shell: bash
|
||||
env:
|
||||
PGVER: ${{ inputs.postgresql-version }}
|
||||
POSTGISVER: ${{ inputs.postgis-version }}
|
||||
|
||||
- name: Adapt postgresql configuration
|
||||
run: |
|
||||
echo 'fsync = off' | sudo tee /etc/postgresql/${PGVER}/main/conf.d/local.conf
|
||||
echo 'synchronous_commit = off' | sudo tee -a /etc/postgresql/${PGVER}/main/conf.d/local.conf
|
||||
echo 'full_page_writes = off' | sudo tee -a /etc/postgresql/${PGVER}/main/conf.d/local.conf
|
||||
echo 'shared_buffers = 1GB' | sudo tee -a /etc/postgresql/${PGVER}/main/conf.d/local.conf
|
||||
echo 'port = 5432' | sudo tee -a /etc/postgresql/${PGVER}/main/conf.d/local.conf
|
||||
shell: bash
|
||||
env:
|
||||
PGVER: ${{ inputs.postgresql-version }}
|
||||
|
||||
- name: Setup database
|
||||
run: |
|
||||
sudo systemctl restart postgresql
|
||||
sudo -u postgres createuser -S www-data
|
||||
sudo -u postgres createuser -s runner
|
||||
shell: bash
|
||||
120
.github/workflows/ci-tests.yml
vendored
Normal file
120
.github/workflows/ci-tests.yml
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
name: CI Tests
|
||||
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
postgresql: [9.5, 13]
|
||||
include:
|
||||
- postgresql: 9.5
|
||||
postgis: 2.5
|
||||
- postgresql: 13
|
||||
postgis: 3
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Get Date
|
||||
id: get-date
|
||||
run: |
|
||||
echo "::set-output name=date::$(/bin/date -u "+%Y%W")"
|
||||
shell: bash
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
data/country_osm_grid.sql.gz
|
||||
monaco-latest.osm.pbf
|
||||
key: nominatim-data-${{ steps.get-date.outputs.date }}
|
||||
|
||||
- uses: ./.github/actions/setup-postgresql
|
||||
with:
|
||||
postgresql-version: ${{ matrix.postgresql }}
|
||||
postgis-version: ${{ matrix.postgis }}
|
||||
- uses: ./.github/actions/build-nominatim
|
||||
|
||||
- name: Install test prerequsites
|
||||
run: |
|
||||
sudo apt-get install -y -qq php-codesniffer python3-tidylib
|
||||
sudo pip3 install behave nose
|
||||
|
||||
- name: PHP linting
|
||||
run: phpcs --report-width=120 .
|
||||
|
||||
- name: PHP unit tests
|
||||
run: phpunit ./
|
||||
working-directory: test/php
|
||||
|
||||
- name: BDD tests
|
||||
run: behave -DREMOVE_TEMPLATE=1 --format=progress3 db osm2pgsql
|
||||
working-directory: test/bdd
|
||||
|
||||
import:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Get Date
|
||||
id: get-date
|
||||
run: |
|
||||
echo "::set-output name=date::$(/bin/date -u "+%Y%W")"
|
||||
shell: bash
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
data/country_osm_grid.sql.gz
|
||||
monaco-latest.osm.pbf
|
||||
key: nominatim-data-${{ steps.get-date.outputs.date }}
|
||||
|
||||
- uses: ./.github/actions/setup-postgresql
|
||||
with:
|
||||
postgresql-version: 13
|
||||
postgis-version: 3
|
||||
- uses: ./.github/actions/build-nominatim
|
||||
|
||||
- name: Create configuration
|
||||
run: |
|
||||
echo '<?php' > settings/local.php
|
||||
echo " @define('CONST_Pyosmium_Binary', '/usr/lib/python3-pyosmium/pyosmium-get-changes');" >> settings/local.php
|
||||
working-directory: build
|
||||
|
||||
- name: Download import data
|
||||
run: |
|
||||
if [ ! -f monaco-latest.osm.pbf ]; then
|
||||
wget --no-verbose https://download.geofabrik.de/europe/monaco-latest.osm.pbf
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Import
|
||||
run: php ./utils/setup.php --osm-file ../monaco-latest.osm.pbf --osm2pgsql-cache 500 --all
|
||||
working-directory: build
|
||||
|
||||
- name: Import special phrases
|
||||
run: php ./utils/specialphrases.php --wiki-import | psql -d nominatim
|
||||
working-directory: build
|
||||
|
||||
- name: Check import
|
||||
run: php ./utils/check_import_finished.php
|
||||
working-directory: build
|
||||
|
||||
- name: Run update
|
||||
run: |
|
||||
php ./utils/update.php --init-updates
|
||||
php ./utils/update.php --import-osmosis
|
||||
working-directory: build
|
||||
|
||||
- name: Run reverse-only import
|
||||
run : |
|
||||
dropdb nominatim
|
||||
php ./utils/setup.php --osm-file ../monaco-latest.osm.pbf --reverse-only --all
|
||||
working-directory: build
|
||||
31
.travis.yml
31
.travis.yml
@@ -1,31 +0,0 @@
|
||||
---
|
||||
sudo: required
|
||||
dist: trusty
|
||||
language: python
|
||||
python:
|
||||
- "3.6"
|
||||
addons:
|
||||
postgresql: "9.6"
|
||||
git:
|
||||
depth: 3
|
||||
env:
|
||||
- TEST_SUITE=tests
|
||||
- TEST_SUITE=monaco
|
||||
install:
|
||||
- vagrant/install-on-travis-ci.sh
|
||||
before_script:
|
||||
- psql -U postgres -c "create extension postgis"
|
||||
script:
|
||||
- cd $TRAVIS_BUILD_DIR/build
|
||||
- if [[ $TEST_SUITE == "monaco" ]]; then wget --no-verbose --output-document=../data/monaco.osm.pbf http://download.geofabrik.de/europe/monaco-latest.osm.pbf; fi
|
||||
- if [[ $TEST_SUITE == "monaco" ]]; then ./utils/setup.php --osm-file ../data/monaco.osm.pbf --osm2pgsql-cache 1000 --all 2>&1 | grep -v 'ETA (seconds)'; fi
|
||||
- if [[ $TEST_SUITE == "monaco" ]]; then ./utils/specialphrases.php --wiki-import | psql -d test_api_nominatim >/dev/null; fi
|
||||
- cd $TRAVIS_BUILD_DIR/test/php
|
||||
- if [[ $TEST_SUITE == "tests" ]]; then phpunit ./ ; fi
|
||||
- if [[ $TEST_SUITE == "tests" ]]; then phpcs --report-width=120 */**.php ; fi
|
||||
- cd $TRAVIS_BUILD_DIR/test/bdd
|
||||
- # behave --format=progress3 api
|
||||
- if [[ $TEST_SUITE == "tests" ]]; then behave --format=progress3 db ; fi
|
||||
- if [[ $TEST_SUITE == "tests" ]]; then behave --format=progress3 osm2pgsql ; fi
|
||||
notifications:
|
||||
email: false
|
||||
17
AUTHORS
17
AUTHORS
@@ -3,16 +3,13 @@ Nominatim was written by:
|
||||
Brian Quinion
|
||||
Sarah Hoffmann
|
||||
Marc Tobias Metten
|
||||
|
||||
markigail
|
||||
gemo1011
|
||||
IrlJidel
|
||||
Frederik Ramm
|
||||
Michael Spreng
|
||||
Daniele Forsi
|
||||
mfn
|
||||
Grant Slater
|
||||
Andree Klattenhoff
|
||||
appelflap
|
||||
b3nn0
|
||||
Spin0us
|
||||
Kurt Roeckx
|
||||
Rodolphe Quiédeville
|
||||
|
||||
and many more.
|
||||
|
||||
For a full list of contributors see
|
||||
https://github.com/openstreetmap/Nominatim/graphs/contributors
|
||||
|
||||
233
CMakeLists.txt
233
CMakeLists.txt
@@ -19,8 +19,8 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||
project(nominatim)
|
||||
|
||||
set(NOMINATIM_VERSION_MAJOR 3)
|
||||
set(NOMINATIM_VERSION_MINOR 0)
|
||||
set(NOMINATIM_VERSION_PATCH 1)
|
||||
set(NOMINATIM_VERSION_MINOR 6)
|
||||
set(NOMINATIM_VERSION_PATCH 0)
|
||||
|
||||
set(NOMINATIM_VERSION "${NOMINATIM_VERSION_MAJOR}.${NOMINATIM_VERSION_MINOR}.${NOMINATIM_VERSION_PATCH}")
|
||||
|
||||
@@ -28,124 +28,175 @@ add_definitions(-DNOMINATIM_VERSION="${NOMINATIM_VERSION}")
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
#
|
||||
# Find external dependencies
|
||||
#
|
||||
# Configuration
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
set(BUILD_TESTS off CACHE BOOL "Build test suite" FORCE)
|
||||
set(WITH_LUA off CACHE BOOL "Build with lua support" FORCE)
|
||||
set(BUILD_IMPORTER on CACHE BOOL "Build everything for importing/updating the database")
|
||||
set(BUILD_API on CACHE BOOL "Build everything for the API server")
|
||||
set(BUILD_MODULE on CACHE BOOL "Build PostgreSQL module")
|
||||
set(BUILD_TESTS on CACHE BOOL "Build test suite")
|
||||
set(BUILD_DOCS on CACHE BOOL "Build documentation")
|
||||
set(BUILD_OSM2PGSQL on CACHE BOOL "Build osm2pgsql (expert only)")
|
||||
|
||||
if (NOT EXISTS "${CMAKE_SOURCE_DIR}/osm2pgsql/CMakeLists.txt")
|
||||
message(FATAL_ERROR "The osm2pgsql directory is empty.\
|
||||
Did you forget to check out Nominatim recursively?\
|
||||
\nTry updating submodules with: git submodule update --init")
|
||||
endif()
|
||||
add_subdirectory(osm2pgsql)
|
||||
#-----------------------------------------------------------------------------
|
||||
# osm2pgsql (imports/updates only)
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
unset(PostgreSQL_TYPE_INCLUDE_DIR CACHE)
|
||||
set(PostgreSQL_TYPE_INCLUDE_DIR "/usr/include/")
|
||||
find_package(PostgreSQL REQUIRED)
|
||||
include_directories(${PostgreSQL_INCLUDE_DIRS})
|
||||
link_directories(${PostgreSQL_LIBRARY_DIRS})
|
||||
|
||||
find_program(PYOSMIUM pyosmium-get-changes)
|
||||
if (NOT EXISTS "${PYOSMIUM}")
|
||||
set(PYOSMIUM_PATH "/nonexistent")
|
||||
message(WARNING "pyosmium-get-changes not found (required for updates)")
|
||||
else()
|
||||
set(PYOSMIUM_PATH "${PYOSMIUM}")
|
||||
message(STATUS "Using pyosmium-get-changes at ${PYOSMIUM_PATH}")
|
||||
if (BUILD_IMPORTER AND BUILD_OSM2PGSQL)
|
||||
if (NOT EXISTS "${CMAKE_SOURCE_DIR}/osm2pgsql/CMakeLists.txt")
|
||||
message(FATAL_ERROR "The osm2pgsql directory is empty.\
|
||||
Did you forget to check out Nominatim recursively?\
|
||||
\nTry updating submodules with: git submodule update --init")
|
||||
endif()
|
||||
set(BUILD_TESTS_SAVED "${BUILD_TESTS}")
|
||||
set(BUILD_TESTS off)
|
||||
set(WITH_LUA off CACHE BOOL "")
|
||||
add_subdirectory(osm2pgsql)
|
||||
set(BUILD_TESTS ${BUILD_TESTS_SAVED})
|
||||
endif()
|
||||
|
||||
|
||||
find_program(PG_CONFIG pg_config)
|
||||
execute_process(COMMAND ${PG_CONFIG} --pgxs
|
||||
OUTPUT_VARIABLE PGXS
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
#-----------------------------------------------------------------------------
|
||||
# python and pyosmium (imports/updates only)
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
if (NOT EXISTS "${PGXS}")
|
||||
message(FATAL_ERROR "Postgresql server package not found.")
|
||||
if (BUILD_IMPORTER)
|
||||
find_package(PythonInterp 3)
|
||||
|
||||
find_program(PYOSMIUM pyosmium-get-changes)
|
||||
if (NOT EXISTS "${PYOSMIUM}")
|
||||
set(PYOSMIUM_PATH "")
|
||||
message(WARNING "pyosmium-get-changes not found (required for updates)")
|
||||
else()
|
||||
set(PYOSMIUM_PATH "${PYOSMIUM}")
|
||||
message(STATUS "Using pyosmium-get-changes at ${PYOSMIUM_PATH}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
find_package(BZip2 REQUIRED)
|
||||
|
||||
find_package(LibXml2 REQUIRED)
|
||||
include_directories(${LIBXML2_INCLUDE_DIR})
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
#
|
||||
# Setup settings and paths
|
||||
#
|
||||
# PHP
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
set(CUSTOMFILES
|
||||
settings/phrase_settings.php
|
||||
website/deletable.php
|
||||
website/details.php
|
||||
website/hierarchy.php
|
||||
website/lookup.php
|
||||
website/polygons.php
|
||||
website/reverse.php
|
||||
website/search.php
|
||||
website/status.php
|
||||
utils/blocks.php
|
||||
utils/country_languages.php
|
||||
utils/imports.php
|
||||
utils/importWikipedia.php
|
||||
utils/query.php
|
||||
utils/server_compare.php
|
||||
utils/setup.php
|
||||
utils/specialphrases.php
|
||||
utils/update.php
|
||||
utils/warm.php
|
||||
# Setting PHP binary variable as to command line (prevailing) or auto detect
|
||||
|
||||
if (BUILD_API OR BUILD_IMPORTER)
|
||||
if (NOT PHP_BIN)
|
||||
find_program (PHP_BIN php)
|
||||
endif()
|
||||
# sanity check if PHP binary exists
|
||||
if (NOT EXISTS ${PHP_BIN})
|
||||
message(FATAL_ERROR "PHP binary not found. Install php or provide location with -DPHP_BIN=/path/php ")
|
||||
endif()
|
||||
message (STATUS "Using PHP binary " ${PHP_BIN})
|
||||
endif()
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# import scripts and utilities (importer only)
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
if (BUILD_IMPORTER)
|
||||
set(CUSTOMSCRIPTS
|
||||
utils/check_import_finished.php
|
||||
utils/country_languages.php
|
||||
utils/importWikipedia.php
|
||||
utils/export.php
|
||||
utils/query.php
|
||||
utils/setup.php
|
||||
utils/specialphrases.php
|
||||
utils/update.php
|
||||
utils/warm.php
|
||||
)
|
||||
|
||||
foreach (script_source ${CUSTOMSCRIPTS})
|
||||
configure_file(${PROJECT_SOURCE_DIR}/cmake/script.tmpl
|
||||
${PROJECT_BINARY_DIR}/${script_source})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# webserver scripts (API only)
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
if (BUILD_API)
|
||||
set(WEBSITESCRIPTS
|
||||
website/deletable.php
|
||||
website/details.php
|
||||
website/lookup.php
|
||||
website/polygons.php
|
||||
website/reverse.php
|
||||
website/search.php
|
||||
website/status.php
|
||||
)
|
||||
|
||||
foreach (cfile ${CUSTOMFILES})
|
||||
configure_file(${PROJECT_SOURCE_DIR}/${cfile} ${PROJECT_BINARY_DIR}/${cfile})
|
||||
endforeach()
|
||||
foreach (script_source ${WEBSITESCRIPTS})
|
||||
configure_file(${PROJECT_SOURCE_DIR}/cmake/website.tmpl
|
||||
${PROJECT_BINARY_DIR}/${script_source})
|
||||
endforeach()
|
||||
|
||||
configure_file(${PROJECT_SOURCE_DIR}/settings/defaults.php ${PROJECT_BINARY_DIR}/settings/settings.php)
|
||||
set(WEBPATHS css images js)
|
||||
|
||||
set(WEBPATHS css images js)
|
||||
foreach (wp ${WEBPATHS})
|
||||
execute_process(
|
||||
COMMAND ln -sf ${PROJECT_SOURCE_DIR}/website/${wp} ${PROJECT_BINARY_DIR}/website/
|
||||
)
|
||||
endforeach()
|
||||
|
||||
foreach (wp ${WEBPATHS})
|
||||
execute_process(
|
||||
COMMAND ln -sf ${PROJECT_SOURCE_DIR}/website/${wp} ${PROJECT_BINARY_DIR}/website/
|
||||
)
|
||||
endforeach()
|
||||
add_custom_target(serve
|
||||
php -S 127.0.0.1:8088
|
||||
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/website
|
||||
)
|
||||
|
||||
add_custom_target(serve-global
|
||||
php -S 0.0.0.0:8088
|
||||
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/website
|
||||
)
|
||||
endif()
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# default settings
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
configure_file(${PROJECT_SOURCE_DIR}/settings/defaults.php
|
||||
${PROJECT_BINARY_DIR}/settings/settings.php)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
#
|
||||
# Tests
|
||||
#
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
include(CTest)
|
||||
if (BUILD_TESTS)
|
||||
include(CTest)
|
||||
|
||||
set(TEST_BDD db osm2pgsql api)
|
||||
set(TEST_BDD db osm2pgsql api)
|
||||
|
||||
foreach (test ${TEST_BDD})
|
||||
add_test(NAME bdd_${test}
|
||||
COMMAND lettuce features/${test}
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests)
|
||||
set_tests_properties(bdd_${test}
|
||||
PROPERTIES ENVIRONMENT "NOMINATIM_DIR=${PROJECT_BINARY_DIR}")
|
||||
endforeach()
|
||||
foreach (test ${TEST_BDD})
|
||||
add_test(NAME bdd_${test}
|
||||
COMMAND behave ${test}
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test/bdd)
|
||||
set_tests_properties(bdd_${test}
|
||||
PROPERTIES ENVIRONMENT "NOMINATIM_DIR=${PROJECT_BINARY_DIR}")
|
||||
endforeach()
|
||||
|
||||
add_test(NAME php
|
||||
COMMAND phpunit ./
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests-php)
|
||||
add_test(NAME php
|
||||
COMMAND phpunit ./
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test/php)
|
||||
|
||||
add_test(NAME phpcs
|
||||
COMMAND phpcs --report-width=120 --colors lib website utils
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
||||
endif()
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Postgres module
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
add_subdirectory(module)
|
||||
add_subdirectory(nominatim)
|
||||
add_subdirectory(docs)
|
||||
if (BUILD_MODULE)
|
||||
add_subdirectory(module)
|
||||
endif()
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Documentation
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
if (BUILD_DOCS)
|
||||
add_subdirectory(docs)
|
||||
endif()
|
||||
|
||||
@@ -7,41 +7,9 @@ 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
|
||||
often are completely different from the maintainer's point of view.
|
||||
|
||||
### When Reporting Bad Search Results...
|
||||
|
||||
Please make sure to add the following information:
|
||||
|
||||
* the URL of the query that produces the bad result
|
||||
* the result you are getting
|
||||
* the expected result, preferably a link to the OSM object you want to find,
|
||||
otherwise an address that is as precise as possible
|
||||
|
||||
To get the link to the OSM object, you can try the following:
|
||||
|
||||
* go to https://openstreetmap.org
|
||||
* zoom to the area of the map where you expect the result and
|
||||
zoom in as much as possible
|
||||
* click on the question mark on the right side of the map,
|
||||
then with the queston cursor on the map where your object is located
|
||||
* find the object of interest in the list that appears on the left side
|
||||
* click on the object and report the URL back that the browser shows
|
||||
|
||||
### When Reporting Problems with your Installation...
|
||||
|
||||
Please add the following information to your issue:
|
||||
|
||||
* hardware configuration: RAM size, CPUs, kind and size of disks
|
||||
* Operating system (also mention if you are running on a cloud service)
|
||||
* Postgres and Postgis version
|
||||
* list of settings you changed in your Postgres configuration
|
||||
* Nominatim version (release version or,
|
||||
if you run from the git repo, the output of `git rev-parse HEAD`)
|
||||
* (if applicable) exact command line of the command that was causing the issue
|
||||
|
||||
|
||||
## Workflow for Pull Requests
|
||||
|
||||
We love to get pull reuqests from you. We operate the "Fork & Pull" model
|
||||
We love to get pull requests from you. We operate the "Fork & Pull" model
|
||||
explained at
|
||||
|
||||
https://help.github.com/articles/using-pull-requests
|
||||
@@ -65,7 +33,7 @@ that duplicate work can be avoided.
|
||||
## Coding style
|
||||
|
||||
Nominatim historically hasn't followed a particular coding style but we
|
||||
are in process of consolodating the style. The following rules apply:
|
||||
are in process of consolidating the style. The following rules apply:
|
||||
|
||||
* Python code uses the official Python style
|
||||
* indention
|
||||
@@ -78,11 +46,13 @@ are in process of consolodating the style. The following rules apply:
|
||||
* no spaces after opening and before closing bracket
|
||||
* leave out space between a function name 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 can be tested with:
|
||||
|
||||
```
|
||||
phpcs --report-width=120 --colors */**.php
|
||||
phpcs --report-width=120 --colors .
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
175
ChangeLog
175
ChangeLog
@@ -1,3 +1,178 @@
|
||||
3.6.0
|
||||
|
||||
* add full support for searching by and displaying of addr:* tags
|
||||
* improve address output for large-area objects
|
||||
* better use of country names from OSM data for search and display
|
||||
* better debug output for reverse call
|
||||
* add support for addr:place links without an place equivalent in OSM
|
||||
* improve finding postcodes with normalisation artefacts
|
||||
* batch object to index for rank 30, avoiding a wrap-around of transaction
|
||||
IDs in PostgreSQL
|
||||
* introduce dynamic address rank computation for administrative boundaries
|
||||
depending on linked objects and their place in the admin level hierarchy
|
||||
* add country-specific address ranking for Indonesia, Russia, Belgium and
|
||||
the Netherlands (thanks @hendrikmoree)
|
||||
* make sure wikidata/wikipedia tags are imported for all styles
|
||||
* make POIs searchable by name and housenumber (thanks @joy-yyd)
|
||||
* reverse geocoding now ignores places without an address rank (rivers etc.)
|
||||
* installation of a webserver is no longer mandatory, for development
|
||||
use the php internal webserver via 'make serve
|
||||
* reduce the influence of place nodes in addresses
|
||||
* drop support for the unspecific is_in tag
|
||||
* various minor tweaks to supplied styles
|
||||
* move HTML web frontend into its own project
|
||||
* move scripts for processing external data sources into separate directories
|
||||
* introduce separate configuration for website (thanks @krahulreddy)
|
||||
* update documentation, in particular, clean up development docs
|
||||
* update osm2pgsql to 1.4.0
|
||||
|
||||
3.5.2
|
||||
|
||||
* ensure that wikipedia tags are imported for all styles
|
||||
* reinstate verbosity for indexing during updates
|
||||
* make house number reappear in display name on named POIs
|
||||
* introduce batch processing in indexer to avoid transaction ID overrun
|
||||
* increase splitting for large geometries to improve indexing speed
|
||||
* remove deprecated get_magic_quotes_gpc() function
|
||||
* make sure that all postcodes have an entry in word and are thus searchable
|
||||
* remove use of ST_Covers in conjunction woth ST_Intersects,
|
||||
causes bad query planning and slow updates in Postgis3
|
||||
* update osm2pgsql
|
||||
|
||||
3.5.1
|
||||
|
||||
* disable jit and parallel processing in PostgreSQL for osm2pgsql
|
||||
* update libosmium to 2.15.6 (fixes an issue with processing hanging
|
||||
on large multipolygons)
|
||||
|
||||
3.5.0
|
||||
|
||||
* structured select on HTML search page
|
||||
* new PHP Nominatim\Shell class to wrap shell escaping
|
||||
* remove polygon parameter from all API calls
|
||||
* improve handling of postcode areas
|
||||
* reorganise place linking algorithm, now using wikidata tag as well
|
||||
* remove linkees from search_name and larger_area tables
|
||||
* introduce country-specific address ranks
|
||||
* reorganise rank address computation
|
||||
* cleanup of partition function
|
||||
* improve parenting for large POIs
|
||||
* add support for Postgresql 12 and Postgis 3
|
||||
* add earlier cleanup when --drop is given, to reduce memory usage
|
||||
* remove use of place_id in URLs
|
||||
* replace C nominatim indexer with a simpler Python implementation
|
||||
* split up the huge sql/functions.sql file
|
||||
* move osm2pgsql tests to osm2pgsql
|
||||
* add new extratags style which imports all tags from OSM
|
||||
* add new script for checking the import after completion
|
||||
* update osm2pgsql, reducing memory usage
|
||||
* use new wikipedia importance and add processing of wikidata tags
|
||||
* add search form for details page
|
||||
* use ExtraDataPath for country_grid table
|
||||
* remove short_name from list of names to be displayed
|
||||
* split up CMakeFile, so that all parts can be built separately
|
||||
* update installation instructions for CentOS and Ubuntu
|
||||
* add script for importing/updating multiple country extracts
|
||||
* various documentation improvements
|
||||
|
||||
3.4.2
|
||||
|
||||
* fix security bug in /details endpoint where user input was not
|
||||
properly sanitized
|
||||
|
||||
3.4.1
|
||||
|
||||
* update osm2pgsql to fix hans during updates and lost address numbers
|
||||
during updates
|
||||
|
||||
3.4.0
|
||||
|
||||
* increase required version for PostgreSQL(9.3), PostGIS(2.2) and PHP(7.0)
|
||||
* better error reporting for out-of-memory errors
|
||||
* exclude postcode ranges separated by colon from centre point calculation
|
||||
* update osm2pgsql, better handling of imports without flatnode file
|
||||
* switch to more efficient algorithm for word set computation
|
||||
* use only boundries for country and state parts of addresses
|
||||
* improve updates of addresses with housenumbers and interpolations
|
||||
* remove country from place_addressline table and use country_code instead
|
||||
* optimise indexes on search_name partition tables
|
||||
* improve searching of attached streets for large objects like airports
|
||||
* drop support for python 2
|
||||
* new scripts for importing Wikidata for importance
|
||||
* create and drop indexes concurrently to not clash with auto vacuum
|
||||
* various documentation improvements
|
||||
|
||||
|
||||
3.3.0
|
||||
|
||||
* zoom 17 in reverse now zooms in on minor streets
|
||||
* fix use of postcode relations in address
|
||||
* support for housenumber 0 on interpolations
|
||||
* replace database abstraction DB with PDO and switch to using exceptions
|
||||
* exclude line features at rank 30 from reverse geocoding
|
||||
* remove self-reference and country from place_addressline
|
||||
* make json output more readable (less escaping)
|
||||
* update conversion scripts for postcodes
|
||||
* scripts in utils/ are no longer executable (always use scripts in build dir)
|
||||
* remove Natural Earth country fallback (OSM is complete enough)
|
||||
* make rank assignments configurable
|
||||
* allow accept languages with underscore
|
||||
* new reverse-only import mode (without search index table)
|
||||
* rely on boundaries only for states and countries
|
||||
* update osm2pgsql, now using a configurable style
|
||||
* provide multiple import styles
|
||||
* improve search when house number and postcodes are dropped
|
||||
* overhaul of setup code
|
||||
* add support for PHPUnit 6
|
||||
* update test database
|
||||
* various documentation improvements
|
||||
|
||||
3.2.0
|
||||
|
||||
* complete rewrite of reverse search algorithm
|
||||
* add new geojson and geocodejson output formats
|
||||
* add simple export script to exprot addresses to CSV
|
||||
* remove is_in terms from address computation
|
||||
* remove unused search_name_country tables
|
||||
* various smaller fixes to query parsing
|
||||
* convert Tokens and token types to class types
|
||||
* correctly handle update when boundary object type is changed
|
||||
* improve debug output for /search endpoint
|
||||
* update to latest osm2pgsql and leaflet.js
|
||||
* overhaul of /details endpoint:
|
||||
* new class parameter when using osmtype/osmid parameters
|
||||
* permalink to instance-independent osmtype/osmid parameter format
|
||||
* new json output format
|
||||
* update CentOS vagrant machine to use SELinux
|
||||
* add vagrant scripts for Ubuntu 18.04
|
||||
* fix build process for BSD
|
||||
* enable running the database on a different host than the setup scripts
|
||||
* allow to configure use of custom PHP binaries (PHP_BIN)
|
||||
* extensive coding style improvements to PHP code
|
||||
* more PHP unit tests for new classes
|
||||
* increase coverage for API tests
|
||||
* add documentation for API
|
||||
|
||||
3.1.0
|
||||
|
||||
* rework postcode handling and introduce location_postcode table
|
||||
* make setup less verbose and print a summary in the end
|
||||
* setup: error out when web site user does not exist
|
||||
* add more API tests to complete code coverage
|
||||
* reinstate key-value amenity search (special term [key=value])
|
||||
* fix detection of coordinates in query
|
||||
* various smaller tweaks to ranking of search interpretations
|
||||
* complete overhaul of PHP frontend code using OOP
|
||||
* add address rank to details page
|
||||
* update Tiger scripts for 2017 data and clean up unused code
|
||||
* various bug fixes and improvements to UI
|
||||
* improve reverse geocoding performance close to coasts
|
||||
* more PHP style cleanup (quoting)
|
||||
* allow unnamed road in reverse geocoding to avoid too far off results
|
||||
* add function to recalculate counts for full-word search term
|
||||
* add function to check if new updates are available
|
||||
* update documentation and switch to mkdocs for generating HTML
|
||||
|
||||
3.0.1
|
||||
|
||||
* fix bug in geometry building algorithm in osm2pgsql
|
||||
|
||||
58
README.md
58
README.md
@@ -1,4 +1,4 @@
|
||||
[](https://travis-ci.org/openstreetmap/Nominatim)
|
||||
[](https://github.com/osm-search/Nominatim/actions?query=workflow%3A%22CI+Tests%22)
|
||||
|
||||
Nominatim
|
||||
=========
|
||||
@@ -6,23 +6,35 @@ Nominatim
|
||||
Nominatim (from the Latin, 'by name') is a tool to search OpenStreetMap data
|
||||
by name and address (geocoding) and to generate synthetic addresses of
|
||||
OSM points (reverse geocoding). An instance with up-to-date data can be found
|
||||
at http://nominatim.openstreetmap.org. Nominatim is also used as one of the
|
||||
sources for the Search box on the OpenStreetMap home page and powers the search
|
||||
on the MapQuest Open Initiative websites.
|
||||
at https://nominatim.openstreetmap.org. Nominatim is also used as one of the
|
||||
sources for the Search box on the OpenStreetMap home page.
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
More information about Nominatim, including usage and installation instructions,
|
||||
can be found in the docs/ subdirectory and in the OSM wiki at:
|
||||
|
||||
http://wiki.openstreetmap.org/wiki/Nominatim
|
||||
The documentation of the latest development version is in the
|
||||
`docs/` subdirectory. A HTML version can be found at
|
||||
https://nominatim.org/release-docs/develop/ .
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
There are detailed installation instructions in the /docs directory.
|
||||
Here is a quick summary of the necessary steps.
|
||||
**Nominatim is a complex piece of software and runs in a complex environment.
|
||||
Installing and running Nominatim is something for experienced system
|
||||
administrators only who can do some trouble-shooting themselves. We are sorry,
|
||||
but we can not provide installation support. We are all doing this in our free
|
||||
time and there is just so much of that time to go around. Do not open issues in
|
||||
our bug tracker if you need help. You can ask questions on the mailing list
|
||||
(see below) or on [help.openstreetmap.org](https://help.openstreetmap.org/).**
|
||||
|
||||
The latest stable release can be downloaded from https://nominatim.org.
|
||||
There you can also find [installation instructions for the release](https://nominatim.org/release-docs/latest/admin/Installation), as well as an extensive [Troubleshooting/FAQ section](https://nominatim.org/release-docs/latest/admin/Faq/).
|
||||
|
||||
Detailed installation instructions for the development version can be
|
||||
found at [nominatim.org](https://nominatim.org/release-docs/develop/admin/Installation)
|
||||
as well.
|
||||
|
||||
A quick summary of the necessary steps:
|
||||
|
||||
1. Compile Nominatim:
|
||||
|
||||
@@ -31,31 +43,29 @@ Here is a quick summary of the necessary steps.
|
||||
cmake ..
|
||||
make
|
||||
|
||||
For more detailed installation instructions see [docs/Installation.md](docs/Installation.md).
|
||||
There are also step-by-step instructions for
|
||||
[Ubuntu 16.04](docs/Install-on-Ubuntu-16.md) and
|
||||
[CentOS 7.2](docs/Install-on-Centos-7.md).
|
||||
|
||||
2. Get OSM data and import:
|
||||
|
||||
./build/utils/setup.php --osm-file <your planet file> --all
|
||||
|
||||
Details can be found in [docs/Import_and_update.md](docs/Import-and-Update.md)
|
||||
|
||||
3. Point your webserver to the ./build/website directory.
|
||||
|
||||
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
The source code is available under a GPLv2 license.
|
||||
|
||||
Contact and Bug reports
|
||||
======================
|
||||
|
||||
For questions you can join the geocoding mailinglist, see
|
||||
http://lists.openstreetmap.org/listinfo/geocoding
|
||||
Contributing
|
||||
============
|
||||
|
||||
Bugs may be reported on the github project site:
|
||||
https://github.com/openstreetmap/Nominatim
|
||||
Contributions are welcome. For details see [contribution guide](CONTRIBUTING.md).
|
||||
|
||||
Both bug reports and pull requests are welcome.
|
||||
|
||||
|
||||
Mailing list
|
||||
============
|
||||
|
||||
For questions you can join the geocoding mailing list, see
|
||||
https://lists.openstreetmap.org/listinfo/geocoding
|
||||
|
||||
79
VAGRANT.md
79
VAGRANT.md
@@ -1,11 +1,11 @@
|
||||
# Install Nominatim in a virtual machine for development and testing
|
||||
|
||||
This document describes how you can install Nominatim inside a Ubuntu 14
|
||||
This document describes how you can install Nominatim inside a Ubuntu 16
|
||||
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
|
||||
without affecting the rest of your system.
|
||||
|
||||
The installation can run largely unsupervised. You should expect 1-2h from
|
||||
The installation can run largely unsupervised. You should expect 1h from
|
||||
start to finish depending on how fast your computer and download speed
|
||||
is.
|
||||
|
||||
@@ -19,7 +19,7 @@ is.
|
||||
|
||||
git clone --recursive https://github.com/openstreetmap/Nominatim.git
|
||||
|
||||
If you haven't used `--recursive`, then you can load the submodules using
|
||||
If you forgot `--recursive`, it you can later load the submodules using
|
||||
|
||||
git submodule init
|
||||
git submodule update
|
||||
@@ -37,18 +37,14 @@ is.
|
||||
vagrant ssh ubuntu
|
||||
|
||||
3. Import a small country (Monaco)
|
||||
|
||||
You need to give the virtual machine more memory (2GB) for an import,
|
||||
see `Vagrantfile`. Otherwise 1GB is enough.
|
||||
|
||||
See the FAQ how to skip this step and point Nominatim to an existing database.
|
||||
|
||||
```
|
||||
# inside the virtual machine:
|
||||
mkdir data
|
||||
cd build
|
||||
wget --no-verbose --output-document=../data/monaco.osm.pbf http://download.geofabrik.de/europe/monaco-latest.osm.pbf
|
||||
./utils/setup.php --osm-file ../data/monaco.osm.pbf --osm2pgsql-cache 1000 --all 2>&1 | tee monaco.$$.log
|
||||
wget --no-verbose --output-document=/tmp/monaco.osm.pbf http://download.geofabrik.de/europe/monaco-latest.osm.pbf
|
||||
./utils/setup.php --osm-file /tmp/monaco.osm.pbf --osm2pgsql-cache 1000 --all 2>&1 | tee monaco.$$.log
|
||||
```
|
||||
|
||||
To repeat an import you'd need to delete the database first
|
||||
@@ -65,45 +61,62 @@ see Nominatim in action on [locahost:8089](http://localhost:8089/nominatim/).
|
||||
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.
|
||||
|
||||
Note that the webserver uses files from the /build directory. If you change
|
||||
files in Nominatim/website or Nominatim/utils for example you first need to
|
||||
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 (preferred) or set
|
||||
`@define('CONST_Debug', true);` in `settings/local.php`.
|
||||
|
||||
In the Python BDD test you can use `logger.info()` for temporary debug
|
||||
statements.
|
||||
|
||||
|
||||
|
||||
## Running unit tests
|
||||
|
||||
cd ~/Nominatim/tests/php
|
||||
phpunit ./
|
||||
|
||||
|
||||
## Running PHP code style tests
|
||||
|
||||
cd ~/Nominatim
|
||||
phpcs --colors .
|
||||
|
||||
|
||||
## Running functional tests
|
||||
|
||||
Tests in `/features/db` and `/features/osm2pgsql` have to pass 100%. Other
|
||||
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/tests
|
||||
NOMINATIM_SERVER=http://localhost:8089/nominatim lettuce features
|
||||
cd ~/Nominatim/test/bdd
|
||||
behave -DBUILDDIR=/home/vagrant/build/ db osm2pgsql
|
||||
|
||||
To run a single file
|
||||
|
||||
NOMINATIM_SERVER=http://localhost:8089/nominatim lettuce features/api/reverse.feature
|
||||
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 tests you can add tags just before the `Scenario line`, e.g.
|
||||
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
|
||||
|
||||
NOMINATIM_SERVER=http://localhost:8089/nominatim lettuce -t bug-34
|
||||
|
||||
|
||||
## Running unit tests
|
||||
|
||||
cd ~/Nominatim/tests-php
|
||||
phpunit ./
|
||||
behave -DBUILDDIR=/home/vagrant/build/ --tags @bug-34
|
||||
|
||||
|
||||
|
||||
@@ -128,27 +141,28 @@ No. Long running Nominatim installations will differ once new import features (o
|
||||
bug fixes) get added since those usually only get applied to new/changed data.
|
||||
|
||||
Also this document skips the optional Wikipedia data import which affects ranking
|
||||
of search results. See [Nominatim installation](http://wiki.openstreetmap.org/wiki/Nominatim/Installation) for details.
|
||||
of search results. See [Nominatim installation](https://nominatim.org/release-docs/latest/admin/Installation) for details.
|
||||
|
||||
##### Why Ubuntu and CentOS, can I test CentOS/CoreOS/FreeBSD?
|
||||
##### Why Ubuntu? Can I test CentOS/Fedora/CoreOS/FreeBSD?
|
||||
|
||||
There is a Vagrant script for CentOS available. Simply start your box
|
||||
with `vagrant up centos` and then log in with `vagrant ssh centos`.
|
||||
In general Nominatim will also run in the other environments. The installation steps
|
||||
There is a Vagrant script for CentOS available, but the Nominatim directory
|
||||
isn't symlinked/mounted to the host which makes development trickier. We used
|
||||
it mainly for debugging installation with SELinux.
|
||||
|
||||
In general Nominatim will run in the other environments. The installation steps
|
||||
are slightly different, e.g. the name of the package manager, Apache2 package
|
||||
name, location of files. We chose Ubuntu because that is closest to the
|
||||
nominatim.openstreetmap.org production environment.
|
||||
|
||||
You can configure/download other Vagrant boxes from [vagrantbox.es](http://www.vagrantbox.es/).
|
||||
|
||||
You can configure/download other Vagrant boxes from [https://app.vagrantup.com/boxes/search](https://app.vagrantup.com/boxes/search).
|
||||
|
||||
##### How can I connect to an existing database?
|
||||
|
||||
Let's say you have a Postgres database named `nominatim_it` on server `your-server.com` and port `5432`. The Postgres username is `postgres`. You can edit `settings/local.php` and point Nominatim to it.
|
||||
|
||||
pgsql://postgres@your-server.com:5432/nominatim_it
|
||||
pgsql:host=your-server.com;port=5432;user=postgres;dbname=nominatim_it
|
||||
|
||||
No data import necessary, no restarting necessary.
|
||||
No data import or restarting necessary.
|
||||
|
||||
If the Postgres installation is behind a firewall, you can try
|
||||
|
||||
@@ -157,14 +171,15 @@ If the Postgres installation is behind a firewall, you can try
|
||||
inside the virtual machine. It will map the port to `localhost:9999` and then
|
||||
you edit `settings/local.php` with
|
||||
|
||||
pgsql://postgres@localhost:9999/nominatim_it
|
||||
@define('CONST_Database_DSN', 'pgsql:host=localhost;port=9999;user=postgres;dbname=nominatim_it');
|
||||
|
||||
To access postgres directly remember to specify the hostname, e.g. `psql --host localhost --port 9999 nominatim_it`
|
||||
|
||||
|
||||
##### My computer is slow and the import takes too long. Can I start the virtual machine "in the cloud"?
|
||||
|
||||
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).
|
||||
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).
|
||||
|
||||
|
||||
|
||||
|
||||
103
Vagrantfile
vendored
103
Vagrantfile
vendored
@@ -4,36 +4,92 @@
|
||||
Vagrant.configure("2") do |config|
|
||||
# Apache webserver
|
||||
config.vm.network "forwarded_port", guest: 80, host: 8089
|
||||
config.vm.network "forwarded_port", guest: 8088, host: 8088
|
||||
|
||||
# If true, then any SSH connections made will enable agent forwarding.
|
||||
config.ssh.forward_agent = true
|
||||
|
||||
# Never sync the current directory to /vagrant.
|
||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
|
||||
checkout = "yes"
|
||||
if ENV['CHECKOUT'] != 'y' then
|
||||
config.vm.synced_folder ".", "/home/vagrant/Nominatim"
|
||||
checkout = "no"
|
||||
checkout = "no"
|
||||
end
|
||||
|
||||
config.vm.provider "virtualbox" do |vb, override|
|
||||
vb.gui = false
|
||||
vb.memory = 2048
|
||||
vb.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate//vagrant","0"]
|
||||
if ENV['CHECKOUT'] != 'y' then
|
||||
override.vm.synced_folder ".", "/home/vagrant/Nominatim"
|
||||
end
|
||||
end
|
||||
|
||||
config.vm.provider "libvirt" do |lv, override|
|
||||
lv.memory = 2048
|
||||
lv.nested = true
|
||||
if ENV['CHECKOUT'] != 'y' then
|
||||
override.vm.synced_folder ".", "/home/vagrant/Nominatim", type: 'nfs'
|
||||
end
|
||||
end
|
||||
|
||||
config.vm.define "ubuntu", primary: true do |sub|
|
||||
sub.vm.box = "bento/ubuntu-16.04"
|
||||
sub.vm.box = "generic/ubuntu2004"
|
||||
sub.vm.provision :shell do |s|
|
||||
s.path = "vagrant/Install-on-Ubuntu-16.sh"
|
||||
s.path = "vagrant/Install-on-Ubuntu-20.sh"
|
||||
s.privileged = false
|
||||
s.args = [checkout]
|
||||
end
|
||||
end
|
||||
|
||||
config.vm.define "travis" do |sub|
|
||||
sub.vm.box = "bento/ubuntu-14.04"
|
||||
config.vm.define "ubuntu-apache" do |sub|
|
||||
sub.vm.box = "generic/ubuntu2004"
|
||||
sub.vm.provision :shell do |s|
|
||||
s.path = "vagrant/install-on-travis-ci.sh"
|
||||
s.path = "vagrant/Install-on-Ubuntu-20.sh"
|
||||
s.privileged = false
|
||||
s.args = [checkout, "install-apache"]
|
||||
end
|
||||
end
|
||||
|
||||
config.vm.define "ubuntu-nginx" do |sub|
|
||||
sub.vm.box = "generic/ubuntu2004"
|
||||
sub.vm.provision :shell do |s|
|
||||
s.path = "vagrant/Install-on-Ubuntu-20.sh"
|
||||
s.privileged = false
|
||||
s.args = [checkout, "install-nginx"]
|
||||
end
|
||||
end
|
||||
|
||||
config.vm.define "ubuntu18" do |sub|
|
||||
sub.vm.box = "generic/ubuntu1804"
|
||||
sub.vm.provision :shell do |s|
|
||||
s.path = "vagrant/Install-on-Ubuntu-18.sh"
|
||||
s.privileged = false
|
||||
s.args = [checkout]
|
||||
end
|
||||
end
|
||||
|
||||
config.vm.define "centos" do |sub|
|
||||
sub.vm.box = "bento/centos-7.2"
|
||||
config.vm.define "ubuntu18-apache" do |sub|
|
||||
sub.vm.box = "generic/ubuntu1804"
|
||||
sub.vm.provision :shell do |s|
|
||||
s.path = "vagrant/Install-on-Ubuntu-18.sh"
|
||||
s.privileged = false
|
||||
s.args = [checkout, "install-apache"]
|
||||
end
|
||||
end
|
||||
|
||||
config.vm.define "ubuntu18-nginx" do |sub|
|
||||
sub.vm.box = "generic/ubuntu1804"
|
||||
sub.vm.provision :shell do |s|
|
||||
s.path = "vagrant/Install-on-Ubuntu-18.sh"
|
||||
s.privileged = false
|
||||
s.args = [checkout, "install-nginx"]
|
||||
end
|
||||
end
|
||||
|
||||
config.vm.define "centos7" do |sub|
|
||||
sub.vm.box = "centos/7"
|
||||
sub.vm.provision :shell do |s|
|
||||
s.path = "vagrant/Install-on-Centos-7.sh"
|
||||
s.privileged = false
|
||||
@@ -41,29 +97,14 @@ Vagrant.configure("2") do |config|
|
||||
end
|
||||
end
|
||||
|
||||
# configure shared package cache if possible
|
||||
#if Vagrant.has_plugin?("vagrant-cachier")
|
||||
# config.cache.enable :apt
|
||||
# config.cache.scope = :box
|
||||
#end
|
||||
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.gui = false
|
||||
vb.customize ["modifyvm", :id, "--memory", "2048"]
|
||||
config.vm.define "centos" do |sub|
|
||||
sub.vm.box = "generic/centos8"
|
||||
sub.vm.provision :shell do |s|
|
||||
s.path = "vagrant/Install-on-Centos-8.sh"
|
||||
s.privileged = false
|
||||
s.args = [checkout]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# config.vm.provider :digital_ocean do |provider, override|
|
||||
# override.ssh.private_key_path = '~/.ssh/id_rsa'
|
||||
# override.vm.box = 'digital_ocean'
|
||||
# override.vm.box_url = "https://github.com/smdahlen/vagrant-digitalocean/raw/master/box/digital_ocean.box"
|
||||
|
||||
# provider.token = ''
|
||||
# # provider.token = 'YOUR TOKEN'
|
||||
# provider.image = 'ubuntu-14-04-x64'
|
||||
# provider.region = 'nyc2'
|
||||
# provider.size = '512mb'
|
||||
# end
|
||||
|
||||
end
|
||||
|
||||
4
cmake/script.tmpl
Executable file
4
cmake/script.tmpl
Executable file
@@ -0,0 +1,4 @@
|
||||
#!@PHP_BIN@ -Cq
|
||||
<?php
|
||||
require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
|
||||
require_once(CONST_BasePath.'/@script_source@');
|
||||
5
cmake/website.tmpl
Executable file
5
cmake/website.tmpl
Executable file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
@define('CONST_Debug', (isset($_GET['debug']) && $_GET['debug']));
|
||||
require_once(dirname(dirname(__FILE__)).'/settings/settings-frontend.php');
|
||||
|
||||
require_once(CONST_BasePath.'/@script_source@');
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
32429
data/us_postcode.sql
32429
data/us_postcode.sql
File diff suppressed because it is too large
Load Diff
16
data/us_postcode_table.sql
Normal file
16
data/us_postcode_table.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
SET statement_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET check_function_bodies = false;
|
||||
SET client_min_messages = warning;
|
||||
|
||||
SET search_path = public, pg_catalog;
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_with_oids = false;
|
||||
|
||||
CREATE TABLE us_postcode (
|
||||
postcode text,
|
||||
x double precision,
|
||||
y double precision
|
||||
);
|
||||
@@ -18,12 +18,13 @@ SET default_with_oids = false;
|
||||
-- Name: word_frequencies; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS word_frequencies;
|
||||
|
||||
CREATE TABLE word_frequencies (
|
||||
word_token text,
|
||||
count bigint
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: word_frequencies; Type: TABLE DATA; Schema: public; Owner: -
|
||||
--
|
||||
@@ -29787,7 +29788,6 @@ st 5557484
|
||||
-- prefill word table
|
||||
|
||||
select count(make_keywords(v)) from (select distinct svals(name) as v from place) as w where v is not null;
|
||||
select count(make_keywords(v)) from (select distinct address->'postcode' as v from place where address ? 'postcode') as w where v is not null;
|
||||
select count(getorcreate_housenumber_id(make_standard_name(v))) from (select distinct address->'housenumber' as v from place where address ? 'housenumber') as w;
|
||||
|
||||
-- copy the word frequencies
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
# Auto-generated vagrant install documentation
|
||||
|
||||
set (INSTALLDOCFILES
|
||||
Install-on-Centos-7
|
||||
Install-on-Ubuntu-16
|
||||
|
||||
# build the actual documentation
|
||||
|
||||
configure_file(mkdocs.yml ../mkdocs.yml)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/appendix)
|
||||
|
||||
set (DOC_SOURCES
|
||||
admin
|
||||
develop
|
||||
api
|
||||
index.md
|
||||
extra.css
|
||||
styles.css
|
||||
)
|
||||
|
||||
foreach (df ${INSTALLDOCFILES})
|
||||
ADD_CUSTOM_COMMAND( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${df}.md
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/${df}.sh ${CMAKE_CURRENT_BINARY_DIR}/${df}.md
|
||||
MAIN_DEPENDENCY ${PROJECT_SOURCE_DIR}/vagrant/${df}.sh
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh
|
||||
COMMENT "Creating markdown docs from vagrant/${df}.sh"
|
||||
)
|
||||
|
||||
ADD_CUSTOM_TARGET( md_install_${df} ALL
|
||||
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${df}.md
|
||||
)
|
||||
endforeach()
|
||||
|
||||
# Copied static documentation
|
||||
|
||||
set (GENERALDOCFILES
|
||||
Installation.md
|
||||
Import-and-Update.md
|
||||
Faq.md
|
||||
foreach (src ${DOC_SOURCES})
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/${src} ${CMAKE_CURRENT_BINARY_DIR}/${src}
|
||||
)
|
||||
|
||||
foreach (df ${GENERALDOCFILES})
|
||||
CONFIGURE_FILE(${df} ${df})
|
||||
endforeach()
|
||||
|
||||
ADD_CUSTOM_TARGET(doc
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Centos-7.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Centos-7.md
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Centos-8.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Centos-8.md
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Ubuntu-18.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Ubuntu-18.md
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Ubuntu-20.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Ubuntu-20.md
|
||||
COMMAND mkdocs build -d ${CMAKE_CURRENT_BINARY_DIR}/../site-html -f ${CMAKE_CURRENT_BINARY_DIR}/../mkdocs.yml
|
||||
)
|
||||
|
||||
|
||||
|
||||
127
docs/Faq.md
127
docs/Faq.md
@@ -1,127 +0,0 @@
|
||||
Frequently Asked Questions
|
||||
==========================
|
||||
|
||||
Running Your Own Instance
|
||||
-------------------------
|
||||
|
||||
### Can I import only a few countries and also keep them up to date?
|
||||
|
||||
You should use the extracts and updates from http://download.geofabrik.de.
|
||||
For the intial import, download the countries you need and merge them.
|
||||
See [OSM Help](https://help.openstreetmap.org/questions/48843/merging-two-or-more-geographical-areas-to-import-two-or-more-osm-files-in-nominatim)
|
||||
for examples how to do that. Use the resulting single osm file when
|
||||
running `setup.php`.
|
||||
|
||||
For updates you need to download the change files for each country
|
||||
once per day and apply them **separately** using
|
||||
|
||||
./utils/update.php --import-diff <filename> --index
|
||||
|
||||
See [this issue](https://github.com/openstreetmap/Nominatim/issues/60#issuecomment-18679446)
|
||||
for a script that runs the updates using osmosis.
|
||||
|
||||
### My website shows: `XML Parsing Error: XML or text declaration not at start of entity Location</code>.`
|
||||
|
||||
Make sure there are no spaces at the beginning of your `settings/local.php` file.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
### I accidentally killed the import process after it has been running for many hours. Can it be resumed?
|
||||
|
||||
It is possible if the import already got to the indexing stage.
|
||||
Check the last line of output that was logged before the process
|
||||
was killed. If it looks like this:
|
||||
|
||||
Done 844 in 13 @ 64.923080 per second - Rank 26 ETA (seconds): 7.886255
|
||||
|
||||
then you can resume with the following command:
|
||||
|
||||
./utils/setup.php --index --create-search-indices --create-country-names
|
||||
|
||||
If the reported rank is 26 or higher, you can also safely add `-index-noanalyse`.
|
||||
|
||||
|
||||
### When running the setup.php script I get a warning:
|
||||
`PHP Warning: file_get_contents(): open_basedir restriction in effect.`
|
||||
|
||||
You need to adjust the [open_basedir](http://www.php.net/manual/en/ini.core.php#ini.open-basedir) setting
|
||||
in your PHP configuration (php.ini file). By default this setting may look like this:
|
||||
|
||||
open_basedir = /srv/http/:/home/:/tmp/:/usr/share/pear/
|
||||
|
||||
Either add reported directories to the list or disable this setting temporarily by
|
||||
dding ";" at the beginning of the line. Don't forget to enable this setting again
|
||||
once you are done with the PHP command line operations.
|
||||
|
||||
|
||||
### The Apache log contains lots of PHP warnings like this:
|
||||
`PHP Warning: date_default_timezone_set() function.`
|
||||
|
||||
You should set the default time zone as instructed in the warning in
|
||||
your `php.ini` file. Find the entry about timezone and set it to
|
||||
something like this:
|
||||
|
||||
; Defines the default timezone used by the date functions
|
||||
; http://php.net/date.timezone
|
||||
date.timezone = 'America/Denver'
|
||||
|
||||
Or
|
||||
|
||||
echo "date.timezone = 'America/Denver'" > /etc/php.d/timezone.ini
|
||||
|
||||
|
||||
### When running the import I get a version mismatch:
|
||||
`COPY_END for place failed: ERROR: incompatible library "/opt/Nominatim/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-9.1` on Ubuntu)
|
||||
and recompile (`cmake .. && make`).
|
||||
|
||||
|
||||
### I see the error: `function transliteration(text) does not exist`
|
||||
|
||||
Reinstall the nominatim functions with `setup.php --create--functions`
|
||||
and check for any errors, e.g. a missing `nominatim.so` file.
|
||||
|
||||
|
||||
### The website shows: `Could not get word tokens`
|
||||
|
||||
The server cannot access your database. Add `&debug=1` to your URL
|
||||
to get the full error message.
|
||||
|
||||
|
||||
### On CentOS the website shows `could not connect to server: No such file or directory`
|
||||
|
||||
On CentOS v7 the PostgreSQL server is started with `systemd`.
|
||||
Check if `/usr/lib/systemd/system/httpd.service` contains a line `PrivateTmp=true`.
|
||||
If so then Apache cannot see the `/tmp/.s.PGSQL.5432` file. It's a good security feature,
|
||||
so use the [[#PostgreSQL_UNIX_Socket_Location_on_CentOS|preferred solution]]
|
||||
|
||||
However, you can solve this the quick and dirty way by commenting out that line and then run
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl restart httpd
|
||||
|
||||
|
||||
### Setup.php fails with the message: `DB Error: extension not found`
|
||||
|
||||
Make sure you have the Postgres extensions hstore and postgis installed.
|
||||
See the installation instruction for a full list of required packages.
|
||||
|
||||
### When running the setup.php script I get a error:
|
||||
`Cannot redeclare getDB() (previously declared in /your/path/Nominatim/lib/db.php:4)`
|
||||
|
||||
The message is a bit misleading as PHP needs to load the file `DB.php` and
|
||||
instead re-loads Nominatim's `db.php`. To solve this make sure you
|
||||
have the [http://pear.php.net/package/DB/ Pear module 'DB'] installed.
|
||||
|
||||
sudo pear install DB
|
||||
|
||||
### I forgot to delete the flatnodes file before starting an import
|
||||
|
||||
That's fine. For each import the flatnodes file get overwritten.
|
||||
See https://help.openstreetmap.org/questions/52419/nominatim-flatnode-storage
|
||||
for more information.
|
||||
@@ -1,183 +0,0 @@
|
||||
Importing a new database
|
||||
========================
|
||||
|
||||
The following instructions explain how to create a Nominatim database
|
||||
from an OSM planet file and how to keep the database up to date. It
|
||||
is assumed that you have already successfully installed the Nominatim
|
||||
software itself, if not return to the [installation page](Installation.md).
|
||||
|
||||
Configuration setup in settings/local.php
|
||||
-----------------------------------------
|
||||
|
||||
There are lots of configuration settings you can tweak. Have a look
|
||||
at `settings/settings.php` for a full list. Most should have a sensible default.
|
||||
If you plan to import a large dataset (e.g. Europe, North America, planet),
|
||||
you should also enable flatnode storage of node locations. With this
|
||||
setting enabled, node coordinates are stored in a simple file instead
|
||||
of the database. This will save you import time and disk storage.
|
||||
Add to your `settings/local.php`:
|
||||
|
||||
@define('CONST_Osm2pgsql_Flatnode_File', '/path/to/flatnode.file');
|
||||
|
||||
Replace the second part with a suitable path on your system and make sure
|
||||
the directory exists. There should be at least 35GB of free space.
|
||||
|
||||
Downloading additional data
|
||||
---------------------------
|
||||
|
||||
### Wikipedia rankings
|
||||
|
||||
Wikipedia can be used as an optional auxiliary data source to help indicate
|
||||
the importance of osm features. Nominatim will work without this information
|
||||
but it will improve the quality of the results if this is installed.
|
||||
This data is available as a binary download:
|
||||
|
||||
cd $NOMINATIM_SOURCE_DIR/data
|
||||
wget http://www.nominatim.org/data/wikipedia_article.sql.bin
|
||||
wget http://www.nominatim.org/data/wikipedia_redirect.sql.bin
|
||||
|
||||
Combined the 2 files are around 1.5GB and add around 30GB to the install
|
||||
size of nominatim. They also increase the install time by an hour or so.
|
||||
|
||||
*NOTE:* you'll need to download the Wikipedia rankings before performing
|
||||
the initial import of the data if you want the rankings applied to the
|
||||
loaded data.
|
||||
|
||||
### UK postcodes
|
||||
|
||||
Nominatim can use postcodes from an external source to improve searches that involve a UK postcode. This data can be optionally downloaded:
|
||||
|
||||
cd $NOMINATIM_SOURCE_DIR/data
|
||||
wget http://www.nominatim.org/data/gb_postcode_data.sql.gz
|
||||
|
||||
|
||||
Initial import of the data
|
||||
--------------------------
|
||||
|
||||
**Important:** first try the import with a small excerpt, for example from
|
||||
[Geofabrik](http://download.geofabrik.de).
|
||||
|
||||
Download the data to import and load the data with the following command:
|
||||
|
||||
./utils/setup.php --osm-file <your data file> --all [--osm2pgsql-cache 28000] 2>&1 | tee setup.log
|
||||
|
||||
The `--osm2pgsql-cache` parameter is optional but strongly recommended for
|
||||
planet imports. It sets the node cache size for the osm2pgsql import part
|
||||
(see `-C` parameter in osm2pgsql help). 28GB are recommended for a full planet
|
||||
import, for excerpts you can use less. Adapt to your available RAM to
|
||||
avoid swapping, never give more than 2/3 of RAM to osm2pgsql.
|
||||
|
||||
|
||||
Loading additional datasets
|
||||
---------------------------
|
||||
|
||||
The following commands will create additional entries for POI searches:
|
||||
|
||||
./utils/specialphrases.php --wiki-import > specialphrases.sql
|
||||
psql -d nominatim -f specialphrases.sql
|
||||
|
||||
|
||||
Installing Tiger housenumber data for the US
|
||||
============================================
|
||||
|
||||
Nominatim is able to use the official TIGER address set to complement the
|
||||
OSM housenumber data in the US. You can add TIGER data to your own Nominatim
|
||||
instance by following these steps:
|
||||
|
||||
1. Install the GDAL library and python bindings and the unzip tool
|
||||
|
||||
* Ubuntu: `sudo apt-get install python-gdal unzip`
|
||||
* CentOS: `sudo yum install gdal-python unzip`
|
||||
|
||||
2. Get the TIGER 2015 data. You will need the EDGES files
|
||||
(3,234 zip files, 11GB total). Choose one of the two sources:
|
||||
|
||||
wget -r ftp://ftp2.census.gov/geo/tiger/TIGER2015/EDGES/
|
||||
wget -r ftp://mirror1.shellbot.com/census/geo/tiger/TIGER2015/EDGES/
|
||||
|
||||
The first one is the original source, the second a considerably faster
|
||||
mirror.
|
||||
|
||||
3. Convert the data into SQL statements (stored in data/tiger):
|
||||
|
||||
./utils/imports.php --parse-tiger <tiger edge data directory>
|
||||
|
||||
4. Import the data into your Nominatim database:
|
||||
|
||||
./utils/setup.php --import-tiger-data
|
||||
|
||||
5. Enable use of the Tiger data in your `settings/local.php` by adding:
|
||||
|
||||
@define('CONST_Use_US_Tiger_Data', true);
|
||||
|
||||
6. Apply the new settings:
|
||||
|
||||
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
|
||||
|
||||
Be warned that the import can take a very long time, especially if you
|
||||
import all of the US. The entire US adds about 10GB to your database.
|
||||
|
||||
|
||||
Updates
|
||||
=======
|
||||
|
||||
There are many different possibilities to update your Nominatim database.
|
||||
The following section describes how to keep it up-to-date with Pyosmium.
|
||||
For a list of other methods see the output of `./utils/update.php --help`.
|
||||
|
||||
Installing the newest version of Pyosmium
|
||||
-----------------------------------------
|
||||
|
||||
It is recommended to install Pyosmium via pip:
|
||||
|
||||
pip install --user osmium
|
||||
|
||||
Nominatim needs a tool called `pyosmium-get-updates` that comes with
|
||||
Pyosmium. You need to tell Nominatim where to find it. Add the
|
||||
following line to your `settings/local.php`:
|
||||
|
||||
@define('CONST_Pyosmium_Binary', '/home/user/.local/bin/pyosmium-get-changes');
|
||||
|
||||
The path above is fine if you used the `--user` parameter with pip.
|
||||
Replace `user` with your user name.
|
||||
|
||||
Setting up the update process
|
||||
-----------------------------
|
||||
|
||||
Next the update needs to be initialised. By default Nominatim is configured
|
||||
to update using the global minutely diffs.
|
||||
|
||||
If you want a different update source you will need to add some settings
|
||||
to `settings/local.php`. For example, to use the daily country extracts
|
||||
diffs for Ireland from geofabrik add the following:
|
||||
|
||||
// base URL of the replication service
|
||||
@define('CONST_Replication_Url', 'http://download.geofabrik.de/europe/ireland-and-northern-ireland-updates');
|
||||
// How often upstream publishes diffs
|
||||
@define('CONST_Replication_Update_Interval', '86400');
|
||||
// How long to sleep if no update found yet
|
||||
@define('CONST_Replication_Recheck_Interval', '900');
|
||||
|
||||
To set up the update process now run the following command:
|
||||
|
||||
./utils/update.php --init-updates
|
||||
|
||||
It outputs the date where updates will start. Recheck that this date is
|
||||
what you expect.
|
||||
|
||||
The --init-updates command needs to be rerun whenever the replication service
|
||||
is changed.
|
||||
|
||||
Updating Nominatim
|
||||
------------------
|
||||
|
||||
The following command will keep your database constantly up to date:
|
||||
|
||||
./utils/update.php --import-osmosis-all
|
||||
|
||||
(Note that even though the old name "import-osmosis-all" has been kept for compatibility reasons, Osmosis is not required to run this - it uses pyosmium behind the scenes.)
|
||||
|
||||
If you have imported multiple country extracts and want to keep them
|
||||
up-to-date, have a look at the script in
|
||||
[issue #60](https://github.com/openstreetmap/Nominatim/issues/60).
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
|
||||
*Note:* these installation instructions are also available in executable
|
||||
form for use with vagrant under `vagrant/Install-on-Centos-7.sh`.
|
||||
|
||||
Installing the Required Software
|
||||
================================
|
||||
|
||||
These instructions expect that you have a freshly installed CentOS version 7.
|
||||
Make sure all packages are up-to-date by running:
|
||||
|
||||
sudo yum update -y
|
||||
|
||||
The standard CentOS repositories don't contain all the required packages,
|
||||
you need to enable the EPEL repository as well. To enable it on CentOS,
|
||||
install the epel-release RPM by running:
|
||||
|
||||
sudo yum install -y epel-release
|
||||
|
||||
Now you can install all packages needed for Nominatim:
|
||||
|
||||
sudo yum install -y postgresql-server postgresql-contrib postgresql-devel postgis postgis-utils \
|
||||
git cmake make gcc gcc-c++ libtool policycoreutils-python \
|
||||
php-pgsql php php-pear php-pear-DB php-intl libpqxx-devel proj-epsg \
|
||||
bzip2-devel proj-devel geos-devel libxml2-devel boost-devel expat-devel zlib-devel
|
||||
|
||||
If you want to run the test suite, you need to install the following
|
||||
additional packages:
|
||||
|
||||
sudo yum install -y python-pip python-Levenshtein python-psycopg2 \
|
||||
python-numpy php-phpunit-PHPUnit
|
||||
pip install --user --upgrade pip setuptools lettuce==0.2.18 six==1.9 \
|
||||
haversine Shapely pytidylib
|
||||
sudo pear install PHP_CodeSniffer
|
||||
|
||||
|
||||
System Configuration
|
||||
====================
|
||||
|
||||
The following steps are meant to configure a fresh CentOS installation
|
||||
for use with Nominatim. You may skip some of the steps if you have your
|
||||
OS already configured.
|
||||
|
||||
Creating Dedicated User Accounts
|
||||
--------------------------------
|
||||
|
||||
Nominatim will run as a global service on your machine. It is therefore
|
||||
best to install it under its own separate user account. In the following
|
||||
we assume this user is called nominatim and the installation will be in
|
||||
/srv/nominatim. To create the user and directory run:
|
||||
|
||||
sudo useradd -d /srv/nominatim -s /bin/bash -m nominatim
|
||||
|
||||
You may find a more suitable location if you wish.
|
||||
|
||||
To be able to copy and paste instructions from this manual, export
|
||||
user name and home directory now like this:
|
||||
|
||||
export USERNAME=nominatim
|
||||
export USERHOME=/srv/nominatim
|
||||
|
||||
**Never, ever run the installation as a root user.** You have been warned.
|
||||
|
||||
Make sure that system servers can read from the home directory:
|
||||
|
||||
chmod a+x $USERHOME
|
||||
|
||||
Setting up PostgreSQL
|
||||
---------------------
|
||||
|
||||
CentOS does not automatically create a database cluster. Therefore, start
|
||||
with initializing the database, then enable the server to start at boot:
|
||||
|
||||
sudo postgresql-setup initdb
|
||||
sudo systemctl enable postgresql
|
||||
|
||||
|
||||
Next tune the postgresql configuration, which is located in
|
||||
`/var/lib/pgsql/data/postgresql.conf`. See section *Postgres Tuning* in
|
||||
[the installation page](Installation.md) for the parameters to change.
|
||||
|
||||
Now start the postgresql service after updating this config file.
|
||||
|
||||
sudo systemctl restart postgresql
|
||||
|
||||
|
||||
Finally, we need to add two postgres users: one for the user that does
|
||||
the import and another for the webserver which should access the database
|
||||
only for reading:
|
||||
|
||||
|
||||
sudo -u postgres createuser -s $USERNAME
|
||||
sudo -u postgres createuser apache
|
||||
|
||||
|
||||
Setting up the Apache Webserver
|
||||
-------------------------------
|
||||
|
||||
You need to create an alias to the website directory in your apache
|
||||
configuration. Add a separate nominatim configuration to your webserver:
|
||||
|
||||
```
|
||||
sudo tee /etc/httpd/conf.d/nominatim.conf << EOFAPACHECONF
|
||||
<Directory "$USERHOME/Nominatim/build/website">
|
||||
Options FollowSymLinks MultiViews
|
||||
AddType text/html .php
|
||||
DirectoryIndex search.php
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
Alias /nominatim $USERHOME/Nominatim/build/website
|
||||
EOFAPACHECONF
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
Then reload apache
|
||||
|
||||
|
||||
sudo systemctl restart httpd
|
||||
|
||||
|
||||
Adding SELinux Security Settings
|
||||
--------------------------------
|
||||
|
||||
It is a good idea to leave SELinux enabled and enforcing, particularly
|
||||
with a web server accessible from the Internet. At a minimum the
|
||||
following SELinux labeling should be done for Nominatim:
|
||||
|
||||
sudo semanage fcontext -a -t httpd_sys_content_t "$USERHOME/Nominatim/(website|lib|settings)(/.*)?"
|
||||
sudo semanage fcontext -a -t lib_t "$USERHOME/Nominatim/module/nominatim.so"
|
||||
sudo restorecon -R -v $USERHOME/Nominatim
|
||||
|
||||
|
||||
Installing Nominatim
|
||||
====================
|
||||
|
||||
Building and Configuration
|
||||
--------------------------
|
||||
|
||||
Get the source code from Github and change into the source directory
|
||||
|
||||
|
||||
|
||||
cd $USERHOME
|
||||
git clone --recursive git://github.com/openstreetmap/Nominatim.git
|
||||
cd Nominatim
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
When installing the latest source from github, you also need to
|
||||
download the country grid:
|
||||
|
||||
|
||||
wget -O data/country_osm_grid.sql.gz http://www.nominatim.org/data/country_grid.sql.gz
|
||||
|
||||
|
||||
The code must be built in a separate directory. Create this directory,
|
||||
then configure and build Nominatim in there:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake $USERHOME/Nominatim
|
||||
make
|
||||
|
||||
You need to create a minimal configuration file that tells nominatim
|
||||
the name of your webserver user and the URL of the website:
|
||||
|
||||
```
|
||||
tee settings/local.php << EOF
|
||||
<?php
|
||||
@define('CONST_Database_Web_User', 'apache');
|
||||
@define('CONST_Website_BaseURL', '/nominatim/');
|
||||
EOF
|
||||
```
|
||||
|
||||
|
||||
Nominatim is now ready to use. Continue with
|
||||
[importing a database from OSM data](Import-and-Update.md).
|
||||
@@ -1,167 +0,0 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
*Note:* these installation instructions are also available in executable
|
||||
form for use with vagrant under vagrant/Install-on-Ubuntu-16.sh.
|
||||
|
||||
Installing the Required Software
|
||||
================================
|
||||
|
||||
These instructions expect that you have a freshly installed Ubuntu 16.04.
|
||||
|
||||
Make sure all packages are are up-to-date by running:
|
||||
|
||||
|
||||
|
||||
sudo apt-get update -qq
|
||||
|
||||
Now you can install all packages needed for Nominatim:
|
||||
|
||||
sudo apt-get install -y build-essential cmake g++ libboost-dev libboost-system-dev \
|
||||
libboost-filesystem-dev libexpat1-dev zlib1g-dev libxml2-dev\
|
||||
libbz2-dev libpq-dev libgeos-dev libgeos++-dev libproj-dev \
|
||||
postgresql-server-dev-9.5 postgresql-9.5-postgis-2.2 postgresql-contrib-9.5 \
|
||||
apache2 php php-pgsql libapache2-mod-php php-pear php-db \
|
||||
php-intl git
|
||||
|
||||
If you want to run the test suite, you need to install the following
|
||||
additional packages:
|
||||
|
||||
sudo apt-get install -y python3-dev python3-pip python3-psycopg2 python3-tidylib phpunit
|
||||
|
||||
pip3 install --user behave nose # urllib3
|
||||
sudo pear install PHP_CodeSniffer
|
||||
|
||||
|
||||
System Configuration
|
||||
====================
|
||||
|
||||
The following steps are meant to configure a fresh Ubuntu installation
|
||||
for use with Nominatim. You may skip some of the steps if you have your
|
||||
OS already configured.
|
||||
|
||||
Creating Dedicated User Accounts
|
||||
--------------------------------
|
||||
|
||||
Nominatim will run as a global service on your machine. It is therefore
|
||||
best to install it under its own separate user account. In the following
|
||||
we assume this user is called nominatim and the installation will be in
|
||||
/srv/nominatim. To create the user and directory run:
|
||||
|
||||
sudo useradd -d /srv/nominatim -s /bin/bash -m nominatim
|
||||
|
||||
You may find a more suitable location if you wish.
|
||||
|
||||
To be able to copy and paste instructions from this manual, export
|
||||
user name and home directory now like this:
|
||||
|
||||
export USERNAME=nominatim
|
||||
export USERHOME=/srv/nominatim
|
||||
|
||||
**Never, ever run the installation as a root user.** You have been warned.
|
||||
|
||||
Make sure that system servers can read from the home directory:
|
||||
|
||||
chmod a+x $USERHOME
|
||||
|
||||
Setting up PostgreSQL
|
||||
---------------------
|
||||
|
||||
Tune the postgresql configuration, which is located in
|
||||
`/etc/postgresql/9.5/main/postgresql.conf`. See section *Postgres Tuning* in
|
||||
[the installation page](Installation.md) for the parameters to change.
|
||||
|
||||
Restart the postgresql service after updating this config file.
|
||||
|
||||
sudo systemctl restart postgresql
|
||||
|
||||
|
||||
Finally, we need to add two postgres users: one for the user that does
|
||||
the import and another for the webserver which should access the database
|
||||
for reading only:
|
||||
|
||||
|
||||
sudo -u postgres createuser -s $USERNAME
|
||||
sudo -u postgres createuser www-data
|
||||
|
||||
|
||||
Setting up the Apache Webserver
|
||||
-------------------------------
|
||||
|
||||
You need to create an alias to the website directory in your apache
|
||||
configuration. Add a separate nominatim configuration to your webserver:
|
||||
|
||||
```
|
||||
sudo tee /etc/apache2/conf-available/nominatim.conf << EOFAPACHECONF
|
||||
<Directory "$USERHOME/Nominatim/build/website">
|
||||
Options FollowSymLinks MultiViews
|
||||
AddType text/html .php
|
||||
DirectoryIndex search.php
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
Alias /nominatim $USERHOME/Nominatim/build/website
|
||||
EOFAPACHECONF
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
Then enable the configuration and restart apache
|
||||
|
||||
|
||||
sudo a2enconf nominatim
|
||||
sudo systemctl restart apache2
|
||||
|
||||
|
||||
Installing Nominatim
|
||||
====================
|
||||
|
||||
Building and Configuration
|
||||
--------------------------
|
||||
|
||||
Get the source code from Github and change into the source directory
|
||||
|
||||
|
||||
|
||||
cd $USERHOME
|
||||
git clone --recursive git://github.com/openstreetmap/Nominatim.git
|
||||
cd Nominatim
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
When installing the latest source from github, you also need to
|
||||
download the country grid:
|
||||
|
||||
|
||||
wget -O data/country_osm_grid.sql.gz http://www.nominatim.org/data/country_grid.sql.gz
|
||||
|
||||
|
||||
The code must be built in a separate directory. Create this directory,
|
||||
then configure and build Nominatim in there:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake $USERHOME/Nominatim
|
||||
make
|
||||
|
||||
You need to create a minimal configuration file that tells nominatim
|
||||
where it is located on the webserver:
|
||||
|
||||
```
|
||||
tee settings/local.php << EOF
|
||||
<?php
|
||||
@define('CONST_Website_BaseURL', '/nominatim/');
|
||||
EOF
|
||||
```
|
||||
|
||||
|
||||
Nominatim is now ready to use. Continue with
|
||||
[importing a database from OSM data](Import-and-Update.md).
|
||||
@@ -1,154 +0,0 @@
|
||||
Nominatim installation
|
||||
======================
|
||||
|
||||
This page contains generic installation instructions for Nominatim and its
|
||||
prerequisites. There are also step-by-step instructions available for
|
||||
the following operating systems:
|
||||
|
||||
* [Ubuntu 16.04](Install-on-Ubuntu-16.md)
|
||||
* [CentOS 7.2](Install-on-Centos-7.md)
|
||||
|
||||
These OS-specific instructions can also be found in executable form
|
||||
in the `vagrant/` directory.
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
### Software
|
||||
|
||||
For compiling:
|
||||
|
||||
* [cmake](https://cmake.org/)
|
||||
* [libxml2](http://xmlsoft.org/)
|
||||
* a recent C++ compiler
|
||||
|
||||
Nominatim comes with its own version of osm2pgsql. See the
|
||||
[osm2pgsql README](../osm2pgsql/README.md) for additional dependencies
|
||||
required for compiling osm2pgsql.
|
||||
|
||||
For running tests:
|
||||
|
||||
* [behave](http://pythonhosted.org/behave/)
|
||||
* [Psycopg2](http://initd.org/psycopg)
|
||||
* [nose](https://nose.readthedocs.io)
|
||||
* [phpunit](https://phpunit.de)
|
||||
|
||||
For running Nominatim:
|
||||
|
||||
* [PostgreSQL](http://www.postgresql.org) (9.1 or later)
|
||||
* [PostGIS](http://postgis.refractions.net) (2.0 or later)
|
||||
* [PHP](http://php.net) (5.4 or later)
|
||||
* PHP-pgsql
|
||||
* PHP-intl (bundled with PHP)
|
||||
* [PEAR::DB](http://pear.php.net/package/DB)
|
||||
* a webserver (apache or nginx are recommended)
|
||||
|
||||
For running continuous updates:
|
||||
|
||||
* [pyosmium](http://osmcode.org/pyosmium/)
|
||||
|
||||
### Hardware
|
||||
|
||||
A minimum of 2GB of RAM is required or installation will fail. For a full
|
||||
planet import 32GB of RAM or more strongly are recommended.
|
||||
|
||||
For a full planet install you will need about 500GB of hard disk space (as of
|
||||
June 2016, take into account that the OSM database is growing fast). SSD disks
|
||||
will help considerably to speed up import and queries.
|
||||
|
||||
On a 6-core machine with 32GB RAM and SSDs the import of a full planet takes
|
||||
a bit more than 2 days. Without SSDs 7-8 days are more realistic.
|
||||
|
||||
|
||||
Setup of the server
|
||||
-------------------
|
||||
|
||||
### PostgreSQL tuning
|
||||
|
||||
You might want to tune your PostgreSQL installation so that the later steps
|
||||
make best use of your hardware. You should tune the following parameters in
|
||||
your `postgresql.conf` file.
|
||||
|
||||
shared_buffers (2GB)
|
||||
maintenance_work_mem (10GB)
|
||||
work_mem (50MB)
|
||||
effective_cache_size (24GB)
|
||||
synchronous_commit = off
|
||||
checkpoint_segments = 100 # only for postgresql <= 9.4
|
||||
checkpoint_timeout = 10min
|
||||
checkpoint_completion_target = 0.9
|
||||
|
||||
The numbers in brackets behind some parameters seem to work fine for
|
||||
32GB RAM machine. Adjust to your setup.
|
||||
|
||||
For the initial import, you should also set:
|
||||
|
||||
fsync = off
|
||||
full_page_writes = off
|
||||
|
||||
Don't forget to reenable them after the initial import or you risk database
|
||||
corruption. Autovacuum must not be switched off because it ensures that the
|
||||
tables are frequently analysed.
|
||||
|
||||
### Webserver setup
|
||||
|
||||
The `website/` directory in the build directory contains the configured
|
||||
website. Include the directory into your webbrowser to serve php files
|
||||
from there.
|
||||
|
||||
#### Configure for use with Apache
|
||||
|
||||
Make sure your Apache configuration contains the required permissions for the
|
||||
directory and create an alias:
|
||||
|
||||
<Directory "/srv/nominatim/build/website">
|
||||
Options FollowSymLinks MultiViews
|
||||
AddType text/html .php
|
||||
DirectoryIndex search.php
|
||||
Require all granted
|
||||
</Directory>
|
||||
Alias /nominatim /srv/nominatim/build/website
|
||||
|
||||
`/srv/nominatim/build` should be replaced with the location of your
|
||||
build directory.
|
||||
|
||||
After making changes in the apache config you need to restart apache.
|
||||
The website should now be available on http://localhost/nominatim.
|
||||
|
||||
#### Configure for use with Nginx
|
||||
|
||||
Use php-fpm as a deamon for serving PHP cgi. Install php-fpm together with nginx.
|
||||
|
||||
By default php listens on a network socket. If you want it to listen to a
|
||||
Unix socket instead, change the pool configuration (`pool.d/www.conf`) as
|
||||
follows:
|
||||
|
||||
; Comment out the tcp listener and add the unix socket
|
||||
;listen = 127.0.0.1:9000
|
||||
listen = /var/run/php5-fpm.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.
|
||||
|
||||
root /srv/nominatim/build/website;
|
||||
index search.php index.html;
|
||||
location ~ [^/]\.php(/|$) {
|
||||
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
||||
if (!-f $document_root$fastcgi_script_name) {
|
||||
return 404;
|
||||
}
|
||||
fastcgi_pass unix:/var/run/php5-fpm.sock;
|
||||
fastcgi_index search.php;
|
||||
include fastcgi.conf;
|
||||
}
|
||||
|
||||
Restart the nginx and php5-fpm services and the website should now be available
|
||||
on http://localhost/.
|
||||
|
||||
|
||||
Now continue with [importing the database](Import-and-Update.md).
|
||||
172
docs/admin/Advanced-Installations.md
Normal file
172
docs/admin/Advanced-Installations.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Advanced installations
|
||||
|
||||
This page contains instructions for setting up multiple countries in
|
||||
your Nominatim database. It is assumed that you have already successfully
|
||||
installed the Nominatim software itself, if not return to the
|
||||
[installation page](Installation.md).
|
||||
|
||||
## Importing multiple regions
|
||||
|
||||
To import multiple regions in your database, you need to configure and run `utils/import_multiple_regions.sh` file. This script will set up the update directory which has the following structure:
|
||||
|
||||
```bash
|
||||
update
|
||||
├── europe
|
||||
│ ├── andorra
|
||||
│ │ └── sequence.state
|
||||
│ └── monaco
|
||||
│ └── sequence.state
|
||||
└── tmp
|
||||
├── combined.osm.pbf
|
||||
└── europe
|
||||
├── andorra-latest.osm.pbf
|
||||
└── monaco-latest.osm.pbf
|
||||
|
||||
|
||||
```
|
||||
|
||||
The `sequence.state` files will contain the sequence ID, which will be used by pyosmium to get updates. The tmp folder is used for import dump.
|
||||
|
||||
### Configuring multiple regions
|
||||
|
||||
The file `import_multiple_regions.sh` needs to be edited as per your requirement:
|
||||
|
||||
1. List of countries. eg:
|
||||
|
||||
COUNTRIES="europe/monaco europe/andorra"
|
||||
|
||||
2. Path to Build directory. eg:
|
||||
|
||||
NOMINATIMBUILD="/srv/nominatim/build"
|
||||
|
||||
3. Path to Update directory. eg:
|
||||
|
||||
UPDATEDIR="/srv/nominatim/update"
|
||||
|
||||
4. Replication URL. eg:
|
||||
|
||||
BASEURL="https://download.geofabrik.de"
|
||||
DOWNCOUNTRYPOSTFIX="-latest.osm.pbf"
|
||||
|
||||
!!! tip
|
||||
If your database already exists and you want to add more countries, replace the setting up part
|
||||
`${SETUPFILE} --osm-file ${UPDATEDIR}/tmp/combined.osm.pbf --all 2>&1`
|
||||
with `${UPDATEFILE} --import-file ${UPDATEDIR}/tmp/combined.osm.pbf --index --index-instances N 2>&1`
|
||||
where N is the numbers of CPUs in your system.
|
||||
|
||||
### Setting up multiple regions
|
||||
|
||||
Run the following command from your Nominatim directory after configuring the file.
|
||||
|
||||
bash ./utils/import_multiple_regions.sh
|
||||
|
||||
!!! danger "Important"
|
||||
This file uses osmium-tool. It must be installed before executing the import script.
|
||||
Installation instructions can be found [here](https://osmcode.org/osmium-tool/manual.html#installation).
|
||||
|
||||
### Updating multiple regions
|
||||
|
||||
To import multiple regions in your database, you need to configure and run ```utils/update_database.sh```.
|
||||
This uses the update directory set up while setting up the DB.
|
||||
|
||||
### Configuring multiple regions
|
||||
|
||||
The file `update_database.sh` needs to be edited as per your requirement:
|
||||
|
||||
1. List of countries. eg:
|
||||
|
||||
COUNTRIES="europe/monaco europe/andorra"
|
||||
|
||||
2. Path to Build directory. eg:
|
||||
|
||||
NOMINATIMBUILD="/srv/nominatim/build"
|
||||
|
||||
3. Path to Update directory. eg:
|
||||
|
||||
UPDATEDIR="/srv/nominatim/update"
|
||||
|
||||
4. Replication URL. eg:
|
||||
|
||||
BASEURL="https://download.geofabrik.de"
|
||||
DOWNCOUNTRYPOSTFIX="-updates"
|
||||
|
||||
5. Followup can be set according to your installation. eg: For Photon,
|
||||
|
||||
FOLLOWUP="curl http://localhost:2322/nominatim-update"
|
||||
|
||||
will handle the indexing.
|
||||
|
||||
### Updating the database
|
||||
|
||||
Run the following command from your Nominatim directory after configuring the file.
|
||||
|
||||
bash ./utils/update_database.sh
|
||||
|
||||
This will get diffs from the replication server, import diffs and index the database. The default replication server in the script([Geofabrik](https://download.geofabrik.de)) provides daily updates.
|
||||
|
||||
## Importing Nominatim to an external PostgreSQL database
|
||||
|
||||
You can install Nominatim using a database that runs on a different server when
|
||||
you have physical access to the file system on the other server. Nominatim
|
||||
uses a custom normalization library that needs to be made accessible to the
|
||||
PostgreSQL server. This section explains how to set up the normalization
|
||||
library.
|
||||
|
||||
### Option 1: Compiling the library on the database server
|
||||
|
||||
The most sure way to get a working library is to compile it on the database
|
||||
server. From the prerequisites you need at least cmake, gcc and the
|
||||
PostgreSQL server package.
|
||||
|
||||
Clone or unpack the Nominatim source code, enter the source directory and
|
||||
create and enter a build directory.
|
||||
|
||||
```sh
|
||||
cd Nominatim
|
||||
mkdir build
|
||||
cd build
|
||||
```
|
||||
|
||||
Now configure cmake to only build the PostgreSQL module and build it:
|
||||
|
||||
```
|
||||
cmake -DBUILD_IMPORTER=off -DBUILD_API=off -DBUILD_TESTS=off -DBUILD_DOCS=off -DBUILD_OSM2PGSQL=off ..
|
||||
make
|
||||
```
|
||||
|
||||
When done, you find the normalization library in `build/module/nominatim.so`.
|
||||
Copy it to a place where it is readable and executable by the PostgreSQL server
|
||||
process.
|
||||
|
||||
### Option 2: Compiling the library on the import machine
|
||||
|
||||
You can also compile the normalization library on the machine from where you
|
||||
run the import.
|
||||
|
||||
!!! important
|
||||
You can only do this when the database server and the import machine have
|
||||
the same architecture and run the same version of Linux. Otherwise there is
|
||||
no guarantee that the compiled library is compatible with the PostgreSQL
|
||||
server running on the database server.
|
||||
|
||||
Make sure that the PostgreSQL server package is installed on the machine
|
||||
**with the same version as on the database server**. You do not need to install
|
||||
the PostgreSQL server itself.
|
||||
|
||||
Download and compile Nominatim as per standard instructions. Once done, you find
|
||||
the nomrmalization library in `build/module/nominatim.so`. Copy the file to
|
||||
the database server at a location where it is readable and executable by the
|
||||
PostgreSQL server process.
|
||||
|
||||
### Running the import
|
||||
|
||||
On the client side you now need to configure the import to point to the
|
||||
correct location of the library **on the database server**. Add the following
|
||||
line to your your `settings/local.php` file:
|
||||
|
||||
```php
|
||||
@define('CONST_Database_Module_Path', '<directory on the database server where nominatim.so resides>');
|
||||
```
|
||||
|
||||
Now change the `CONST_Database_DSN` to point to your remote server and continue
|
||||
to follow the [standard instructions for importing](/admin/Import).
|
||||
141
docs/admin/Deployment.md
Normal file
141
docs/admin/Deployment.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Deploying Nominatim
|
||||
|
||||
The Nominatim API is implemented as a PHP application. The `website/` directory
|
||||
in the build 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](http://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 that your Nominatim build directory is
|
||||
located in `/srv/nominatim/build` and the source code in
|
||||
`/srv/nominatim/Nominatim`. 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/build/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 "/srv/nominatim/Nominatim/(website|lib|settings)(/.*)?"
|
||||
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/nominatim/build/(website|settings)(/.*)?"
|
||||
sudo semanage fcontext -a -t lib_t "/srv/nominatim/build/module/nominatim.so"
|
||||
sudo restorecon -R -v /srv/nominatim/Nominatim
|
||||
sudo restorecon -R -v /srv/nominatim/build
|
||||
```
|
||||
|
||||
## 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/build/website">
|
||||
Options FollowSymLinks MultiViews
|
||||
AddType text/html .php
|
||||
DirectoryIndex search.php
|
||||
Require all granted
|
||||
</Directory>
|
||||
Alias /nominatim /srv/nominatim/build/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 deamon 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.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/build/website;
|
||||
index search.php;
|
||||
location / {
|
||||
try_files $uri $uri/ @php;
|
||||
}
|
||||
|
||||
location @php {
|
||||
fastcgi_param SCRIPT_FILENAME "$document_root$uri.php";
|
||||
fastcgi_param PATH_TRANSLATED "$document_root$uri.php";
|
||||
fastcgi_param QUERY_STRING $args;
|
||||
fastcgi_pass unix:/var/run/php-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
||||
location ~ [^/]\.php(/|$) {
|
||||
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
||||
if (!-f $document_root$fastcgi_script_name) {
|
||||
return 404;
|
||||
}
|
||||
fastcgi_pass unix:/var/run/php-fpm.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/`.
|
||||
|
||||
221
docs/admin/Faq.md
Normal file
221
docs/admin/Faq.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# Troubleshooting Nominatim Installations
|
||||
|
||||
## Installation Issues
|
||||
|
||||
### Can a stopped/killed import process be resumed?
|
||||
|
||||
"I accidentally killed the import process after it has been running for many hours. Can it be resumed?"
|
||||
|
||||
It is possible if the import already got to the indexing stage.
|
||||
Check the last line of output that was logged before the process
|
||||
was killed. If it looks like this:
|
||||
|
||||
|
||||
Done 844 in 13 @ 64.923080 per second - Rank 26 ETA (seconds): 7.886255
|
||||
|
||||
then you can resume with the following command:
|
||||
|
||||
```sh
|
||||
./utils/setup.php --index --create-search-indices --create-country-names
|
||||
```
|
||||
|
||||
If the reported rank is 26 or higher, you can also safely add `--index-noanalyse`.
|
||||
|
||||
|
||||
### PostgreSQL crashed "invalid page in block"
|
||||
|
||||
Usually serious problem, can be a hardware issue, not all data written to disc
|
||||
for example. Check PostgreSQL log file and search PostgreSQL issues/mailing
|
||||
list for hints.
|
||||
|
||||
If it happened during index creation you can try rerunning the step with
|
||||
|
||||
```sh
|
||||
./utils/setup.php --create-search-indices --ignore-errors
|
||||
```
|
||||
|
||||
Otherwise it's best to start the full setup from the beginning.
|
||||
|
||||
|
||||
### PHP "open_basedir restriction in effect" warnings
|
||||
|
||||
PHP Warning: file_get_contents(): open_basedir restriction in effect.
|
||||
|
||||
You need to adjust the
|
||||
[open_basedir](https://www.php.net/manual/en/ini.core.php#ini.open-basedir)
|
||||
setting in your PHP configuration (`php.ini` file). By default this setting may
|
||||
look like this:
|
||||
|
||||
open_basedir = /srv/http/:/home/:/tmp/:/usr/share/pear/
|
||||
|
||||
Either add reported directories to the list or disable this setting temporarily
|
||||
by adding ";" at the beginning of the line. Don't forget to enable this setting
|
||||
again once you are done with the PHP command line operations.
|
||||
|
||||
|
||||
### PHP timezeone warnings
|
||||
|
||||
The Apache log may contain lots of PHP warnings like this:
|
||||
`PHP Warning: date_default_timezone_set() function.`
|
||||
|
||||
You should set the default time zone as instructed in the warning in
|
||||
your `php.ini` file. Find the entry about timezone and set it to
|
||||
something like this:
|
||||
|
||||
; Defines the default timezone used by the date functions
|
||||
; https://php.net/date.timezone
|
||||
date.timezone = 'America/Denver'
|
||||
|
||||
Or
|
||||
|
||||
```
|
||||
echo "date.timezone = 'America/Denver'" > /etc/php.d/timezone.ini
|
||||
```
|
||||
|
||||
### 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-9.5` on Ubuntu)
|
||||
and recompile (`cmake .. && make`).
|
||||
|
||||
|
||||
### I see the error "ERROR: permission denied for language c"
|
||||
|
||||
`nominatim.so`, written in C, is required to be installed on the database
|
||||
server. Some managed database (cloud) services like Amazon RDS do not allow
|
||||
this. There is currently no work-around other than installing a database
|
||||
on a non-managed machine.
|
||||
|
||||
|
||||
### I see the error: "function transliteration(text) does not exist"
|
||||
|
||||
Reinstall the nominatim functions with `setup.php --create--functions`
|
||||
and check for any errors, e.g. a missing `nominatim.so` file.
|
||||
|
||||
### I see the error: "ERROR: mmap (remap) failed"
|
||||
|
||||
This may be a simple out-of-memory error. Try reducing the memory used
|
||||
for `--osm2pgsql-cache`. Also make sure that overcommitting memory is
|
||||
allowed: `cat /proc/sys/vm/overcommit_memory` should print 0 or 1.
|
||||
|
||||
If you are using a flatnode file, then it may also be that the underlying
|
||||
filesystem does not fully support 'mmap'. A notable candidate is virtualbox's
|
||||
vboxfs.
|
||||
|
||||
### I see the error: "clang: Command not found" on CentOS
|
||||
|
||||
On CentOS 7 users reported `/opt/rh/llvm-toolset-7/root/usr/bin/clang: Command not found`.
|
||||
Double-check clang is installed. Instead of `make` try running `make CLANG=true`.
|
||||
|
||||
### nominatim UPDATE failed: ERROR: buffer 179261 is not owned by resource owner Portal
|
||||
|
||||
Several users [reported this](https://github.com/openstreetmap/Nominatim/issues/1168) during the initial import of the database. It's
|
||||
something PostgreSQL internal Nominatim doesn't control. And PostgreSQL forums
|
||||
suggest it's threading related but definitely some kind of crash of a process.
|
||||
Users reported either rebooting the server, different hardware or just trying
|
||||
the import again worked.
|
||||
|
||||
### The website shows: "Could not get word tokens"
|
||||
|
||||
The server cannot access your database. Add `&debug=1` to your URL
|
||||
to get the full error message.
|
||||
|
||||
|
||||
### On CentOS the website shows "Could not connect to server"
|
||||
|
||||
`could not connect to server: No such file or directory`
|
||||
|
||||
On CentOS v7 the PostgreSQL server is started with `systemd`. Check if
|
||||
`/usr/lib/systemd/system/httpd.service` contains a line `PrivateTmp=true`. If
|
||||
so then Apache cannot see the `/tmp/.s.PGSQL.5432` file. It's a good security
|
||||
feature, so use the
|
||||
[preferred solution](../appendix/Install-on-Centos-7/#adding-selinux-security-settings).
|
||||
|
||||
However, you can solve this the quick and dirty way by commenting out that line and then run
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl restart httpd
|
||||
|
||||
|
||||
### Website reports "DB Error: insufficient permissions"
|
||||
|
||||
The user the webserver, e.g. Apache, runs under needs to have access to the
|
||||
Nominatim database. You can find the user like
|
||||
[this](https://serverfault.com/questions/125865/finding-out-what-user-apache-is-running-as),
|
||||
for default Ubuntu operating system for example it's `www-data`.
|
||||
|
||||
1. Repeat the `createuser` step of the installation instructions.
|
||||
|
||||
2. Give the user permission to existing tables
|
||||
|
||||
```
|
||||
GRANT usage ON 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 running SELinux, make sure that the
|
||||
[context is set up correctly](../appendix/Install-on-Centos-7/#adding-selinux-security-settings).
|
||||
|
||||
When you recently updated your operating system, updated PostgreSQL to
|
||||
a new version or moved files (e.g. the build directory) you should
|
||||
recreate `nominatim.so`. Try
|
||||
|
||||
```
|
||||
cd build
|
||||
rm -r module/
|
||||
cmake $main_Nominatim_path && make
|
||||
```
|
||||
|
||||
### Setup.php fails with "DB Error: extension not found"
|
||||
|
||||
Make sure you have the PostgreSQL extensions "hstore" and "postgis" installed.
|
||||
See the installation instructions for a full list of required packages.
|
||||
|
||||
|
||||
### I forgot to delete the flatnodes file before starting an import.
|
||||
|
||||
That's fine. For each import the flatnodes file get overwritten.
|
||||
See [https://help.openstreetmap.org/questions/52419/nominatim-flatnode-storage]()
|
||||
for more information.
|
||||
|
||||
|
||||
## Running your own instance
|
||||
|
||||
### Can I import negative OSM ids into Nominatim?
|
||||
|
||||
See [this question of Stackoverflow](https://help.openstreetmap.org/questions/64662/nominatim-flatnode-with-negative-id).
|
||||
|
||||
### Missing XML or text declaration
|
||||
|
||||
The website might show: `XML Parsing Error: XML or text declaration not at start of entity Location.`
|
||||
|
||||
Make sure there are no spaces at the beginning of your `settings/local.php` file.
|
||||
|
||||
|
||||
285
docs/admin/Import.md
Normal file
285
docs/admin/Import.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# Importing the Database
|
||||
|
||||
The following instructions explain how to create a Nominatim database
|
||||
from an OSM planet file and how to keep the database up to date. It
|
||||
is assumed that you have already successfully installed the Nominatim
|
||||
software itself, if not return to the [installation page](Installation.md).
|
||||
|
||||
## Configuration setup in settings/local.php
|
||||
|
||||
The Nominatim server can be customized via the file `settings/local.php`
|
||||
in the build directory. Note that this is a PHP file, so it must always
|
||||
start like this:
|
||||
|
||||
<?php
|
||||
|
||||
without any leading spaces.
|
||||
|
||||
There are lots of configuration settings you can tweak. Have a look
|
||||
at `settings/default.php` for a full list. Most should have a sensible default.
|
||||
|
||||
#### Flatnode files
|
||||
|
||||
If you plan to import a large dataset (e.g. Europe, North America, planet),
|
||||
you should also enable flatnode storage of node locations. With this
|
||||
setting enabled, node coordinates are stored in a simple file instead
|
||||
of the database. This will save you import time and disk storage.
|
||||
Add to your `settings/local.php`:
|
||||
|
||||
@define('CONST_Osm2pgsql_Flatnode_File', '/path/to/flatnode.file');
|
||||
|
||||
Replace the second part with a suitable path on your system and make sure
|
||||
the directory exists. There should be at least 75GB of free space.
|
||||
|
||||
## Downloading additional data
|
||||
|
||||
### Wikipedia/Wikidata rankings
|
||||
|
||||
Wikipedia can be used as an optional auxiliary data source to help indicate
|
||||
the importance of OSM features. Nominatim will work without this information
|
||||
but it will improve the quality of the results if this is installed.
|
||||
This data is available as a binary download:
|
||||
|
||||
cd $NOMINATIM_SOURCE_DIR/data
|
||||
wget https://www.nominatim.org/data/wikimedia-importance.sql.gz
|
||||
|
||||
The file is about 400MB and adds around 4GB to the Nominatim database.
|
||||
|
||||
!!! tip
|
||||
If you forgot to download the wikipedia rankings, you can also add
|
||||
importances after the import. Download the files, then run
|
||||
`./utils/setup.php --import-wikipedia-articles`
|
||||
and `./utils/update.php --recompute-importance`.
|
||||
|
||||
### Great Britain, USA postcodes
|
||||
|
||||
Nominatim can use postcodes from an external source to improve searches that
|
||||
involve a GB or US postcode. This data can be optionally downloaded:
|
||||
|
||||
cd $NOMINATIM_SOURCE_DIR/data
|
||||
wget https://www.nominatim.org/data/gb_postcode_data.sql.gz
|
||||
wget https://www.nominatim.org/data/us_postcode_data.sql.gz
|
||||
|
||||
## Choosing the data to import
|
||||
|
||||
In its default setup Nominatim is configured to import the full OSM data
|
||||
set for the entire planet. Such a setup requires a powerful machine with
|
||||
at least 64GB of RAM and around 900GB of SSD hard disks. Depending on your
|
||||
use case there are various ways to reduce the amount of data imported. This
|
||||
section discusses these methods. They can also be combined.
|
||||
|
||||
### Using an extract
|
||||
|
||||
If you only need geocoding for a smaller region, then precomputed OSM extracts
|
||||
are a good way to reduce the database size and import time.
|
||||
[Geofabrik](https://download.geofabrik.de) offers extracts for most countries.
|
||||
They even have daily updates which can be used with the update process described
|
||||
[in the next section](../Update). There are also
|
||||
[other providers for extracts](https://wiki.openstreetmap.org/wiki/Planet.osm#Downloading).
|
||||
|
||||
Please be aware that some extracts are not cut exactly along the country
|
||||
boundaries. As a result some parts of the boundary may be missing which means
|
||||
that Nominatim cannot compute the areas for some administrative areas.
|
||||
|
||||
### Dropping Data Required for Dynamic Updates
|
||||
|
||||
About half of the data in Nominatim's database is not really used for serving
|
||||
the API. It is only there to allow the data to be updated from the latest
|
||||
changes from OSM. For many uses these dynamic updates are not really required.
|
||||
If you don't plan to apply updates, the dynamic part of the database can be
|
||||
safely dropped using the following command:
|
||||
|
||||
```
|
||||
./utils/setup.php --drop
|
||||
```
|
||||
|
||||
Note that you still need to provide for sufficient disk space for the initial
|
||||
import. So this option is particularly interesting if you plan to transfer the
|
||||
database or reuse the space later.
|
||||
|
||||
### Reverse-only Imports
|
||||
|
||||
If you only want to use the Nominatim database for reverse lookups or
|
||||
if you plan to use the installation only for exports to a
|
||||
[photon](https://photon.komoot.de/) database, then you can set up a database
|
||||
without search indexes. Add `--reverse-only` to your setup command above.
|
||||
|
||||
This saves about 5% of disk space.
|
||||
|
||||
### Filtering Imported Data
|
||||
|
||||
Nominatim normally sets up a full search database containing administrative
|
||||
boundaries, places, streets, addresses and POI data. There are also other
|
||||
import styles available which only read selected data:
|
||||
|
||||
* **settings/import-admin.style**
|
||||
Only import administrative boundaries and places.
|
||||
* **settings/import-street.style**
|
||||
Like the admin style but also adds streets.
|
||||
* **settings/import-address.style**
|
||||
Import all data necessary to compute addresses down to house number level.
|
||||
* **settings/import-full.style**
|
||||
Default style that also includes points of interest.
|
||||
* **settings/import-extratags.style**
|
||||
Like the full style but also adds most of the OSM tags into the extratags
|
||||
column.
|
||||
|
||||
The style can be changed with the configuration `CONST_Import_Style`.
|
||||
|
||||
To give you an idea of the impact of using the different styles, the table
|
||||
below gives rough estimates of the final database size after import of a
|
||||
2020 planet and after using the `--drop` option. It also shows the time
|
||||
needed for the import on a machine with 64GB RAM, 4 CPUS and NVME disks.
|
||||
Note that the given sizes are just an estimate meant for comparison of
|
||||
style requirements. Your planet import is likely to be larger as the
|
||||
OSM data grows with time.
|
||||
|
||||
style | Import time | DB size | after drop
|
||||
----------|--------------|------------|------------
|
||||
admin | 4h | 215 GB | 20 GB
|
||||
street | 22h | 440 GB | 185 GB
|
||||
address | 36h | 545 GB | 260 GB
|
||||
full | 54h | 640 GB | 330 GB
|
||||
extratags | 54h | 650 GB | 340 GB
|
||||
|
||||
You can also customize the styles further.
|
||||
A [description of the style format](../develop/Import.md#configuring-the-import)
|
||||
can be found in the development section.
|
||||
|
||||
## Initial import of the data
|
||||
|
||||
!!! danger "Important"
|
||||
First try the import with a small extract, for example from
|
||||
[Geofabrik](https://download.geofabrik.de).
|
||||
|
||||
Download the data to import. Then issue the following command
|
||||
from the **build directory** to start the import:
|
||||
|
||||
```sh
|
||||
./utils/setup.php --osm-file <data file> --all 2>&1 | tee setup.log
|
||||
```
|
||||
|
||||
### Notes on full planet imports
|
||||
|
||||
Even on a perfectly configured machine
|
||||
the import of a full planet takes around 2 days. Once you see messages
|
||||
with `Rank .. ETA` appear, the indexing process has started. This part takes
|
||||
the most time. There are 30 ranks to process. Rank 26 and 30 are the most complex.
|
||||
They take each about a third of the total import time. If you have not reached
|
||||
rank 26 after two days of import, it is worth revisiting your system
|
||||
configuration as it may not be optimal for the import.
|
||||
|
||||
### Notes on memory usage
|
||||
|
||||
In the first step of the import Nominatim uses [osm2pgsql](https://osm2pgsql.org)
|
||||
to load the OSM data into the PostgreSQL database. This step is very demanding
|
||||
in terms of RAM usage. osm2pgsql and PostgreSQL are running in parallel at
|
||||
this point. PostgreSQL blocks at least the part of RAM that has been configured
|
||||
with the `shared_buffers` parameter during
|
||||
[PostgreSQL tuning](Installation#postgresql-tuning)
|
||||
and needs some memory on top of that. osm2pgsql needs at least 2GB of RAM for
|
||||
its internal data structures, potentially more when it has to process very large
|
||||
relations. In addition it needs to maintain a cache for node locations. The size
|
||||
of this cache can be configured with the parameter `--osm2pgsql-cache`.
|
||||
|
||||
When importing with a flatnode file, it is best to disable the node cache
|
||||
completely and leave the memory for the flatnode file. Nominatim will do this
|
||||
by default, so you do not need to configure anything in this case.
|
||||
|
||||
For imports without a flatnode file, set `--osm2pgsql-cache` approximately to
|
||||
the size of the OSM pbf file you are importing. The size needs to be given in
|
||||
MB. Make sure you leave enough RAM for PostgreSQL and osm2pgsql as mentioned
|
||||
above. If the system starts swapping or you are getting out-of-memory errors,
|
||||
reduce the cache size or even consider using a flatnode file.
|
||||
|
||||
### Verify the import
|
||||
|
||||
Run this script to verify all required tables and indices got created successfully.
|
||||
|
||||
```sh
|
||||
./utils/check_import_finished.php
|
||||
```
|
||||
|
||||
### Setting up the website
|
||||
|
||||
Run the following command to set up the configuration file for the API frontend
|
||||
`settings/settings-frontend.php`. These settings are used in website/*.php files.
|
||||
|
||||
```sh
|
||||
./utils/setup.php --setup-website
|
||||
```
|
||||
!!! Note
|
||||
This step is not necessary if you use `--all` option while setting up the DB.
|
||||
|
||||
Now you can try out your installation by running:
|
||||
|
||||
```sh
|
||||
make serve
|
||||
```
|
||||
|
||||
This runs a small test server normally used for development. You can use it
|
||||
to verify that your installation is working. 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`.
|
||||
|
||||
To run Nominatim via webservers like Apache or nginx, please read the
|
||||
[Deployment chapter](Deployment.md).
|
||||
|
||||
## Tuning the database
|
||||
|
||||
Accurate word frequency information for search terms helps PostgreSQL's query
|
||||
planner to make the right decisions. Recomputing them can improve the performance
|
||||
of forward geocoding in particular under high load. To recompute word counts run:
|
||||
|
||||
```sh
|
||||
./utils/update.php --recompute-word-counts
|
||||
```
|
||||
|
||||
This will take a couple of hours for a full planet installation. You can
|
||||
also defer that step to a later point in time when you realise that
|
||||
performance becomes an issue. Just make sure that updates are stopped before
|
||||
running this function.
|
||||
|
||||
If you want to be able to search for places by their type through
|
||||
[special key phrases](https://wiki.openstreetmap.org/wiki/Nominatim/Special_Phrases)
|
||||
you also need to enable these key phrases like this:
|
||||
|
||||
./utils/specialphrases.php --wiki-import > specialphrases.sql
|
||||
psql -d nominatim -f specialphrases.sql
|
||||
|
||||
Note that this command downloads the phrases from the wiki link above. You
|
||||
need internet access for the step.
|
||||
|
||||
|
||||
## Installing Tiger housenumber data for the US
|
||||
|
||||
Nominatim is able to use the official [TIGER](https://www.census.gov/geographies/mapping-files/time-series/geo/tiger-line-file.html)
|
||||
address set to complement the OSM house number data in the US. You can add
|
||||
TIGER data to your own Nominatim instance by following these steps. The
|
||||
entire US adds about 10GB to your database.
|
||||
|
||||
1. Get preprocessed TIGER 2019 data and unpack it into the
|
||||
data directory in your Nominatim sources:
|
||||
|
||||
cd Nominatim/data
|
||||
wget https://nominatim.org/data/tiger2019-nominatim-preprocessed.tar.gz
|
||||
tar xf tiger2019-nominatim-preprocessed.tar.gz
|
||||
|
||||
2. Import the data into your Nominatim database:
|
||||
|
||||
./utils/setup.php --import-tiger-data
|
||||
|
||||
3. Enable use of the Tiger data in your `settings/local.php` by adding:
|
||||
|
||||
@define('CONST_Use_US_Tiger_Data', true);
|
||||
|
||||
4. Apply the new settings:
|
||||
|
||||
```sh
|
||||
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
|
||||
```
|
||||
|
||||
|
||||
See the [developer's guide](../develop/data-sources.md#us-census-tiger) for more
|
||||
information on how the data got preprocessed.
|
||||
|
||||
147
docs/admin/Installation.md
Normal file
147
docs/admin/Installation.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Basic Installation
|
||||
|
||||
This page contains generic installation instructions for Nominatim and its
|
||||
prerequisites. There are also step-by-step instructions available for
|
||||
the following operating systems:
|
||||
|
||||
* [Ubuntu 20.04](../appendix/Install-on-Ubuntu-20.md)
|
||||
* [Ubuntu 18.04](../appendix/Install-on-Ubuntu-18.md)
|
||||
* [CentOS 8](../appendix/Install-on-Centos-8.md)
|
||||
* [CentOS 7.2](../appendix/Install-on-Centos-7.md)
|
||||
|
||||
These OS-specific instructions can also be found in executable form
|
||||
in the `vagrant/` directory.
|
||||
|
||||
Users have created instructions for other frameworks. We haven't tested those
|
||||
and can't offer support.
|
||||
|
||||
* [Docker](https://github.com/mediagis/nominatim-docker)
|
||||
* [Docker on Kubernetes](https://github.com/peter-evans/nominatim-k8s)
|
||||
* [Ansible](https://github.com/synthesio/infra-ansible-nominatim)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Software
|
||||
|
||||
For compiling:
|
||||
|
||||
* [cmake](https://cmake.org/)
|
||||
* [expat](https://libexpat.github.io/)
|
||||
* [proj](https://proj.org/)
|
||||
* [bzip2](http://www.bzip.org/)
|
||||
* [zlib](https://www.zlib.net/)
|
||||
* [Boost libraries](https://www.boost.org/), including system and filesystem
|
||||
* PostgreSQL client libraries
|
||||
* a recent C++ compiler (gcc 5+ or Clang 3.8+)
|
||||
|
||||
For running Nominatim:
|
||||
|
||||
* [PostgreSQL](https://www.postgresql.org) (9.3+)
|
||||
* [PostGIS](https://postgis.net) (2.2+)
|
||||
* [Python 3](https://www.python.org/)
|
||||
* [Psycopg2](https://www.psycopg.org)
|
||||
* [PHP](https://php.net) (7.0 or later)
|
||||
* PHP-pgsql
|
||||
* PHP-intl (bundled with PHP)
|
||||
|
||||
For running continuous updates:
|
||||
|
||||
* [pyosmium](https://osmcode.org/pyosmium/) (with Python 3)
|
||||
|
||||
For dependencies for running tests and building documentation, see
|
||||
the [Development section](../develop/Development-Environment.md).
|
||||
|
||||
### Hardware
|
||||
|
||||
A minimum of 2GB of RAM is required or installation will fail. For a full
|
||||
planet import 64GB of RAM or more are strongly recommended. Do not report
|
||||
out of memory problems if you have less than 64GB RAM.
|
||||
|
||||
For a full planet install you will need at least 900GB of hard disk space.
|
||||
Take into account that the OSM database is growing fast.
|
||||
Fast disks are essential. Using NVME disks is recommended.
|
||||
|
||||
Even on a well configured machine the import of a full planet takes
|
||||
around 2 days. On traditional spinning disks, 7-8 days are more realistic.
|
||||
|
||||
## Tuning the PostgreSQL database
|
||||
|
||||
You might want to tune your PostgreSQL installation so that the later steps
|
||||
make best use of your hardware. You should tune the following parameters in
|
||||
your `postgresql.conf` file.
|
||||
|
||||
shared_buffers = 2GB
|
||||
maintenance_work_mem = (10GB)
|
||||
autovacuum_work_mem = 2GB
|
||||
work_mem = (50MB)
|
||||
effective_cache_size = (24GB)
|
||||
synchronous_commit = off
|
||||
checkpoint_segments = 100 # only for postgresql <= 9.4
|
||||
max_wal_size = 1GB # postgresql > 9.4
|
||||
checkpoint_timeout = 10min
|
||||
checkpoint_completion_target = 0.9
|
||||
|
||||
The numbers in brackets behind some parameters seem to work fine for
|
||||
64GB RAM machine. Adjust to your setup. A higher number for `max_wal_size`
|
||||
means that PostgreSQL needs to run checkpoints less often but it does require
|
||||
the additional space on your disk.
|
||||
|
||||
Autovacuum must not be switched off because it ensures that the
|
||||
tables are frequently analysed. If your machine has very little memory,
|
||||
you might consider setting:
|
||||
|
||||
autovacuum_max_workers = 1
|
||||
|
||||
and even reduce `autovacuum_work_mem` further. This will reduce the amount
|
||||
of memory that autovacuum takes away from the import process.
|
||||
|
||||
For the initial import, you should also set:
|
||||
|
||||
fsync = off
|
||||
full_page_writes = off
|
||||
|
||||
Don't forget to reenable them after the initial import or you risk database
|
||||
corruption.
|
||||
|
||||
|
||||
## Downloading and building Nominatim
|
||||
|
||||
### Downloading the latest release
|
||||
|
||||
You can download the [latest release from nominatim.org](https://nominatim.org/downloads/).
|
||||
The release contains all necessary files. Just unpack it.
|
||||
|
||||
### Downloading the latest development version
|
||||
|
||||
If you want to install latest development version from github, make sure to
|
||||
also check out the osm2pgsql subproject:
|
||||
|
||||
```
|
||||
git clone --recursive git://github.com/openstreetmap/Nominatim.git
|
||||
```
|
||||
|
||||
The development version does not include the country grid. Download it separately:
|
||||
|
||||
```
|
||||
wget -O Nominatim/data/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz
|
||||
```
|
||||
|
||||
### Building Nominatim
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Now continue with [importing the database](Import.md).
|
||||
288
docs/admin/Migration.md
Normal file
288
docs/admin/Migration.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# Database Migrations
|
||||
|
||||
This page describes database migrations necessary to update existing databases
|
||||
to newer versions of Nominatim.
|
||||
|
||||
SQL statements should be executed from the PostgreSQL commandline. Execute
|
||||
`psql nominatim` to enter command line mode.
|
||||
|
||||
## 3.5.0 -> 3.6.0
|
||||
|
||||
### Change of layout of search_name_* tables
|
||||
|
||||
The table need a different index for nearest place lookup. Recreate the
|
||||
indexes using the following shell script:
|
||||
|
||||
```bash
|
||||
for table in `psql -d nominatim -c "SELECT tablename FROM pg_tables WHERE tablename LIKE 'search_name_%'" -tA | grep -v search_name_blank`;
|
||||
do
|
||||
psql -d nominatim -c "DROP INDEX idx_${table}_centroid_place; CREATE INDEX idx_${table}_centroid_place ON ${table} USING gist (centroid) WHERE ((address_rank >= 2) AND (address_rank <= 25)); DROP INDEX idx_${table}_centroid_street; CREATE INDEX idx_${table}_centroid_street ON ${table} USING gist (centroid) WHERE ((address_rank >= 26) AND (address_rank <= 27))";
|
||||
done
|
||||
```
|
||||
|
||||
### Removal of html output
|
||||
|
||||
The debugging UI is no longer directly provided with Nominatim. Instead we
|
||||
now provide a simple Javascript application. Please refer to
|
||||
[Setting up the Nominatim UI](../Setup-Nominatim-UI) for details on how to
|
||||
set up the UI.
|
||||
|
||||
The icons served together with the API responses have been moved to the
|
||||
nominatim-ui project as well. If you want to keep the `icon` field in the
|
||||
response, you need to set `CONST_MapIcon_URL` to the URL of the `/mapicon`
|
||||
directory of nominatim-ui.
|
||||
|
||||
### Change order during indexing
|
||||
|
||||
When reindexing places during updates, there is now a different order used
|
||||
which needs a different database index. Create it with the following SQL command:
|
||||
|
||||
```sql
|
||||
CREATE INDEX idx_placex_pendingsector_rank_address
|
||||
ON placex
|
||||
USING BTREE (rank_address, geometry_sector)
|
||||
WHERE indexed_status > 0;
|
||||
```
|
||||
|
||||
You can then drop the old index with:
|
||||
|
||||
```sql
|
||||
DROP INDEX idx_placex_pendingsector;
|
||||
```
|
||||
|
||||
### Unused index
|
||||
|
||||
This index has been unused ever since the query using it was changed two years ago. Saves about 12GB on a planet installation.
|
||||
|
||||
```sql
|
||||
DROP INDEX idx_placex_geometry_reverse_lookupPoint;
|
||||
```
|
||||
|
||||
### Switching to dotenv
|
||||
|
||||
As part of the work changing the configuration format, the configuration for
|
||||
the website is now using a separate configuration file. To create the
|
||||
configuration file, run the following command after updating:
|
||||
|
||||
```sh
|
||||
./utils/setup.php --setup-website
|
||||
```
|
||||
|
||||
## 3.4.0 -> 3.5.0
|
||||
|
||||
### New Wikipedia/Wikidata importance tables
|
||||
|
||||
The `wikipedia_*` tables have a new format that also includes references to
|
||||
Wikidata. You need to update the computation functions and the tables as
|
||||
follows:
|
||||
|
||||
* download the new Wikipedia tables as described in the import section
|
||||
* reimport the tables: `./utils/setup.php --import-wikipedia-articles`
|
||||
* update the functions: `./utils/setup.php --create-functions --enable-diff-updates`
|
||||
* create a new lookup index:
|
||||
```sql
|
||||
CREATE INDEX idx_placex_wikidata
|
||||
ON placex
|
||||
USING BTREE ((extratags -> 'wikidata'))
|
||||
WHERE extratags ? 'wikidata'
|
||||
AND class = 'place'
|
||||
AND osm_type = 'N'
|
||||
AND rank_search < 26;
|
||||
```
|
||||
* compute importance: `./utils/update.php --recompute-importance`
|
||||
|
||||
The last step takes about 10 hours on the full planet.
|
||||
|
||||
Remove one function (it will be recreated in the next step):
|
||||
|
||||
```sql
|
||||
DROP FUNCTION create_country(hstore,character varying);
|
||||
```
|
||||
|
||||
Finally, update all SQL functions:
|
||||
|
||||
```sh
|
||||
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
|
||||
```
|
||||
|
||||
## 3.3.0 -> 3.4.0
|
||||
|
||||
### Reorganisation of location_area_country table
|
||||
|
||||
The table `location_area_country` has been optimized. You need to switch to the
|
||||
new format when you run updates. While updates are disabled, run the following
|
||||
SQL commands:
|
||||
|
||||
```sql
|
||||
CREATE TABLE location_area_country_new AS
|
||||
SELECT place_id, country_code, geometry FROM location_area_country;
|
||||
DROP TABLE location_area_country;
|
||||
ALTER TABLE location_area_country_new RENAME TO location_area_country;
|
||||
CREATE INDEX idx_location_area_country_geometry ON location_area_country USING GIST (geometry);
|
||||
CREATE INDEX idx_location_area_country_place_id ON location_area_country USING BTREE (place_id);
|
||||
```
|
||||
|
||||
Finally, update all SQL functions:
|
||||
|
||||
```sh
|
||||
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
|
||||
```
|
||||
|
||||
## 3.2.0 -> 3.3.0
|
||||
|
||||
### New database connection string (DSN) format
|
||||
|
||||
Previously database connection setting (`CONST_Database_DSN` in `settings/*.php`) had the format
|
||||
|
||||
* (simple) `pgsql://@/nominatim`
|
||||
* (complex) `pgsql://johndoe:secret@machine1.domain.com:1234/db1`
|
||||
|
||||
The new format is
|
||||
|
||||
* (simple) `pgsql:dbname=nominatim`
|
||||
* (complex) `pgsql:dbname=db1;host=machine1.domain.com;port=1234;user=johndoe;password=secret`
|
||||
|
||||
### Natural Earth country boundaries no longer needed as fallback
|
||||
|
||||
```sql
|
||||
DROP TABLE country_naturalearthdata;
|
||||
```
|
||||
|
||||
Finally, update all SQL functions:
|
||||
|
||||
```sh
|
||||
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
|
||||
```
|
||||
|
||||
### Configurable Address Levels
|
||||
|
||||
The new configurable address levels require a new table. Create it with the
|
||||
following command:
|
||||
|
||||
```sh
|
||||
./utils/update.php --update-address-levels
|
||||
```
|
||||
|
||||
## 3.1.0 -> 3.2.0
|
||||
|
||||
### New reverse algorithm
|
||||
|
||||
The reverse algorithm has changed and requires new indexes. Run the following
|
||||
SQL statements to create the indexes:
|
||||
|
||||
```sql
|
||||
CREATE INDEX idx_placex_geometry_reverse_lookupPoint
|
||||
ON placex
|
||||
USING gist (geometry)
|
||||
WHERE (name IS NOT null or housenumber IS NOT null or rank_address BETWEEN 26 AND 27)
|
||||
AND class NOT IN ('railway','tunnel','bridge','man_made')
|
||||
AND rank_address >= 26
|
||||
AND indexed_status = 0
|
||||
AND linked_place_id IS null;
|
||||
CREATE INDEX idx_placex_geometry_reverse_lookupPolygon
|
||||
ON placex USING gist (geometry)
|
||||
WHERE St_GeometryType(geometry) in ('ST_Polygon', 'ST_MultiPolygon')
|
||||
AND rank_address between 4 and 25
|
||||
AND type != 'postcode'
|
||||
AND name is not null
|
||||
AND indexed_status = 0
|
||||
AND linked_place_id is null;
|
||||
CREATE INDEX idx_placex_geometry_reverse_placeNode
|
||||
ON placex USING gist (geometry)
|
||||
WHERE osm_type = 'N'
|
||||
AND rank_search between 5 and 25
|
||||
AND class = 'place'
|
||||
AND type != 'postcode'
|
||||
AND name is not null
|
||||
AND indexed_status = 0
|
||||
AND linked_place_id is null;
|
||||
```
|
||||
|
||||
You also need to grant the website user access to the `country_osm_grid` table:
|
||||
|
||||
```sql
|
||||
GRANT SELECT ON table country_osm_grid to "www-user";
|
||||
```
|
||||
|
||||
Replace the `www-user` with the user name of your website server if necessary.
|
||||
|
||||
You can now drop the unused indexes:
|
||||
|
||||
```sql
|
||||
DROP INDEX idx_placex_reverse_geometry;
|
||||
```
|
||||
|
||||
Finally, update all SQL functions:
|
||||
|
||||
```sh
|
||||
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
|
||||
```
|
||||
|
||||
## 3.0.0 -> 3.1.0
|
||||
|
||||
### Postcode Table
|
||||
|
||||
A new separate table for artificially computed postcode centroids was introduced.
|
||||
Migration to the new format is possible but **not recommended**.
|
||||
|
||||
Create postcode table and indexes, running the following SQL statements:
|
||||
|
||||
```sql
|
||||
CREATE TABLE location_postcode
|
||||
(place_id BIGINT, parent_place_id BIGINT, rank_search SMALLINT,
|
||||
rank_address SMALLINT, indexed_status SMALLINT, indexed_date TIMESTAMP,
|
||||
country_code varchar(2), postcode TEXT,
|
||||
geometry GEOMETRY(Geometry, 4326));
|
||||
CREATE INDEX idx_postcode_geometry ON location_postcode USING GIST (geometry);
|
||||
CREATE UNIQUE INDEX idx_postcode_id ON location_postcode USING BTREE (place_id);
|
||||
CREATE INDEX idx_postcode_postcode ON location_postcode USING BTREE (postcode);
|
||||
GRANT SELECT ON location_postcode TO "www-data";
|
||||
DROP TYPE IF EXISTS nearfeaturecentr CASCADE;
|
||||
CREATE TYPE nearfeaturecentr AS (
|
||||
place_id BIGINT,
|
||||
keywords int[],
|
||||
rank_address smallint,
|
||||
rank_search smallint,
|
||||
distance float,
|
||||
isguess boolean,
|
||||
postcode TEXT,
|
||||
centroid GEOMETRY
|
||||
);
|
||||
```
|
||||
|
||||
Add postcode column to `location_area` tables with SQL statement:
|
||||
|
||||
```sql
|
||||
ALTER TABLE location_area ADD COLUMN postcode TEXT;
|
||||
```
|
||||
|
||||
Then reimport the functions:
|
||||
|
||||
```sh
|
||||
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
|
||||
```
|
||||
|
||||
Create appropriate triggers with SQL:
|
||||
|
||||
```sql
|
||||
CREATE TRIGGER location_postcode_before_update BEFORE UPDATE ON location_postcode
|
||||
FOR EACH ROW EXECUTE PROCEDURE postcode_update();
|
||||
```
|
||||
|
||||
Finally populate the postcode table (will take a while):
|
||||
|
||||
```sh
|
||||
./utils/setup.php --calculate-postcodes --index --index-noanalyse
|
||||
```
|
||||
|
||||
This will create a working database. You may also delete the old artificial
|
||||
postcodes now. Note that this may be expensive and is not absolutely necessary.
|
||||
The following SQL statement will remove them:
|
||||
|
||||
```sql
|
||||
DELETE FROM place_addressline a USING placex p
|
||||
WHERE a.address_place_id = p.place_id and p.osm_type = 'P';
|
||||
ALTER TABLE placex DISABLE TRIGGER USER;
|
||||
DELETE FROM placex WHERE osm_type = 'P';
|
||||
ALTER TABLE placex ENABLE TRIGGER USER;
|
||||
```
|
||||
185
docs/admin/Setup-Nominatim-UI.md
Normal file
185
docs/admin/Setup-Nominatim-UI.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# Setting up the Nominatim UI
|
||||
|
||||
Nominatim is a search API, it does not provide a website interface on its
|
||||
own. [nominatim-ui](https://github.com/osm-search/nominatim-ui) offers a
|
||||
small website for testing your setup and inspecting the database content.
|
||||
|
||||
This section provides a quick start how to use nominatim-ui with your
|
||||
installation. For more details, please also have a look at the
|
||||
[README of nominatim-ui](https://github.com/osm-search/nominatim-ui/blob/master/README.md).
|
||||
|
||||
## Installing nominatim-ui
|
||||
|
||||
nominatim-ui does not need any special installation, just download, configure
|
||||
and run it.
|
||||
|
||||
Clone the source from github:
|
||||
|
||||
git clone https://github.com/osm-search/nominatim-ui
|
||||
|
||||
Copy the example configuration into the right place:
|
||||
|
||||
cd nominatim-ui
|
||||
cp dist/config.example.js dist/config.js
|
||||
|
||||
Now adapt the configuration to your needs. You need at least
|
||||
to change the `Nominatim_API_Endpoint` to point to your Nominatim installation.
|
||||
|
||||
Then you can just test it locally by spinning up a webserver in the `dist`
|
||||
directory. For example, with Python:
|
||||
|
||||
cd nominatim-ui/dist
|
||||
python3 -m http.server 8765
|
||||
|
||||
The website is now available at `http://localhost:8765`.
|
||||
|
||||
## Forwarding searches to nominatim-ui
|
||||
|
||||
Nominatim used to provide the search interface directly by itself when
|
||||
`format=html` was requested. For all endpoints except for `/reverse` and
|
||||
`/lookup` this even used to be the default.
|
||||
|
||||
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
|
||||
the UI.
|
||||
|
||||
### Setting up forwarding in Nginx
|
||||
|
||||
First of all make nominatim-ui available under `/ui` on your webserver:
|
||||
|
||||
``` nginx
|
||||
server {
|
||||
|
||||
# Here is the Nominatim setup as described in the Installation section
|
||||
|
||||
location /ui/ {
|
||||
alias <full path to the nominatim-ui directory>/dist/;
|
||||
index index.html;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now we need to find out if a URL should be forwarded to the UI. Add the
|
||||
following `map` commands *outside* the server section:
|
||||
|
||||
``` nginx
|
||||
# Inspect the format parameter in the query arguments. We are interested
|
||||
# if it is set to html or something else or if it is missing completely.
|
||||
map $args $format {
|
||||
default default;
|
||||
~(^|&)format=html(&|$) html;
|
||||
~(^|&)format= other;
|
||||
}
|
||||
|
||||
# Determine from the URI and the format parameter above if forwarding is needed.
|
||||
map $uri/$format $forward_to_ui {
|
||||
default 1; # The default is to forward.
|
||||
~^/ui 0; # If the URI point to the UI already, we are done.
|
||||
~/other$ 0; # An explicit non-html format parameter. No forwarding.
|
||||
~/reverse.*/default 0; # Reverse and lookup assume xml format when
|
||||
~/lookup.*/default 0; # no format parameter is given. No forwarding.
|
||||
}
|
||||
```
|
||||
|
||||
The `$forward_to_ui` parameter can now be used to conditionally forward the
|
||||
calls:
|
||||
|
||||
```
|
||||
# When no endpoint is given, default to search.
|
||||
# Need to add a rewrite so that the rewrite rules below catch it correctly.
|
||||
rewrite ^/$ /search;
|
||||
|
||||
location @php {
|
||||
# fastcgi stuff..
|
||||
if ($forward_to_ui) {
|
||||
rewrite ^(/[^/]*) https://yourserver.com/ui$1.html redirect;
|
||||
}
|
||||
}
|
||||
|
||||
location ~ [^/]\.php(/|$) {
|
||||
# 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.
|
||||
|
||||
### Setting up forwarding in Apache
|
||||
|
||||
First of all make nominatim-ui available in the `ui/` subdirectory where
|
||||
Nominatim is installed. For example, given you have set up an alias under
|
||||
`nominatim` like this:
|
||||
|
||||
``` apache
|
||||
Alias /nominatim /home/vagrant/build/website
|
||||
```
|
||||
|
||||
you need to insert the following rules for nominatim-ui before that alias:
|
||||
|
||||
```
|
||||
<Directory "/home/vagrant/nominatim-ui/dist">
|
||||
DirectoryIndex search.html
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
Alias /nominatim/ui /home/vagrant/nominatim-ui/dist
|
||||
```
|
||||
|
||||
Replace `/home/vagrant/nominatim-ui` with the directory where you have cloned
|
||||
nominatim-ui.
|
||||
|
||||
!!! important
|
||||
The alias for nominatim-ui must come before the alias for the Nominatim
|
||||
website directory.
|
||||
|
||||
To set up forwarding, the Apache rewrite module is needed. Enable it with:
|
||||
|
||||
``` sh
|
||||
sudo a2enmod rewrite
|
||||
```
|
||||
|
||||
Then add rewrite rules to the `Directory` directive of the Nominatim website
|
||||
directory like this:
|
||||
|
||||
``` apache
|
||||
<Directory "/home/vagrant/build/website">
|
||||
Options FollowSymLinks MultiViews
|
||||
AddType text/html .php
|
||||
Require all granted
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# This must correspond to the URL where nominatim can be found.
|
||||
RewriteBase "/nominatim/"
|
||||
|
||||
# If no endpoint is given, then use search.
|
||||
RewriteRule ^(/|$) "search.php"
|
||||
|
||||
# If format-html is explicity requested, forward to the UI.
|
||||
RewriteCond %{QUERY_STRING} "format=html"
|
||||
RewriteRule ^([^/]+).php ui/$1.html [R,END]
|
||||
# Same but .php suffix is missing.
|
||||
RewriteCond %{QUERY_STRING} "format=html"
|
||||
RewriteRule ^([^/]+) ui/$1.html [R,END]
|
||||
|
||||
# If no format parameter is there then forward anything
|
||||
# but /reverse and /lookup to the UI.
|
||||
RewriteCond %{QUERY_STRING} "!format="
|
||||
RewriteCond %{REQUEST_URI} "!/lookup"
|
||||
RewriteCond %{REQUEST_URI} "!/reverse"
|
||||
RewriteRule ^([^/]+).php ui/$1.html [R,END]
|
||||
# Same but .php suffix is missing.
|
||||
RewriteCond %{QUERY_STRING} "!format="
|
||||
RewriteCond %{REQUEST_URI} "!/lookup"
|
||||
RewriteCond %{REQUEST_URI} "!/reverse"
|
||||
RewriteRule ^([^/]+) ui/$1.html [R,END]
|
||||
</Directory>
|
||||
```
|
||||
|
||||
Restart Apache and the UI should be available.
|
||||
67
docs/admin/Update.md
Normal file
67
docs/admin/Update.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Updating the Database
|
||||
|
||||
There are many different ways to update your Nominatim database.
|
||||
The following section describes how to keep it up-to-date with Pyosmium.
|
||||
For a list of other methods see the output of `./utils/update.php --help`.
|
||||
|
||||
!!! important
|
||||
If you have configured a flatnode file for the import, then you
|
||||
need to keep this flatnode file around for updates.
|
||||
|
||||
#### Installing the newest version of Pyosmium
|
||||
|
||||
It is recommended to install Pyosmium via pip. Make sure to use python3.
|
||||
Run (as the same user who will later run the updates):
|
||||
|
||||
```sh
|
||||
pip3 install --user osmium
|
||||
```
|
||||
|
||||
Nominatim needs a tool called `pyosmium-get-changes` which comes with
|
||||
Pyosmium. You need to tell Nominatim where to find it. Add the
|
||||
following line to your `settings/local.php`:
|
||||
|
||||
@define('CONST_Pyosmium_Binary', '/home/user/.local/bin/pyosmium-get-changes');
|
||||
|
||||
The path above is fine if you used the `--user` parameter with pip.
|
||||
Replace `user` with your user name.
|
||||
|
||||
#### Setting up the update process
|
||||
|
||||
Next the update needs to be initialised. By default Nominatim is configured
|
||||
to update using the global minutely diffs.
|
||||
|
||||
If you want a different update source you will need to add some settings
|
||||
to `settings/local.php`. For example, to use the daily country extracts
|
||||
diffs for Ireland from Geofabrik add the following:
|
||||
|
||||
// base URL of the replication service
|
||||
@define('CONST_Replication_Url', 'https://download.geofabrik.de/europe/ireland-and-northern-ireland-updates');
|
||||
// How often upstream publishes diffs
|
||||
@define('CONST_Replication_Update_Interval', '86400');
|
||||
// How long to sleep if no update found yet
|
||||
@define('CONST_Replication_Recheck_Interval', '900');
|
||||
|
||||
To set up the update process now run the following command:
|
||||
|
||||
./utils/update.php --init-updates
|
||||
|
||||
It outputs the date where updates will start. Recheck that this date is
|
||||
what you expect.
|
||||
|
||||
The `--init-updates` command needs to be rerun whenever the replication service
|
||||
is changed.
|
||||
|
||||
#### Updating Nominatim
|
||||
|
||||
The following command will keep your database constantly up to date:
|
||||
|
||||
./utils/update.php --import-osmosis-all
|
||||
|
||||
(Note that even though the old name "import-osmosis-all" has been kept for
|
||||
compatibility reasons, Osmosis is not required to run this - it uses pyosmium
|
||||
behind the scenes.)
|
||||
|
||||
If you have imported multiple country extracts and want to keep them
|
||||
up-to-date, [Advanced installations section](Advanced-Installations.md) contains instructions
|
||||
to set up and update multiple country extracts.
|
||||
154
docs/api/Details.md
Normal file
154
docs/api/Details.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# Place details
|
||||
|
||||
Show all details about a single place saved in the database.
|
||||
|
||||
!!! warning
|
||||
The details page exists for debugging only. You may not use it in scripts
|
||||
or to automatically query details about a result.
|
||||
See [Nominatim Usage Policy](https://operations.osmfoundation.org/policies/nominatim/).
|
||||
|
||||
|
||||
## Parameters
|
||||
|
||||
The details API supports the following two request formats:
|
||||
|
||||
``` xml
|
||||
https://nominatim.openstreetmap.org/details?osmtype=[N|W|R]&osmid=<value>&class=<value>
|
||||
```
|
||||
|
||||
`osmtype` and `osmid` are required parameters. The type is one of node (N), way (W)
|
||||
or relation (R). The id must be a number. The `class` parameter is optional and
|
||||
allows to distinguish between entries, when the corresponding OSM object has more
|
||||
than one main tag. For example, when a place is tagged with `tourism=hotel` and
|
||||
`amenity=restaurant`, there will be two place entries in Nominatim, one for a
|
||||
restaurant, one for a hotel. You need to specify `class=tourism` or `class=amentity`
|
||||
to get exactly the one you want. If there are multiple places in the database
|
||||
but the `class` parameter is left out, then one of the places will be chosen
|
||||
at random and displayed.
|
||||
|
||||
``` xml
|
||||
https://nominatim.openstreetmap.org/details?place_id=<value>
|
||||
```
|
||||
|
||||
Place IDs are assigned sequentially during Nominatim data import. The ID
|
||||
for a place is different between Nominatim installation (servers) and
|
||||
changes when data gets reimported. Therefore it cannot be used as
|
||||
a permanent id and shouldn't be used in bug reports.
|
||||
|
||||
|
||||
Additional optional parameters are explained below.
|
||||
|
||||
### Output format
|
||||
|
||||
* `json_callback=<string>`
|
||||
|
||||
Wrap JSON output in a callback function (JSONP) i.e. `<string>(<json>)`.
|
||||
|
||||
* `pretty=[0|1]`
|
||||
|
||||
Add indentation to make it more human-readable. (Default: 0)
|
||||
|
||||
|
||||
### Output details
|
||||
|
||||
* `addressdetails=[0|1]`
|
||||
|
||||
Include a breakdown of the address into elements. (Default: 0)
|
||||
|
||||
* `keywords=[0|1]`
|
||||
|
||||
Include a list of name keywords and address keywords (word ids). (Default: 0)
|
||||
|
||||
* `linkedplaces=[0|1]`
|
||||
|
||||
Include a details of places that are linked with this one. Places get linked
|
||||
together when they are different forms of the same physical object. Nominatim
|
||||
links two kinds of objects together: place nodes get linked with the
|
||||
corresponding administrative boundaries. Waterway relations get linked together with their
|
||||
members.
|
||||
(Default: 1)
|
||||
|
||||
* `hierarchy=[0|1]`
|
||||
|
||||
Include details of places lower in the address hierarchy. (Default: 0)
|
||||
|
||||
* `group_hierarchy=[0|1]`
|
||||
|
||||
For JSON output will group the places by type. (Default: 0)
|
||||
|
||||
* `polygon_geojson=[0|1]`
|
||||
|
||||
Include geometry of result. (Default: 0)
|
||||
|
||||
### Language of results
|
||||
|
||||
* `accept-language=<browser language string>`
|
||||
|
||||
Preferred language order for showing result, overrides the value
|
||||
specified in the "Accept-Language" HTTP header.
|
||||
Either use a standard RFC2616 accept-language string or a simple
|
||||
comma-separated list of language codes.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
##### 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)
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"place_id": 85993608,
|
||||
"parent_place_id": 72765313,
|
||||
"osm_type": "W",
|
||||
"osm_id": 38210407,
|
||||
"category": "place",
|
||||
"type": "square",
|
||||
"admin_level": "15",
|
||||
"localname": "Pariser Platz",
|
||||
"names": {
|
||||
"name": "Pariser Platz",
|
||||
"name:be": "Парыжская плошча",
|
||||
"name:de": "Pariser Platz",
|
||||
"name:es": "Plaza de París",
|
||||
"name:he": "פאריזר פלאץ",
|
||||
"name:ko": "파리저 광장",
|
||||
"name:la": "Forum Parisinum",
|
||||
"name:ru": "Парижская площадь",
|
||||
"name:uk": "Паризька площа",
|
||||
"name:zh": "巴黎廣場"
|
||||
},
|
||||
"addresstags": {
|
||||
"postcode": "10117"
|
||||
},
|
||||
"housenumber": null,
|
||||
"calculated_postcode": "10117",
|
||||
"country_code": "de",
|
||||
"indexed_date": "2018-08-18T17:02:45+00:00",
|
||||
"importance": 0.339401620591472,
|
||||
"calculated_importance": 0.339401620591472,
|
||||
"extratags": {
|
||||
"wikidata": "Q156716",
|
||||
"wikipedia": "de:Pariser Platz"
|
||||
},
|
||||
"calculated_wikipedia": "de:Pariser_Platz",
|
||||
"rank_address": 30,
|
||||
"rank_search": 30,
|
||||
"isarea": true,
|
||||
"centroid": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
13.3786822618517,
|
||||
52.5163654
|
||||
]
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
13.3786822618517,
|
||||
52.5163654
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
61
docs/api/Faq.md
Normal file
61
docs/api/Faq.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
## API Results
|
||||
|
||||
#### 1. The address of my search results contains far-away places that don't belong there.
|
||||
|
||||
Nominatim computes the address from two sources in the OpenStreetMap data:
|
||||
from administrative boundaries and from place nodes. Boundaries are the more
|
||||
useful source. They precisely describe an area. So it is very clear for
|
||||
Nominatim if a point belongs to an area or not. Place nodes are more complicated.
|
||||
These are only points without any precise extent. So Nominatim has to take a
|
||||
guess and assume that an address belongs to the closest place node it can find.
|
||||
In an ideal world, Nominatim would not need the place nodes but there are
|
||||
many places on earth where there are no precise boundaries available for
|
||||
all parts that make up an address. This is in particular true for the more
|
||||
local address parts, like villages and suburbs. Therefore it is not possible
|
||||
to completely dismiss place nodes. And sometimes they sneak in where they
|
||||
don't belong.
|
||||
|
||||
As a OpenStreetMap mapper, you can improve the situation in two ways: if you
|
||||
see a place node for which already an administrative area exists, then you
|
||||
should _link_ the two by adding the node with a 'label' role to the boundary
|
||||
relation. If there is no administrative area, you can add the approximate
|
||||
extent of the place and tag it place=<something> as well.
|
||||
|
||||
#### 2. When doing reverse search, the address details have parts that don't contain the point I was looking up.
|
||||
|
||||
There is a common misconception how the reverse API call works in Nominatim.
|
||||
Reverse does not give you the address of the point you asked for. Reverse
|
||||
returns the closest object to the point you asked for and then returns the
|
||||
address of that object. Now, if you are close to a border, then the closest
|
||||
object may be across that border. When Nominatim then returns the address,
|
||||
it contains the county/state/country across the border.
|
||||
|
||||
#### 3. I get different counties/states/countries when I change the zoom parameter in the reverse query. How is that possible?
|
||||
|
||||
This is basically the same problem as in the previous answer.
|
||||
The zoom level influences at which [search rank](https://wiki.openstreetmap.org/wiki/Nominatim/Development_overview#Country_to_street_level) Nominatim starts looking
|
||||
for the closest object. So the closest house number maybe on one side of the
|
||||
border while the closest street is on the other. As the address details contain
|
||||
the address of the closest object found, you might sometimes get one result,
|
||||
sometimes the other for the closest point.
|
||||
|
||||
#### 4. Can you return the continent?
|
||||
|
||||
Nominatim assigns each map feature one country. Those outside any administrative
|
||||
boundaries are assigned a special no-country. Continents or other super-national
|
||||
administrations (e.g. European Union, NATO, Custom unions) are not supported,
|
||||
see also [Administrative Boundary](https://wiki.openstreetmap.org/wiki/Tag:boundary%3Dadministrative#Super-national_administrations).
|
||||
|
||||
#### 5. Can you return the timezone?
|
||||
|
||||
See this separate OpenStreetMap-based project [Timezone Boundary Builder](https://github.com/evansiroky/timezone-boundary-builder).
|
||||
|
||||
#### 6. I want to download a list of streets/restaurants of a city/region
|
||||
|
||||
The [Overpass API](https://wiki.openstreetmap.org/wiki/Overpass_API) is more
|
||||
suited for these kinds of queries.
|
||||
|
||||
That said if you installed your own Nominatim instance you can use the
|
||||
`/utils/export.php` PHP script as basis to return such lists.
|
||||
162
docs/api/Lookup.md
Normal file
162
docs/api/Lookup.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# Address lookup
|
||||
|
||||
The lookup API allows to query the address and other details of one or
|
||||
multiple OSM objects like node, way or relation.
|
||||
|
||||
## Parameters
|
||||
|
||||
The lookup API has the following format:
|
||||
|
||||
```
|
||||
https://nominatim.openstreetmap.org/lookup?osm_ids=[N|W|R]<value>,…,…,&<params>
|
||||
```
|
||||
|
||||
`osm_ids` is mandatory and must contain a comma-separated list of OSM ids each
|
||||
prefixed with its type, one of node(N), way(W) or relation(R). Up to 50 ids
|
||||
can be queried at the same time.
|
||||
|
||||
Additional optional parameters are explained below.
|
||||
|
||||
### Output format
|
||||
|
||||
* `format=[xml|json|jsonv2|geojson|geocodejson]`
|
||||
|
||||
See [Place Output Formats](Output.md) for details on each format. (Default: xml)
|
||||
|
||||
* `json_callback=<string>`
|
||||
|
||||
Wrap JSON output in a callback function (JSONP) i.e. `<string>(<json>)`.
|
||||
Only has an effect for JSON output formats.
|
||||
|
||||
### Output details
|
||||
|
||||
* `addressdetails=[0|1]`
|
||||
|
||||
Include a breakdown of the address into elements. (Default: 0)
|
||||
|
||||
|
||||
* `extratags=[0|1]`
|
||||
|
||||
Include additional information in the result if available,
|
||||
e.g. wikipedia link, opening hours. (Default: 0)
|
||||
|
||||
|
||||
* `namedetails=[0|1]`
|
||||
|
||||
Include a list of alternative names in the results. These may include
|
||||
language variants, references, operator and brand. (Default: 0)
|
||||
|
||||
|
||||
### Language of results
|
||||
|
||||
* `accept-language=<browser language string>`
|
||||
|
||||
Preferred language order for showing search results, overrides the value
|
||||
specified in the "Accept-Language" HTTP header.
|
||||
Either use a standard RFC2616 accept-language string or a simple
|
||||
comma-separated list of language codes.
|
||||
|
||||
### Polygon output
|
||||
|
||||
* `polygon_geojson=1`
|
||||
* `polygon_kml=1`
|
||||
* `polygon_svg=1`
|
||||
* `polygon_text=1`
|
||||
|
||||
Output geometry of results as a GeoJSON, KML, SVG or WKT. Only one of these
|
||||
options can be used at a time. (Default: 0)
|
||||
|
||||
* `polygon_threshold=0.0`
|
||||
|
||||
Return a simplified version of the output geometry. The parameter is the
|
||||
tolerance in degrees with which the geometry may differ from the original
|
||||
geometry. Topology is preserved in the result. (Default: 0.0)
|
||||
|
||||
### Other
|
||||
|
||||
* `email=<valid email address>`
|
||||
|
||||
If you are making large numbers of request please include an appropriate email
|
||||
address to identify your requests. See Nominatim's [Usage Policy](https://operations.osmfoundation.org/policies/nominatim/) for more details.
|
||||
|
||||
* `debug=[0|1]`
|
||||
|
||||
Output assorted developer debug information. Data on internals of Nominatim's
|
||||
"Search Loop" logic, and SQL queries. The output is (rough) HTML format.
|
||||
This overrides the specified machine readable format. (Default: 0)
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
##### XML
|
||||
|
||||
[https://nominatim.openstreetmap.org/lookup?osm_ids=R146656,W104393803,N240109189](https://nominatim.openstreetmap.org/lookup?osm_ids=R146656,W104393803,N240109189)
|
||||
|
||||
```xml
|
||||
<lookupresults timestamp="Mon, 29 Jun 15 18:01:33 +0000" attribution="Data © OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright" querystring="R146656,W104393803,N240109189" polygon="false">
|
||||
<place place_id="127761056" osm_type="relation" osm_id="146656" place_rank="16" lat="53.4791466" lon="-2.2447445" display_name="Manchester, Greater Manchester, North West England, England, United Kingdom" class="boundary" type="administrative" importance="0.704893333438333">
|
||||
<city>Manchester</city>
|
||||
<county>Greater Manchester</county>
|
||||
<state_district>North West England</state_district>
|
||||
<state>England</state>
|
||||
<country>United Kingdom</country>
|
||||
<country_code>gb</country_code>
|
||||
</place>
|
||||
<place place_id="77769745" osm_type="way" osm_id="104393803" place_rank="30" lat="52.5162024" lon="13.3777343363579" display_name="Brandenburg Gate, 1, Pariser Platz, Mitte, Berlin, 10117, Germany" class="tourism" type="attraction" importance="0.443472858361592">
|
||||
<attraction>Brandenburg Gate</attraction>
|
||||
<house_number>1</house_number>
|
||||
<pedestrian>Pariser Platz</pedestrian>
|
||||
<suburb>Mitte</suburb>
|
||||
<city_district>Mitte</city_district>
|
||||
<city>Berlin</city>
|
||||
<state>Berlin</state>
|
||||
<postcode>10117</postcode>
|
||||
<country>Germany</country>
|
||||
<country_code>de</country_code>
|
||||
</place>
|
||||
<place place_id="2570600569" osm_type="node" osm_id="240109189" place_rank="15" lat="52.5170365" lon="13.3888599" display_name="Berlin, Germany" class="place" type="city" importance="0.822149797630868">
|
||||
<city>Berlin</city>
|
||||
<state>Berlin</state>
|
||||
<country>Germany</country>
|
||||
<country_code>de</country_code>
|
||||
</place>
|
||||
</lookupresults>
|
||||
```
|
||||
|
||||
##### JSON with extratags
|
||||
|
||||
[https://nominatim.openstreetmap.org/lookup?osm_ids=W50637691&format=json](https://nominatim.openstreetmap.org/lookup?osm_ids=W50637691&format=json)
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"place_id": "84271358",
|
||||
"licence": "Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright",
|
||||
"osm_type": "way",
|
||||
"osm_id": "50637691",
|
||||
"lat": "52.39955055",
|
||||
"lon": "13.04806574678",
|
||||
"display_name": "Brandenburger Tor, Brandenburger Straße, Nördliche Innenstadt, Innenstadt, Potsdam, Brandenburg, 14467, Germany",
|
||||
"class": "historic",
|
||||
"type": "city_gate",
|
||||
"importance": "0.221233780277011",
|
||||
"address": {
|
||||
"address29": "Brandenburger Tor",
|
||||
"pedestrian": "Brandenburger Straße",
|
||||
"suburb": "Nördliche Innenstadt",
|
||||
"city": "Potsdam",
|
||||
"state": "Brandenburg",
|
||||
"postcode": "14467",
|
||||
"country": "Germany",
|
||||
"country_code": "de"
|
||||
},
|
||||
"extratags": {
|
||||
"image": "http://commons.wikimedia.org/wiki/File:Potsdam_brandenburger_tor.jpg",
|
||||
"wikidata": "Q695045",
|
||||
"wikipedia": "de:Brandenburger Tor (Potsdam)",
|
||||
"wheelchair": "yes",
|
||||
"description": "Kleines Brandenburger Tor in Potsdam"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
295
docs/api/Output.md
Normal file
295
docs/api/Output.md
Normal file
@@ -0,0 +1,295 @@
|
||||
# Place Output
|
||||
|
||||
The [/reverse](Reverse.md), [/search](Search.md) and [/lookup](Lookup.md)
|
||||
API calls produce very similar output which is explained in this section.
|
||||
There is one section for each format. The format correspond to what was
|
||||
selected via the `format` parameter.
|
||||
|
||||
## JSON
|
||||
|
||||
The JSON format returns an array of places (for search and lookup) or
|
||||
a single place (for reverse) of the following format:
|
||||
|
||||
```
|
||||
{
|
||||
"place_id": "100149",
|
||||
"licence": "Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright",
|
||||
"osm_type": "node",
|
||||
"osm_id": "107775",
|
||||
"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",
|
||||
"class": "place",
|
||||
"type": "city",
|
||||
"importance": 0.9654895765402,
|
||||
"icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png",
|
||||
"address": {
|
||||
"city": "London",
|
||||
"state_district": "Greater London",
|
||||
"state": "England",
|
||||
"postcode": "SW1A 2DU",
|
||||
"country": "United Kingdom",
|
||||
"country_code": "gb"
|
||||
},
|
||||
"extratags": {
|
||||
"capital": "yes",
|
||||
"website": "http://www.london.gov.uk",
|
||||
"wikidata": "Q84",
|
||||
"wikipedia": "en:London",
|
||||
"population": "8416535"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The possible fields are:
|
||||
|
||||
* `place_id` - reference to the Nominatim internal database ID ([see notes](#place_id-is-not-a-persistent-id))
|
||||
* `osm_type`, `osm_id` - reference to the OSM object ([see notes](#osm-reference))
|
||||
* `boundingbox` - area of corner coordinates ([see notes](#boundingbox))
|
||||
* `lat`, `lon` - latitude and longitude of the centroid of the object
|
||||
* `display_name` - full comma-separated address
|
||||
* `class`, `type` - key and value of the main OSM tag
|
||||
* `importance` - computed importance rank
|
||||
* `icon` - link to class icon (if available)
|
||||
* `address` - dictionary of address details (only with `addressdetails=1`,
|
||||
[see notes](#addressdetails))
|
||||
* `extratags` - dictionary with additional useful tags like website or maxspeed
|
||||
(only with `extratags=1`)
|
||||
* `namedetails` - dictionary with full list of available names including ref etc.
|
||||
* `geojson`, `svg`, `geotext`, `geokml` - full geometry
|
||||
(only with the appropriate `polygon_*` parameter)
|
||||
|
||||
## JSONv2
|
||||
|
||||
This is the same as the JSON format with two changes:
|
||||
|
||||
* `class` renamed to `category`
|
||||
* additional field `place_rank` with the search rank of the object
|
||||
|
||||
## GeoJSON
|
||||
|
||||
This format follows the [RFC7946](https://geojson.org). Every feature includes
|
||||
a bounding box (`bbox`).
|
||||
|
||||
The properties object has the following fields:
|
||||
|
||||
* `place_id` - reference to the Nominatim internal database ID ([see notes](#place_id-is-not-a-persistent-id))
|
||||
* `osm_type`, `osm_id` - reference to the OSM object ([see notes](#osm-reference))
|
||||
* `category`, `type` - key and value of the main OSM tag
|
||||
* `display_name` - full comma-separated address
|
||||
* `place_rank` - class search rank
|
||||
* `importance` - computed importance rank
|
||||
* `icon` - link to class icon (if available)
|
||||
* `address` - dictionary of address details (only with `addressdetails=1`,
|
||||
[see notes](#addressdetails))
|
||||
* `extratags` - dictionary with additional useful tags like `website` or `maxspeed`
|
||||
(only with `extratags=1`)
|
||||
* `namedetails` - dictionary with full list of available names including ref etc.
|
||||
|
||||
Use `polygon_geojson` to output the full geometry of the object instead
|
||||
of the centroid.
|
||||
|
||||
## GeocodeJSON
|
||||
|
||||
The GeocodeJSON format follows the
|
||||
[GeocodeJSON spec 0.1.0](https://github.com/geocoders/geocodejson-spec).
|
||||
The following feature attributes are implemented:
|
||||
|
||||
* `osm_type`, `osm_id` - reference to the OSM object (unofficial extension, [see notes](#osm-reference))
|
||||
* `type` - value of the main tag of the object (e.g. residential, restaurant, ...)
|
||||
* `label` - full comma-separated address
|
||||
* `name` - localised name of the place
|
||||
* `housenumber`, `street`, `locality`, `district`, `postcode`, `city`,
|
||||
`county`, `state`, `country` -
|
||||
provided when it can be determined from the address
|
||||
* `admin` - list of localised names of administrative boundaries (only with `addressdetails=1`)
|
||||
|
||||
Use `polygon_geojson` to output the full geometry of the object instead
|
||||
of the centroid.
|
||||
|
||||
## XML
|
||||
|
||||
The XML response returns one or more place objects in slightly different
|
||||
formats depending on the API call.
|
||||
|
||||
### Reverse
|
||||
|
||||
```
|
||||
<reversegeocode timestamp="Sat, 11 Aug 18 11:53:21 +0000"
|
||||
attribution="Data © OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright"
|
||||
querystring="lat=48.400381&lon=11.745876&zoom=5&format=xml">
|
||||
<result place_id="179509537" osm_type="relation" osm_id="2145268" ref="BY" place_rank="15" address_rank="15"
|
||||
lat="48.9467562" lon="11.4038717"
|
||||
boundingbox="47.2701114,50.5647142,8.9763497,13.8396373">
|
||||
Bavaria, Germany
|
||||
</result>
|
||||
<addressparts>
|
||||
<state>Bavaria</state>
|
||||
<country>Germany</country>
|
||||
<country_code>de</country_code>
|
||||
</addressparts>
|
||||
<extratags>
|
||||
<tag key="place" value="state"/>
|
||||
<tag key="wikidata" value="Q980"/>
|
||||
<tag key="wikipedia" value="de:Bayern"/>
|
||||
<tag key="population" value="12520000"/>
|
||||
<tag key="name:prefix" value="Freistaat"/>
|
||||
</extratags>
|
||||
</reversegeocode>
|
||||
```
|
||||
|
||||
The attributes of the outer `reversegeocode` element return generic information
|
||||
about the query, including the time when the response was sent (in UTC),
|
||||
attribution to OSM and the original querystring.
|
||||
|
||||
The place information can be found in the `result` element. The attributes of that element contain:
|
||||
|
||||
* `place_id` - reference to the Nominatim internal database ID ([see notes](#place_id-is-not-a-persistent-id))
|
||||
* `osm_type`, `osm_id` - reference to the OSM object ([see notes](#osm-reference))
|
||||
* `ref` - content of `ref` tag if it exists
|
||||
* `lat`, `lon` - latitude and longitude of the centroid of the object
|
||||
* `boundingbox` - comma-separated list of corner coordinates ([see notes](#boundingbox))
|
||||
|
||||
The full address of the result can be found in the content of the
|
||||
`result` element as a comma-separated list.
|
||||
|
||||
Additional information requested with `addressdetails=1`, `extratags=1` and
|
||||
`namedetails=1` can be found in extra elements.
|
||||
|
||||
### Search and Lookup
|
||||
|
||||
```
|
||||
<searchresults timestamp="Sat, 11 Aug 18 11:55:35 +0000"
|
||||
attribution="Data © OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright"
|
||||
querystring="london" polygon="false" exclude_place_ids="100149"
|
||||
more_url="https://nominatim.openstreetmap.org/search.php?q=london&addressdetails=1&extratags=1&exclude_place_ids=100149&format=xml&accept-language=en-US%2Cen%3Bq%3D0.7%2Cde%3Bq%3D0.3">
|
||||
<place place_id="100149" osm_type="node" osm_id="107775" place_rank="15" address_rank="15"
|
||||
boundingbox="51.3473219,51.6673219,-0.2876474,0.0323526" lat="51.5073219" lon="-0.1276474"
|
||||
display_name="London, Greater London, England, SW1A 2DU, United Kingdom"
|
||||
class="place" type="city" importance="0.9654895765402"
|
||||
icon="https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png">
|
||||
<extratags>
|
||||
<tag key="capital" value="yes"/>
|
||||
<tag key="website" value="http://www.london.gov.uk"/>
|
||||
<tag key="wikidata" value="Q84"/>
|
||||
<tag key="wikipedia" value="en:London"/>
|
||||
<tag key="population" value="8416535"/>
|
||||
</extratags>
|
||||
<city>London</city>
|
||||
<state_district>Greater London</state_district>
|
||||
<state>England</state>
|
||||
<postcode>SW1A 2DU</postcode>
|
||||
<country>United Kingdom</country>
|
||||
<country_code>gb</country_code>
|
||||
</place>
|
||||
</searchresults>
|
||||
```
|
||||
|
||||
The attributes of the outer `searchresults` or `lookupresults` element return
|
||||
generic information about the query:
|
||||
|
||||
* `timestamp` - UTC time when the response was sent
|
||||
* `attribution` - OSM licensing information
|
||||
* `querystring` - original query
|
||||
* `polygon` - true when extra geometry information was requested
|
||||
* `exclude_place_ids` - IDs of places that should be ignored in a follow-up request
|
||||
* `more_url` - search call that will yield additional results for the query
|
||||
just sent
|
||||
|
||||
The place information can be found in the `place` elements, of which there may
|
||||
be more than one. The attributes of that element contain:
|
||||
|
||||
* `place_id` - reference to the Nominatim internal database ID ([see notes](#place_id-is-not-a-persistent-id))
|
||||
* `osm_type`, `osm_id` - reference to the OSM object ([see notes](#osm-reference))
|
||||
* `ref` - content of `ref` tag if it exists
|
||||
* `lat`, `lon` - latitude and longitude of the centroid of the object
|
||||
* `boundingbox` - comma-separated list of corner coordinates ([see notes](#boundingbox))
|
||||
* `place_rank` - class [search rank](../develop/Ranking#search-rank)
|
||||
* `address_rank` - place [address rank](../develop/Ranking#address-rank)
|
||||
* `display_name` - full comma-separated address
|
||||
* `class`, `type` - key and value of the main OSM tag
|
||||
* `importance` - computed importance rank
|
||||
* `icon` - link to class icon (if available)
|
||||
|
||||
When `addressdetails=1` is requested, the localised address parts appear
|
||||
as subelements with the type of the address part.
|
||||
|
||||
Additional information requested with `extratags=1` and `namedetails=1` can
|
||||
be found in extra elements as sub-element of `extratags` and `namedetails`
|
||||
respectively.
|
||||
|
||||
|
||||
## Notes on field values
|
||||
|
||||
### place_id is not a persistent id
|
||||
|
||||
The `place_id` is an internal identifier that is assigned data is imported
|
||||
into a Nominatim database. The same OSM object will have a different value
|
||||
on another server. It may even change its ID on the same server when it is
|
||||
removed and reimported while updating the database with fresh OSM data.
|
||||
It is thus not useful to treat it as permanent for later use.
|
||||
|
||||
The combination `osm_type`+`osm_id` is slighly better but remember in
|
||||
OpenStreetMap mappers can delete, split, recreate places (and those
|
||||
get a new `osm_id`), there is no link between those old and new ids.
|
||||
Places can also change their meaning without changing their `osm_id`,
|
||||
e.g. when a restaurant is retagged as supermarket. For a more in-depth
|
||||
discussion see [Permanent ID](https://wiki.openstreetmap.org/wiki/Permanent_ID).
|
||||
|
||||
If you need an ID that is consistent over multiple installations of Nominatim,
|
||||
then you should use the combination of `osm_type`+`osm_id`+`class`.
|
||||
|
||||
### OSM reference
|
||||
|
||||
Nominatim may sometimes return special objects that do not correspond directly
|
||||
to an object in OpenStreetMap. These are:
|
||||
|
||||
* **Postcodes**. Nominatim returns an postcode point created from all mapped
|
||||
postcodes of the same name. The class and type of these object is `place=postcdode`.
|
||||
No `osm_type` and `osm_id` are included in the result.
|
||||
* **Housenumber interpolations**. Nominatim returns a single interpolated
|
||||
housenumber from the interpolation way. The class and type are `place=house`
|
||||
and `osm_type` and `osm_id` correspond to the interpolation way in OSM.
|
||||
* **TIGER housenumber.** Nominatim returns a single interpolated housenumber
|
||||
from the TIGER data. The class and type are `place=house`
|
||||
and `osm_type` and `osm_id` correspond to the street mentioned in the result.
|
||||
|
||||
Please note that the `osm_type` and `osm_id` returned may be changed in the
|
||||
future. You should not expect to only find `node`, `way` and `relation` for
|
||||
the type.
|
||||
|
||||
### boundingbox
|
||||
|
||||
Comma separated list of min latitude, max latitude, min longitude, max longitude.
|
||||
The whole planet would be `-90,90,-180,180`.
|
||||
|
||||
Can be used to pan and center the map on the result, for example with leafletjs
|
||||
mapping library
|
||||
`map.fitBounds([[bbox[0],bbox[2]],[bbox[1],bbox[3]]], {padding: [20, 20], maxzoom: 16});`
|
||||
|
||||
Bounds crossing the antimeridian have a min latitude -180 and max latitude 180,
|
||||
essentially covering the entire planet
|
||||
(see [issue 184](https://github.com/openstreetmap/Nominatim/issues/184)).
|
||||
|
||||
### addressdetails
|
||||
|
||||
Address details in the xml and json formats return a list of names together
|
||||
with a designation label. Per default the following labels may appear:
|
||||
|
||||
* continent
|
||||
* country, country_code
|
||||
* region, state, state_district, county
|
||||
* municipality, city, town, village
|
||||
* city_district, district, borough, suburb, subdivision
|
||||
* hamlet, croft, isolated_dwelling
|
||||
* neighbourhood, allotments, quarter
|
||||
* city_block, residental, farm, farmyard, industrial, commercial, retail
|
||||
* road
|
||||
* house_number, house_name
|
||||
* emergency, historic, military, natural, landuse, place, railway,
|
||||
man_made, aerialway, boundary, amenity, aeroway, club, craft, leisure,
|
||||
office, mountain_pass, shop, tourism, bridge, tunnel, waterway
|
||||
|
||||
They roughly correspond to the classification of the OpenStreetMap data
|
||||
according to either the `place` tag or the main key of the object.
|
||||
14
docs/api/Overview.md
Normal file
14
docs/api/Overview.md
Normal file
@@ -0,0 +1,14 @@
|
||||
### Nominatim API
|
||||
|
||||
Nominatim indexes named (or numbered) features within the OpenStreetMap (OSM) dataset and a subset of other unnamed features (pubs, hotels, churches, etc).
|
||||
|
||||
Its API has the following endpoints for querying the data:
|
||||
|
||||
* __[/search](Search.md)__ - search OSM objects by name or type
|
||||
* __[/reverse](Reverse.md)__ - search OSM object by their location
|
||||
* __[/lookup](Lookup.md)__ - look up address details for OSM objects by their ID
|
||||
* __[/status](Status.md)__ - query the status of the server
|
||||
* __/deletable__ - list objects that have been deleted in OSM but are held
|
||||
back in Nominatim in case the deletion was accidental
|
||||
* __/polygons__ - list of broken polygons detected by Nominatim
|
||||
* __[/details](Details.md)__ - show internal details for an object (for debugging only)
|
||||
285
docs/api/Reverse.md
Normal file
285
docs/api/Reverse.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# Reverse Geocoding
|
||||
|
||||
Reverse geocoding generates an address from a latitude and longitude.
|
||||
|
||||
## How it works
|
||||
|
||||
The reverse geocoding API does not exactly compute the address for the
|
||||
coordinate it receives. It works by finding the closest suitable OSM object
|
||||
and returning its address information. This may occasionally lead to
|
||||
unexpected results.
|
||||
|
||||
First of all, Nominatim only includes OSM objects in
|
||||
its index that are suitable for searching. Small, unnamed paths for example
|
||||
are missing from the database and can therefore not be used for reverse
|
||||
geocoding either.
|
||||
|
||||
The other issue to be aware of is that the closest OSM object may not always
|
||||
have a similar enough address to the coordinate you were requesting. For
|
||||
example, in dense city areas it may belong to a completely different street.
|
||||
|
||||
|
||||
## Parameters
|
||||
|
||||
The main format of the reverse API is
|
||||
|
||||
```
|
||||
https://nominatim.openstreetmap.org/reverse?lat=<value>&lon=<value>&<params>
|
||||
```
|
||||
|
||||
where `lat` and `lon` are latitude and longitutde of a coordinate in WGS84
|
||||
projection. The API returns exactly one result or an error when the coordinate
|
||||
is in an area with no OSM data coverage.
|
||||
|
||||
Additional paramters are accepted as listed below.
|
||||
|
||||
!!! warning "Deprecation warning"
|
||||
The reverse API used to allow address lookup for a single OSM object by
|
||||
its OSM id. This use is now deprecated. Use the [Address Lookup API](../Lookup)
|
||||
instead.
|
||||
|
||||
### Output format
|
||||
|
||||
* `format=[xml|json|jsonv2|geojson|geocodejson]`
|
||||
|
||||
See [Place Output Formats](Output.md) for details on each format. (Default: xml)
|
||||
|
||||
* `json_callback=<string>`
|
||||
|
||||
Wrap JSON output in a callback function ([JSONP](https://en.wikipedia.org/wiki/JSONP)) i.e. `<string>(<json>)`.
|
||||
Only has an effect for JSON output formats.
|
||||
|
||||
### Output details
|
||||
|
||||
* `addressdetails=[0|1]`
|
||||
|
||||
Include a breakdown of the address into elements. (Default: 1)
|
||||
|
||||
|
||||
* `extratags=[0|1]`
|
||||
|
||||
Include additional information in the result if available,
|
||||
e.g. wikipedia link, opening hours. (Default: 0)
|
||||
|
||||
|
||||
* `namedetails=[0|1]`
|
||||
|
||||
Include a list of alternative names in the results. These may include
|
||||
language variants, references, operator and brand. (Default: 0)
|
||||
|
||||
|
||||
### Language of results
|
||||
|
||||
* `accept-language=<browser language string>`
|
||||
|
||||
Preferred language order for showing search results, overrides the value
|
||||
specified in the "Accept-Language" HTTP header.
|
||||
Either use a standard RFC2616 accept-language string or a simple
|
||||
comma-separated list of language codes.
|
||||
|
||||
### Result limitation
|
||||
|
||||
* `zoom=[0-18]`
|
||||
|
||||
Level of detail required for the address. Default: 18. This is a number that
|
||||
corresponds roughly to the zoom level used in XYZ tile sources in frameworks
|
||||
like Leaflet.js, Openlayers etc.
|
||||
In terms of address details the zoom levels are as follows:
|
||||
|
||||
zoom | address detail
|
||||
-----|---------------
|
||||
3 | country
|
||||
5 | state
|
||||
8 | county
|
||||
10 | city
|
||||
14 | suburb
|
||||
16 | major streets
|
||||
17 | major and minor streets
|
||||
18 | building
|
||||
|
||||
|
||||
### Polygon output
|
||||
|
||||
* `polygon_geojson=1`
|
||||
* `polygon_kml=1`
|
||||
* `polygon_svg=1`
|
||||
* `polygon_text=1`
|
||||
|
||||
Output geometry of results as a GeoJSON, KML, SVG or WKT. Only one of these
|
||||
options can be used at a time. (Default: 0)
|
||||
|
||||
* `polygon_threshold=0.0`
|
||||
|
||||
Return a simplified version of the output geometry. The parameter is the
|
||||
tolerance in degrees with which the geometry may differ from the original
|
||||
geometry. Topology is preserved in the result. (Default: 0.0)
|
||||
|
||||
### Other
|
||||
|
||||
* `email=<valid email address>`
|
||||
|
||||
If you are making large numbers of request please include an appropriate email
|
||||
address to identify your requests. See Nominatim's [Usage Policy](https://operations.osmfoundation.org/policies/nominatim/) for more details.
|
||||
|
||||
|
||||
* `debug=[0|1]`
|
||||
|
||||
Output assorted developer debug information. Data on internals of Nominatim's
|
||||
"Search Loop" logic, and SQL queries. The output is (rough) HTML format.
|
||||
This overrides the specified machine readable format. (Default: 0)
|
||||
|
||||
|
||||
## 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)
|
||||
|
||||
```xml
|
||||
<reversegeocode timestamp="Fri, 06 Nov 09 16:33:54 +0000" querystring="...">
|
||||
<result place_id="1620612" osm_type="node" osm_id="452010817">
|
||||
135, Pilkington Avenue, Wylde Green, City of Birmingham, West Midlands (county), B72, United Kingdom
|
||||
</result>
|
||||
<addressparts>
|
||||
<house_number>135</house_number>
|
||||
<road>Pilkington Avenue</road>
|
||||
<village>Wylde Green</village>
|
||||
<town>Sutton Coldfield</town>
|
||||
<city>City of Birmingham</city>
|
||||
<county>West Midlands (county)</county>
|
||||
<postcode>B72</postcode>
|
||||
<country>United Kingdom</country>
|
||||
<country_code>gb</country_code>
|
||||
</addressparts>
|
||||
</reversegeocode>
|
||||
```
|
||||
|
||||
##### 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)
|
||||
|
||||
```json
|
||||
{
|
||||
"place_id":"134140761",
|
||||
"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https:\/\/www.openstreetmap.org\/copyright",
|
||||
"osm_type":"way",
|
||||
"osm_id":"280940520",
|
||||
"lat":"-34.4391708",
|
||||
"lon":"-58.7064573",
|
||||
"place_rank":"26",
|
||||
"category":"highway",
|
||||
"type":"motorway",
|
||||
"importance":"0.1",
|
||||
"addresstype":"road",
|
||||
"display_name":"Autopista Pedro Eugenio Aramburu, El Triángulo, Partido de Malvinas Argentinas, Buenos Aires, 1.619, Argentina",
|
||||
"name":"Autopista Pedro Eugenio Aramburu",
|
||||
"address":{
|
||||
"road":"Autopista Pedro Eugenio Aramburu",
|
||||
"village":"El Triángulo",
|
||||
"state_district":"Partido de Malvinas Argentinas",
|
||||
"state":"Buenos Aires",
|
||||
"postcode":"1.619",
|
||||
"country":"Argentina",
|
||||
"country_code":"ar"
|
||||
},
|
||||
"boundingbox":["-34.44159","-34.4370994","-58.7086067","-58.7044712"]
|
||||
}
|
||||
```
|
||||
|
||||
##### 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)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"licence": "Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"place_id": "18512203",
|
||||
"osm_type": "node",
|
||||
"osm_id": "1704756187",
|
||||
"place_rank": "30",
|
||||
"category": "place",
|
||||
"type": "house",
|
||||
"importance": "0",
|
||||
"addresstype": "place",
|
||||
"name": null,
|
||||
"display_name": "71, Via Guglielmo Marconi, Saragozza-Porto, Bologna, BO, Emilia-Romagna, 40122, Italy",
|
||||
"address": {
|
||||
"house_number": "71",
|
||||
"road": "Via Guglielmo Marconi",
|
||||
"suburb": "Saragozza-Porto",
|
||||
"city": "Bologna",
|
||||
"county": "BO",
|
||||
"state": "Emilia-Romagna",
|
||||
"postcode": "40122",
|
||||
"country": "Italy",
|
||||
"country_code": "it"
|
||||
}
|
||||
},
|
||||
"bbox": [
|
||||
11.3397676,
|
||||
44.5014307,
|
||||
11.3399676,
|
||||
44.5016307
|
||||
],
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
11.3398676,
|
||||
44.5015307
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
##### 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)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"geocoding": {
|
||||
"version": "0.1.0",
|
||||
"attribution": "Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright",
|
||||
"licence": "ODbL",
|
||||
"query": "60.229917843587,11.16630979382"
|
||||
},
|
||||
"features": {
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"geocoding": {
|
||||
"place_id": "42700574",
|
||||
"osm_type": "node",
|
||||
"osm_id": "3110596255",
|
||||
"type": "house",
|
||||
"accuracy": 0,
|
||||
"label": "1, Løvenbergvegen, Mogreina, Ullensaker, Akershus, 2054, Norway",
|
||||
"name": null,
|
||||
"housenumber": "1",
|
||||
"street": "Løvenbergvegen",
|
||||
"postcode": "2054",
|
||||
"county": "Akershus",
|
||||
"country": "Norway",
|
||||
"admin": {
|
||||
"level7": "Ullensaker",
|
||||
"level4": "Akershus",
|
||||
"level2": "Norway"
|
||||
}
|
||||
}
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
11.1658572,
|
||||
60.2301296
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
356
docs/api/Search.md
Normal file
356
docs/api/Search.md
Normal file
@@ -0,0 +1,356 @@
|
||||
# Search queries
|
||||
|
||||
The search API allows you to look up a location from a textual description
|
||||
or address. Nominatim supports structured and free-form search queries.
|
||||
|
||||
The search query may also contain
|
||||
[special phrases](https://wiki.openstreetmap.org/wiki/Nominatim/Special_Phrases)
|
||||
which are translated into specific OpenStreetMap (OSM) tags (e.g. Pub => `amenity=pub`).
|
||||
This can be used to narrow down the kind of objects to be returned.
|
||||
|
||||
!!! warning
|
||||
Special phrases are not suitable to query all objects of a certain type in an
|
||||
area. Nominatim will always just return a collection of the best matches. To
|
||||
download OSM data by object type, use the [Overpass API](https://overpass-api.de/).
|
||||
|
||||
## Parameters
|
||||
|
||||
The search API has the following format:
|
||||
|
||||
```
|
||||
https://nominatim.openstreetmap.org/search?<params>
|
||||
```
|
||||
|
||||
The search term may be specified with two different sets of parameters:
|
||||
|
||||
* `q=<query>`
|
||||
|
||||
Free-form query string to search for.
|
||||
Free-form queries are processed first left-to-right and then right-to-left if that fails. So you may search for
|
||||
[pilkington avenue, birmingham](//nominatim.openstreetmap.org/search?q=pilkington+avenue,birmingham) as well as for
|
||||
[birmingham, pilkington avenue](//nominatim.openstreetmap.org/search?q=birmingham,+pilkington+avenue).
|
||||
Commas are optional, but improve performance by reducing the complexity of the search.
|
||||
|
||||
|
||||
* `street=<housenumber> <streetname>`
|
||||
* `city=<city>`
|
||||
* `county=<county>`
|
||||
* `state=<state>`
|
||||
* `country=<country>`
|
||||
* `postalcode=<postalcode>`
|
||||
|
||||
Alternative query string format split into several parameters for structured requests.
|
||||
Structured requests are faster but are less robust against alternative
|
||||
OSM tagging schemas. **Do not combine with** `q=<query>` **parameter**.
|
||||
|
||||
Both query forms accept the additional parameters listed below.
|
||||
|
||||
### Output format
|
||||
|
||||
* `format=[xml|json|jsonv2|geojson|geocodejson]`
|
||||
|
||||
See [Place Output Formats](Output.md) for details on each format. (Default: jsonv2)
|
||||
|
||||
* `json_callback=<string>`
|
||||
|
||||
Wrap JSON output in a callback function ([JSONP](https://en.wikipedia.org/wiki/JSONP)) i.e. `<string>(<json>)`.
|
||||
Only has an effect for JSON output formats.
|
||||
|
||||
### Output details
|
||||
|
||||
* `addressdetails=[0|1]`
|
||||
|
||||
Include a breakdown of the address into elements. (Default: 0)
|
||||
|
||||
|
||||
* `extratags=[0|1]`
|
||||
|
||||
Include additional information in the result if available,
|
||||
e.g. wikipedia link, opening hours. (Default: 0)
|
||||
|
||||
|
||||
* `namedetails=[0|1]`
|
||||
|
||||
Include a list of alternative names in the results. These may include
|
||||
language variants, references, operator and brand. (Default: 0)
|
||||
|
||||
|
||||
### Language of results
|
||||
|
||||
* `accept-language=<browser language string>`
|
||||
|
||||
Preferred language order for showing search results, overrides the value
|
||||
specified in the ["Accept-Language" HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language).
|
||||
Either use a standard RFC2616 accept-language string or a simple
|
||||
comma-separated list of language codes.
|
||||
|
||||
### Result limitation
|
||||
|
||||
* `countrycodes=<countrycode>[,<countrycode>][,<countrycode>]...`
|
||||
|
||||
Limit search results to one or more countries. `<countrycode>` must be the
|
||||
[ISO 3166-1alpha2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code,
|
||||
e.g. `gb` for the United Kingdom, `de` for Germany.
|
||||
|
||||
Each place in Nominatim is assigned to one country code based
|
||||
on OSM country boundaries. In rare cases a place may not be in any country
|
||||
at all, for example, in international waters.
|
||||
|
||||
* `exclude_place_ids=<place_id,[place_id],[place_id]`
|
||||
|
||||
If you do not want certain OSM objects to appear in the search
|
||||
result, give a comma separated list of the `place_id`s you want to skip.
|
||||
This can be used to retrieve additional search results. For example, if a
|
||||
previous query only returned a few results, then including those here would
|
||||
cause the search to return other, less accurate, matches (if possible).
|
||||
|
||||
|
||||
* `limit=<integer>`
|
||||
|
||||
Limit the number of returned results. (Default: 10, Maximum: 50)
|
||||
|
||||
|
||||
* `viewbox=<x1>,<y1>,<x2>,<y2>`
|
||||
|
||||
The preferred area to find search results. Any two corner points of the box
|
||||
are accepted as long as they span a real box. `x` is longitude,
|
||||
`y` is latitude.
|
||||
|
||||
|
||||
* `bounded=[0|1]`
|
||||
|
||||
When a viewbox is given, restrict the result to items contained within that
|
||||
viewbox (see above). When `viewbox` and `bounded=1` are given, an amenity
|
||||
only search is allowed. Give the special keyword for the amenity in square
|
||||
brackets, e.g. `[pub]` and a selection of objects of this type is returned.
|
||||
There is no guarantee that the result is complete. (Default: 0)
|
||||
|
||||
|
||||
### Polygon output
|
||||
|
||||
* `polygon_geojson=1`
|
||||
* `polygon_kml=1`
|
||||
* `polygon_svg=1`
|
||||
* `polygon_text=1`
|
||||
|
||||
Output geometry of results as a GeoJSON, KML, SVG or WKT. Only one of these
|
||||
options can be used at a time. (Default: 0)
|
||||
|
||||
* `polygon_threshold=0.0`
|
||||
|
||||
Return a simplified version of the output geometry. The parameter is the
|
||||
tolerance in degrees with which the geometry may differ from the original
|
||||
geometry. Topology is preserved in the result. (Default: 0.0)
|
||||
|
||||
### Other
|
||||
|
||||
* `email=<valid email address>`
|
||||
|
||||
If you are making large numbers of request please include an appropriate email
|
||||
address to identify your requests. See Nominatim's [Usage Policy](https://operations.osmfoundation.org/policies/nominatim/) for more details.
|
||||
|
||||
* `dedupe=[0|1]`
|
||||
|
||||
Sometimes you have several objects in OSM identifying the same place or
|
||||
object in reality. The simplest case is a street being split into many
|
||||
different OSM ways due to different characteristics. Nominatim will
|
||||
attempt to detect such duplicates and only return one match unless
|
||||
this parameter is set to 0. (Default: 1)
|
||||
|
||||
* `debug=[0|1]`
|
||||
|
||||
Output assorted developer debug information. Data on internals of Nominatim's
|
||||
"Search Loop" logic, and SQL queries. The output is (rough) HTML format.
|
||||
This overrides the specified machine readable format. (Default: 0)
|
||||
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
##### XML with kml polygon
|
||||
|
||||
* [https://nominatim.openstreetmap.org/search?q=135+pilkington+avenue,+birmingham&format=xml&polygon_geojson=1&addressdetails=1](https://nominatim.openstreetmap.org/search?q=135+pilkington+avenue,+birmingham&format=xml&polygon_geojson=1&addressdetails=1)
|
||||
|
||||
```xml
|
||||
<searchresults timestamp="Sat, 07 Nov 09 14:42:10 +0000" querystring="135 pilkington, avenue birmingham" polygon="true">
|
||||
<place
|
||||
place_id="1620612" osm_type="node" osm_id="452010817"
|
||||
boundingbox="52.548641204834,52.5488433837891,-1.81612110137939,-1.81592094898224"
|
||||
lat="52.5487429714954" lon="-1.81602098644987"
|
||||
display_name="135, Pilkington Avenue, Wylde Green, City of Birmingham, West Midlands (county), B72, United Kingdom"
|
||||
class="place" type="house">
|
||||
<geokml>
|
||||
<Polygon>
|
||||
<outerBoundaryIs>
|
||||
<LinearRing>
|
||||
<coordinates>-1.816513,52.548756599999997 -1.816434,52.548747300000002 -1.816429,52.5487629 -1.8163717,52.548756099999999 -1.8163464,52.548834599999999 -1.8164599,52.548848100000001 -1.8164685,52.5488213 -1.8164913,52.548824000000003 -1.816513,52.548756599999997</coordinates>
|
||||
</LinearRing>
|
||||
</outerBoundaryIs>
|
||||
</Polygon>
|
||||
</geokml>
|
||||
<house_number>135</house_number>
|
||||
<road>Pilkington Avenue</road>
|
||||
<village>Wylde Green</village>
|
||||
<town>Sutton Coldfield</town>
|
||||
<city>City of Birmingham</city>
|
||||
<county>West Midlands (county)</county>
|
||||
<postcode>B72</postcode>
|
||||
<country>United Kingdom</country>
|
||||
<country_code>gb</country_code>
|
||||
</place>
|
||||
</searchresults>
|
||||
```
|
||||
|
||||
##### JSON with SVG polygon
|
||||
|
||||
[https://nominatim.openstreetmap.org/search/Unter%20den%20Linden%201%20Berlin?format=json&addressdetails=1&limit=1&polygon_svg=1](https://nominatim.openstreetmap.org/search/Unter%20den%20Linden%201%20Berlin?format=json&addressdetails=1&limit=1&polygon_svg=1)
|
||||
|
||||
```json
|
||||
{
|
||||
"address": {
|
||||
"city": "Berlin",
|
||||
"city_district": "Mitte",
|
||||
"construction": "Unter den Linden",
|
||||
"continent": "European Union",
|
||||
"country": "Deutschland",
|
||||
"country_code": "de",
|
||||
"house_number": "1",
|
||||
"neighbourhood": "Scheunenviertel",
|
||||
"postcode": "10117",
|
||||
"public_building": "Kommandantenhaus",
|
||||
"state": "Berlin",
|
||||
"suburb": "Mitte"
|
||||
},
|
||||
"boundingbox": [
|
||||
"52.5170783996582",
|
||||
"52.5173187255859",
|
||||
"13.3975105285645",
|
||||
"13.3981599807739"
|
||||
],
|
||||
"class": "amenity",
|
||||
"display_name": "Kommandantenhaus, 1, Unter den Linden, Scheunenviertel, Mitte, Berlin, 10117, Deutschland, European Union",
|
||||
"importance": 0.73606775332943,
|
||||
"lat": "52.51719785",
|
||||
"licence": "Data \u00a9 OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright",
|
||||
"lon": "13.3978352028938",
|
||||
"osm_id": "15976890",
|
||||
"osm_type": "way",
|
||||
"place_id": "30848715",
|
||||
"svg": "M 13.397511 -52.517283599999999 L 13.397829400000001 -52.517299800000004 13.398131599999999 -52.517315099999998 13.398159400000001 -52.517112099999999 13.3975388 -52.517080700000001 Z",
|
||||
"type": "public_building"
|
||||
}
|
||||
```
|
||||
|
||||
##### JSON with address details
|
||||
|
||||
[https://nominatim.openstreetmap.org/?addressdetails=1&q=bakery+in+berlin+wedding&format=json&limit=1](https://nominatim.openstreetmap.org/?addressdetails=1&q=bakery+in+berlin+wedding&format=json&limit=1)
|
||||
|
||||
```json
|
||||
{
|
||||
"address": {
|
||||
"bakery": "B\u00e4cker Kamps",
|
||||
"city_district": "Mitte",
|
||||
"continent": "European Union",
|
||||
"country": "Deutschland",
|
||||
"country_code": "de",
|
||||
"footway": "Bahnsteig U6",
|
||||
"neighbourhood": "Sprengelkiez",
|
||||
"postcode": "13353",
|
||||
"state": "Berlin",
|
||||
"suburb": "Wedding"
|
||||
},
|
||||
"boundingbox": [
|
||||
"52.5460929870605",
|
||||
"52.5460968017578",
|
||||
"13.3591794967651",
|
||||
"13.3591804504395"
|
||||
],
|
||||
"class": "shop",
|
||||
"display_name": "B\u00e4cker Kamps, Bahnsteig U6, Sprengelkiez, Wedding, Mitte, Berlin, 13353, Deutschland, European Union",
|
||||
"icon": "https://nominatim.openstreetmap.org/images/mapicons/shopping_bakery.p.20.png",
|
||||
"importance": 0.201,
|
||||
"lat": "52.5460941",
|
||||
"licence": "Data \u00a9 OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright",
|
||||
"lon": "13.35918",
|
||||
"osm_id": "317179427",
|
||||
"osm_type": "node",
|
||||
"place_id": "1453068",
|
||||
"type": "bakery"
|
||||
}
|
||||
```
|
||||
|
||||
##### GeoJSON
|
||||
|
||||
[https://nominatim.openstreetmap.org/search?q=17+Strada+Pictor+Alexandru+Romano%2C+Bukarest&format=geojson](https://nominatim.openstreetmap.org/search?q=17+Strada+Pictor+Alexandru+Romano%2C+Bukarest&format=geojson)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"licence": "Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"place_id": "35811445",
|
||||
"osm_type": "node",
|
||||
"osm_id": "2846295644",
|
||||
"display_name": "17, Strada Pictor Alexandru Romano, Bukarest, Bucharest, Sector 2, Bucharest, 023964, Romania",
|
||||
"place_rank": "30",
|
||||
"category": "place",
|
||||
"type": "house",
|
||||
"importance": 0.62025
|
||||
},
|
||||
"bbox": [
|
||||
26.1156689,
|
||||
44.4354754,
|
||||
26.1157689,
|
||||
44.4355754
|
||||
],
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
26.1157189,
|
||||
44.4355254
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
##### GeocodeJSON
|
||||
|
||||
[https://nominatim.openstreetmap.org/search?q=%CE%91%CE%B3%CE%AF%CE%B1+%CE%A4%CF%81%CE%B9%CE%AC%CE%B4%CE%B1%2C+%CE%91%CE%B4%CF%89%CE%BD%CE%B9%CE%B4%CE%BF%CF%82%2C+Athens%2C+Greece&format=geocodejson](https://nominatim.openstreetmap.org/search?q=%CE%91%CE%B3%CE%AF%CE%B1+%CE%A4%CF%81%CE%B9%CE%AC%CE%B4%CE%B1%2C+%CE%91%CE%B4%CF%89%CE%BD%CE%B9%CE%B4%CE%BF%CF%82%2C+Athens%2C+Greece&format=geocodejson)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"geocoding": {
|
||||
"version": "0.1.0",
|
||||
"attribution": "Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright",
|
||||
"licence": "ODbL",
|
||||
"query": "Αγία Τριάδα, Αδωνιδος, Athens, Greece"
|
||||
},
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"geocoding": {
|
||||
"type": "place_of_worship",
|
||||
"label": "Αγία Τριάδα, Αδωνιδος, Άγιος Νικόλαος, 5º Δημοτικό Διαμέρισμα Αθηνών, Athens, Municipality of Athens, Regional Unit of Central Athens, Region of Attica, Attica, 11472, Greece",
|
||||
"name": "Αγία Τριάδα",
|
||||
"admin": null
|
||||
}
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
23.72949633941,
|
||||
38.0051697
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
60
docs/api/Status.md
Normal file
60
docs/api/Status.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Status
|
||||
|
||||
Useful for checking if the service and database is running. The JSON output also shows
|
||||
when the database was last updated.
|
||||
|
||||
## Parameters
|
||||
|
||||
* `format=[text|json]` (defaults to 'text')
|
||||
|
||||
|
||||
## Output
|
||||
|
||||
#### Text format
|
||||
|
||||
```
|
||||
https://nominatim.openstreetmap.org/status.php
|
||||
```
|
||||
|
||||
will return HTTP status code 200 and print `OK`.
|
||||
|
||||
On error it will return HTTP status code 500 and print a message, e.g.
|
||||
`ERROR: Database connection failed`.
|
||||
|
||||
|
||||
|
||||
#### JSON format
|
||||
|
||||
```
|
||||
https://nominatim.openstreetmap.org/status.php?format=json
|
||||
```
|
||||
|
||||
will return HTTP code 200 and a structure
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 0,
|
||||
"message": "OK",
|
||||
"data_updated": "2020-05-04T14:47:00+00:00"
|
||||
}
|
||||
```
|
||||
|
||||
On error will also return HTTP status code 200 and a structure with error
|
||||
code and message, e.g.
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 700,
|
||||
"message": "Database connection failed"
|
||||
}
|
||||
```
|
||||
|
||||
Possible status codes are
|
||||
|
||||
| | message | notes |
|
||||
|-----|----------------------|---------------------------------------------------|
|
||||
| 700 | "No database" | connection failed |
|
||||
| 701 | "Module failed" | database could not load nominatim.so |
|
||||
| 702 | "Module call failed" | nominatim.so loaded but calling a function failed |
|
||||
| 703 | "Query failed" | test query against a database table failed |
|
||||
| 704 | "No value" | test query worked but returned no results |
|
||||
169
docs/develop/Development-Environment.md
Normal file
169
docs/develop/Development-Environment.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# Setting up Nominatim for Development
|
||||
|
||||
This chapter gives an overview how to set up Nominatim for developement
|
||||
and how to run tests.
|
||||
|
||||
!!! Important
|
||||
This guide assumes that you develop under the latest version of Ubuntu. You
|
||||
can of course also use your favourite distribution. You just might have to
|
||||
adapt the commands below slightly, in particular the commands for installing
|
||||
additional software.
|
||||
|
||||
## Installing Nominatim
|
||||
|
||||
The first step is to install Nominatim itself. Please follow the installation
|
||||
instructions in the [Admin section](../admin/Installation.md). You don't need
|
||||
to set up a webserver for development, the webserver that is included with PHP
|
||||
is sufficient.
|
||||
|
||||
If you want to run Nominatim in a VM via Vagrant, use the default `ubuntu` setup.
|
||||
Vagrant's libvirt provider runs out-of-the-box under Ubuntu. You also need to
|
||||
install an NFS daemon to enable directory sharing between host and guest. The
|
||||
following packages should get you started:
|
||||
|
||||
sudo apt install vagrant vagrant-libvirt libvirt-daemon nfs-kernel-server
|
||||
|
||||
## Prerequisites for testing and documentation
|
||||
|
||||
The Nominatim test suite consists of behavioural tests (using behave) and
|
||||
unit tests (using PHPUnit). It has the following additional requirements:
|
||||
|
||||
* [behave test framework](https://behave.readthedocs.io) >= 1.2.5
|
||||
* [nose](https://nose.readthedocs.io)
|
||||
* [phpunit](https://phpunit.de) >= 7.3
|
||||
* [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer)
|
||||
|
||||
The documentation is built with mkdocs:
|
||||
|
||||
* [mkdocs](https://www.mkdocs.org/) >= 1.1.2
|
||||
|
||||
### Installing prerequisites on Ubuntu/Debian
|
||||
|
||||
Some of the Python packages require the newest version which is not yet
|
||||
available with the current distributions. Therefore it is recommended to
|
||||
install pip to get the newest versions.
|
||||
|
||||
To install all necessary packages run:
|
||||
|
||||
```sh
|
||||
sudo apt install php-cgi phpunit php-codesniffer \
|
||||
python3-pip python3-setuptools python3-dev
|
||||
|
||||
pip3 install --user behave nose mkdocs
|
||||
```
|
||||
|
||||
The `mkdocs` executable will be located in `.local/bin`. You may have to add
|
||||
this directory to your path, for example by running:
|
||||
|
||||
```
|
||||
echo 'export PATH=~/.local/bin:$PATH' > ~/.profile
|
||||
```
|
||||
|
||||
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 as well:
|
||||
|
||||
```
|
||||
echo 'export PATH=~/.config/composer/vendor/bin:$PATH' > ~/.profile
|
||||
```
|
||||
|
||||
|
||||
## Executing Tests
|
||||
|
||||
All tests are located in the `\test` directory.
|
||||
|
||||
### Preparing the test database
|
||||
|
||||
Some of the behavioural test expect a test database to be present. You need at
|
||||
least 2GB RAM and 10GB disk space to create the database.
|
||||
|
||||
First create a separate directory for the test DB and fetch the test planet
|
||||
data and the Tiger data for South Dakota:
|
||||
|
||||
```
|
||||
mkdir testdb
|
||||
cd testdb
|
||||
wget https://www.nominatim.org/data/test/nominatim-api-testdata.pbf
|
||||
wget -O - https://nominatim.org/data/tiger2018-nominatim-preprocessed.tar.gz | tar xz --wildcards --no-anchored '46*'
|
||||
```
|
||||
|
||||
Configure and build Nominatim in the usual way:
|
||||
|
||||
```
|
||||
cmake $USERNAME/Nominatim
|
||||
make
|
||||
```
|
||||
|
||||
Copy the test settings:
|
||||
|
||||
```
|
||||
cp $USERNAME/Nominatim/test/testdb/local.php settings/
|
||||
```
|
||||
|
||||
Inspect the file to check that all settings are correct for your local setup.
|
||||
|
||||
Now you can import the test database:
|
||||
|
||||
```
|
||||
dropdb --if-exists test_api_nominatim
|
||||
./utils/setup.php --all --osm-file nominatim-api-testdb.pbf 2>&1 | tee import.log
|
||||
./utils/specialphrases.php --wiki-import | psql -d test_api_nominatim 2>&1 | tee -a import.log
|
||||
./utils/setup.php --import-tiger-data 2>&1 | tee -a import.log
|
||||
```
|
||||
|
||||
### Running the tests
|
||||
|
||||
To run all tests just go to the test directory and run make:
|
||||
|
||||
```sh
|
||||
cd test
|
||||
make
|
||||
```
|
||||
|
||||
To skip tests that require the test database, run `make no-test-db` instead.
|
||||
|
||||
For more information about the structure of the tests and how to change and
|
||||
extend the test suite, see the [Testing chapter](Testing.md).
|
||||
|
||||
## Documentation Pages
|
||||
|
||||
The [Nominatim documentation](https://nominatim.org/release-docs/develop/) is
|
||||
built using the [MkDocs](https://www.mkdocs.org/) static site generation
|
||||
framework. The master branch is automatically deployed every night on
|
||||
[https://nominatim.org/release-docs/develop/](https://nominatim.org/release-docs/develop/)
|
||||
|
||||
To build the documentation, go to the build directory and run
|
||||
|
||||
```
|
||||
make doc
|
||||
INFO - Cleaning site directory
|
||||
INFO - Building documentation to directory: /home/vagrant/build/site-html
|
||||
```
|
||||
|
||||
This runs `mkdocs build` plus extra transformation of some files and adds
|
||||
symlinks (see `CMakeLists.txt` for the exact steps).
|
||||
|
||||
Now you can start webserver for local testing
|
||||
|
||||
```
|
||||
build> mkdocs serve
|
||||
[server:296] Serving on http://127.0.0.1:8000
|
||||
[handlers:62] Start watching changes
|
||||
```
|
||||
|
||||
If you develop inside a Vagrant virtual machine, use a port that is forwarded
|
||||
to your host:
|
||||
|
||||
```
|
||||
build> mkdocs serve --dev-addr 0.0.0.0:8088
|
||||
[server:296] Serving on http://0.0.0.0:8088
|
||||
[handlers:62] Start watching changes
|
||||
```
|
||||
170
docs/develop/Import.md
Normal file
170
docs/develop/Import.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# OSM Data Import
|
||||
|
||||
OSM data is initially imported using [osm2pgsql](https://osm2pgsql.org).
|
||||
Nominatim uses its own data output style 'gazetteer', which differs from the
|
||||
output style created for map rendering.
|
||||
|
||||
## Database Layout
|
||||
|
||||
The gazetteer style produces a single table `place` with the following rows:
|
||||
|
||||
* `osm_type` - kind of OSM object (**N** - node, **W** - way, **R** - relation)
|
||||
* `osm_id` - original OSM ID
|
||||
* `class` - key of principal tag defining the object type
|
||||
* `type` - value of principal tag defining the object type
|
||||
* `name` - collection of tags that contain a name or reference
|
||||
* `admin_level` - numerical value of the tagged administrative level
|
||||
* `address` - collection of tags defining the address of an object
|
||||
* `extratags` - collection of additional interesting tags that are not
|
||||
directly relevant for searching
|
||||
* `geometry` - geometry of the object (in WGS84)
|
||||
|
||||
A single OSM object may appear multiple times in this table when it is tagged
|
||||
with multiple tags that may constitute a principal tag. Take for example a
|
||||
motorway bridge. In OSM, this would be a way which is tagged with
|
||||
`highway=motorway` and `bridge=yes`. This way would appear in the `place` table
|
||||
once with `class` of `highway` and once with a `class` of `bridge`. Thus the
|
||||
*unique key* for `place` is (`osm_type`, `osm_id`, `class`).
|
||||
|
||||
## Configuring the Import
|
||||
|
||||
How tags are interpreted and assigned to the different `place` columns can be
|
||||
configured via the import style configuration file (`CONST_Import_style`). This
|
||||
is a JSON file which contains a list of rules which are matched against every
|
||||
tag of every object and then assign the tag its specific role.
|
||||
|
||||
### Configuration Rules
|
||||
|
||||
A single rule looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"keys" : ["key1", "key2", ...],
|
||||
"values" : {
|
||||
"value1" : "prop",
|
||||
"value2" : "prop1,prop2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A rule first defines a list of keys to apply the rule to. This is always a list
|
||||
of strings. The string may have four forms. An empty string matches against
|
||||
any key. A string that ends in an asterisk `*` is a prefix match and accordingly
|
||||
matches against any key that starts with the given string (minus the `*`). A
|
||||
suffix match can be defined similarly with a string that starts with a `*`. Any
|
||||
other string constitutes an exact match.
|
||||
|
||||
The second part of the rules defines a list of values and the properties that
|
||||
apply to a successful match. Value strings may be either empty, which
|
||||
means that they match any value, or describe an exact match. Prefix
|
||||
or suffix matching of values is not possible.
|
||||
|
||||
For a rule to match, it has to find a valid combination of keys and values. The
|
||||
resulting property is that of the matched values.
|
||||
|
||||
The rules in a configuration file are processed sequentially and the first
|
||||
match for each tag wins.
|
||||
|
||||
A rule where key and value are the empty string is special. This defines the
|
||||
fallback when none of the rules match. The fallback is always used as a last
|
||||
resort when nothing else matches, no matter where the rule appears in the file.
|
||||
Defining multiple fallback rules is not allowed. What happens in this case,
|
||||
is undefined.
|
||||
|
||||
### Tag Properties
|
||||
|
||||
One or more of the following properties may be given for each tag:
|
||||
|
||||
* `main`
|
||||
|
||||
A principal tag. A new row will be added for the object with key and value
|
||||
as `class` and `type`.
|
||||
|
||||
* `with_name`
|
||||
|
||||
When the tag is a principal tag (`main` property set): only really add a new
|
||||
row, if there is any name tag found (a reference tag is not sufficient, see
|
||||
below).
|
||||
|
||||
* `with_name_key`
|
||||
|
||||
When the tag is a principal tag (`main` property set): only really add a new
|
||||
row, if there is also a name tag that matches the key of the principal tag.
|
||||
For example, if the main tag is `bridge=yes`, then it will only be added as
|
||||
an extra row, if there is a tag `bridge:name[:XXX]` for the same object.
|
||||
If this property is set, all other names that are not domain-specific are
|
||||
ignored.
|
||||
|
||||
* `fallback`
|
||||
|
||||
When the tag is a principal tag (`main` property set): only really add a new
|
||||
row, when no other principal tags for this object have been found. Only one
|
||||
fallback tag can win for an object.
|
||||
|
||||
* `operator`
|
||||
|
||||
When the tag is a principal tag (`main` property set): also include the
|
||||
`operator` tag in the list of names. This is a special construct for an
|
||||
out-dated tagging practise in OSM. Fuel stations and chain restaurants
|
||||
in particular used to have the name of the chain tagged as `operator`.
|
||||
These days the chain can be more commonly found in the `brand` tag but
|
||||
there is still enough old data around to warrant this special case.
|
||||
|
||||
* `name`
|
||||
|
||||
Add tag to the list of names.
|
||||
|
||||
* `ref`
|
||||
|
||||
Add tag to the list of names as a reference. At the moment this only means
|
||||
that the object is not considered to be named for `with_name`.
|
||||
|
||||
* `address`
|
||||
|
||||
Add tag to the list of address tags. If the tag starts with `addr:` or
|
||||
`is_in:`, then this prefix is cut off before adding it to the list.
|
||||
|
||||
* `postcode`
|
||||
|
||||
Add the value as a postcode to the address tags. If multiple tags are
|
||||
candidate for postcodes, one wins out and the others are dropped.
|
||||
|
||||
* `country`
|
||||
|
||||
Add the value as a country code to the address tags. The value must be a
|
||||
two letter country code, otherwise it is ignored. If there are multiple
|
||||
tags that match, then one wins out and the others are dropped.
|
||||
|
||||
* `house`
|
||||
|
||||
If no principle tags can be found for the object, still add the object with
|
||||
`class`=`place` and `type`=`house`. Use this for address nodes that have no
|
||||
other function.
|
||||
|
||||
* `interpolation`
|
||||
|
||||
Add this object as an address interpolation (appears as `class`=`place` and
|
||||
`type`=`houses` in the database).
|
||||
|
||||
* `extra`
|
||||
|
||||
Add tag to the list of extra tags.
|
||||
|
||||
* `skip`
|
||||
|
||||
Skip the tag completely. Useful when a custom default fallback is defined
|
||||
or to define exceptions to rules.
|
||||
|
||||
A rule can define as many of these properties for one match as it likes. For
|
||||
example, if the property is `"main,extra"` then the tag will open a new row
|
||||
but also have the tag appear in the list of extra tags.
|
||||
|
||||
There are a number of pre-defined styles in the `settings/` directory. It is
|
||||
advisable to start from one of these styles when defining your own.
|
||||
|
||||
### Changing the Style of Existing Databases
|
||||
|
||||
There is normally no issue changing the style of a database that is already
|
||||
imported and now kept up-to-date with change files. Just be aware that any
|
||||
change in the style applies to updates only. If you want to change the data
|
||||
that is already in the database, then a reimport is necessary.
|
||||
45
docs/develop/Postcodes.md
Normal file
45
docs/develop/Postcodes.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Postcodes in Nominatim
|
||||
|
||||
The blog post
|
||||
[Nominatim and Postcodes](https://www.openstreetmap.org/user/lonvia/diary/43143)
|
||||
describes the handling implemented since Nominatim 3.1.
|
||||
|
||||
Postcode centroids (aka 'calculated postcodes') are generated by looking at all
|
||||
postcodes of a country, grouping them and calculating the geometric centroid.
|
||||
There is currently no logic to deal with extreme outliers (typos or other
|
||||
mistakes in OSM data). There is also no check if a postcodes adheres to a
|
||||
country's format, e.g. if Swiss postcodes are 4 digits.
|
||||
|
||||
|
||||
## Regular updating calculated postcodes
|
||||
|
||||
The script to rerun the calculation is
|
||||
`build/utils/update.php --calculate-postcodes`
|
||||
and runs once per night on nominatim.openstreetmap.org.
|
||||
|
||||
|
||||
## Finding places that share a specific postcode
|
||||
|
||||
In the Nominatim database run
|
||||
|
||||
```sql
|
||||
SELECT address->'postcode' as pc,
|
||||
osm_type, osm_id, class, type,
|
||||
st_x(centroid) as lon, st_y(centroid) as lat
|
||||
FROM placex
|
||||
WHERE country_code='fr'
|
||||
AND upper(trim (both ' ' from address->'postcode')) = '33210';
|
||||
```
|
||||
|
||||
Alternatively on [Overpass](https://overpass-turbo.eu/) run the following query
|
||||
|
||||
```
|
||||
[out:json][timeout:250];
|
||||
area["name"="France"]->.boundaryarea;
|
||||
(
|
||||
nwr(area.boundaryarea)["addr:postcode"="33210"];
|
||||
);
|
||||
out body;
|
||||
>;
|
||||
out skel qt;
|
||||
```
|
||||
140
docs/develop/Ranking.md
Normal file
140
docs/develop/Ranking.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Place Ranking in Nominatim
|
||||
|
||||
Nominatim uses two metrics to rank a place: search rank and address rank.
|
||||
Both can be assigned a value between 0 and 30. They serve slightly
|
||||
different purposes, which are explained in this chapter.
|
||||
|
||||
## Search rank
|
||||
|
||||
The search rank describes the extent and importance of a place. It is used
|
||||
when ranking search results. Simply put, if there are two results for a
|
||||
search query which are otherwise equal, then the result with the _lower_
|
||||
search rank will be appear higher in the result list.
|
||||
|
||||
Search ranks are not so important these days because many well-known
|
||||
places use the Wikipedia importance ranking instead.
|
||||
|
||||
The following table gives an overview of the kind of features that Nominatim
|
||||
expects for each rank:
|
||||
|
||||
rank | typical place types | extent
|
||||
-------|---------------------------------|-------
|
||||
1-3 | oceans, continents | -
|
||||
4 | countries | -
|
||||
5-9 | states, regions, provinces | -
|
||||
10-12 | counties | -
|
||||
13-16 | cities, municipalities, islands | 15 km
|
||||
17-18 | towns, boroughs | 4 km
|
||||
19 | villages, suburbs | 2 km
|
||||
20 | hamlets, farms, neighbourhoods | 1 km
|
||||
21-25 | isolated dwellings, city blocks | 500 m
|
||||
|
||||
The extent column describes how far a feature is assumed to reach when it
|
||||
is mapped only as a point. Larger features like countries and states are usually
|
||||
available with their exact area in the OpenStreetMap data. That is why no extent
|
||||
is given.
|
||||
|
||||
## Address rank
|
||||
|
||||
The address rank describes where a place shows up in an address hierarchy.
|
||||
Usually only administrative boundaries and place nodes and areas are
|
||||
eligible to be part of an address. Places that should not appear in the
|
||||
address must have an address rank of 0.
|
||||
|
||||
The following table gives an overview how ranks are mapped to address parts:
|
||||
|
||||
rank | address part
|
||||
-------------|-------------
|
||||
1-3 | _unused_
|
||||
4 | country
|
||||
5-9 | state
|
||||
10-12 | county
|
||||
13-16 | city
|
||||
17-21 | suburb
|
||||
22-24 | neighbourhood
|
||||
25 | squares, farms, localities
|
||||
26-27 | street
|
||||
28-30 | POI/house number
|
||||
|
||||
The country rank 4 usually doesn't show up in the address parts of an object.
|
||||
The country is determined indirectly from the country code.
|
||||
|
||||
Ranks 5-24 can be assigned more or less freely. They make up the major part
|
||||
of the address.
|
||||
|
||||
Rank 25 is also an addressing rank but it is special because while it can be
|
||||
the parent to a POI with an addr:place of the same name, it cannot be a parent
|
||||
to streets. Use it for place features that are technically on the same level
|
||||
as a street (e.g. squares, city blocks) or for places that should not normally
|
||||
appear in an address unless explicitly tagged so (e.g place=locality which
|
||||
should be uninhabited and as such not addressable).
|
||||
|
||||
The street ranks 26 and 27 are handled slightly differently. Only one object
|
||||
from these ranks shows up in an address.
|
||||
|
||||
For POI level objects like shops, buildings or house numbers always use rank 30.
|
||||
Ranks 28 is reserved for house number interpolations. 29 is for internal use
|
||||
only.
|
||||
|
||||
## Rank configuration
|
||||
|
||||
Search and address ranks are assigned to a place when it is first imported
|
||||
into the database. There are a few hard-coded rules for the assignment:
|
||||
|
||||
* postcodes follow special rules according to their length
|
||||
* boundaries that are not areas and railway=rail are dropped completely
|
||||
* the following are always search rank 30 and address rank 0:
|
||||
* highway nodes
|
||||
* landuse that is not an area
|
||||
|
||||
Other than that, the ranks can be freely assigned via the JSON file
|
||||
defined with `CONST_Address_Level_Config` according to their type and
|
||||
the country they are in.
|
||||
|
||||
The address level configuration must consist of an array of configuration
|
||||
entries, each containing a tag definition and an optional country array:
|
||||
|
||||
```
|
||||
[ {
|
||||
"tags" : {
|
||||
"place" : {
|
||||
"county" : 12,
|
||||
"city" : 16,
|
||||
},
|
||||
"landuse" : {
|
||||
"residential" : 22,
|
||||
"" : 30
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"countries" : [ "ca", "us" ],
|
||||
"tags" : {
|
||||
"boundary" : {
|
||||
"administrative8" : 18,
|
||||
"administrative9" : 20
|
||||
},
|
||||
"landuse" : {
|
||||
"residential" : [22, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The `countries` field contains a list of countries (as ISO 3166-1 alpha 2 code)
|
||||
for which the definition applies. When the field is omitted, then the
|
||||
definition is used as a fallback, when nothing more specific for a given
|
||||
country exists.
|
||||
|
||||
`tags` contains the ranks for key/value pairs. The ranks can be either a
|
||||
single number, in which case they are the search and address rank, or an array
|
||||
of search and address rank (in that order). The value may be left empty.
|
||||
Then the rank is used when no more specific value is found for the given
|
||||
key.
|
||||
|
||||
Countries and key/value combination may appear in multiple definitions. Just
|
||||
make sure that each combination of country/key/value appears only once per
|
||||
file. Otherwise the import will fail with a UNIQUE INDEX constraint violation
|
||||
on import.
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
This directory contains functional and unit tests for the Nominatim API.
|
||||
# Nominatim Test Suite
|
||||
|
||||
Prerequisites
|
||||
=============
|
||||
This chapter describes the tests in the `/test` directory, how they are
|
||||
structured and how to extend them. For a quick introduction on how to run
|
||||
the tests, see the [Development setup chapter](Development-Environment.md).
|
||||
|
||||
* Python 3 (https://www.python.org/)
|
||||
* behave test framework >= 1.2.5 (https://github.com/behave/behave)
|
||||
* nose (https://nose.readthedocs.org)
|
||||
* pytidylib (http://countergram.com/open-source/pytidylib)
|
||||
* psycopg2 (http://initd.org/psycopg/)
|
||||
|
||||
To get the prerequisites on a a fresh Ubuntu LTS 16.04 run:
|
||||
|
||||
[sudo] apt-get install python3-dev python3-pip python3-psycopg2 python3-tidylib phpunit php-cgi
|
||||
pip3 install --user behave nose
|
||||
|
||||
|
||||
Overall structure
|
||||
=================
|
||||
## Overall structure
|
||||
|
||||
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
|
||||
@@ -37,42 +25,54 @@ This test directory is sturctured as follows:
|
||||
+- testdb Base data for generating API test database
|
||||
```
|
||||
|
||||
PHP Unit Tests
|
||||
==============
|
||||
## PHP Unit Tests (`test/php`)
|
||||
|
||||
Unit tests can be found in the php/ directory and tests selected php functions.
|
||||
Unit tests can be found in the php/ directory. They test selected php functions.
|
||||
Very low coverage.
|
||||
|
||||
To execute the test suite run
|
||||
|
||||
cd test/php
|
||||
phpunit ../
|
||||
UNIT_TEST_DSN='pgsql:dbname=nominatim_unit_tests' phpunit ../
|
||||
|
||||
It will read phpunit.xml which points to the library, test path, bootstrap
|
||||
strip and set 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'.
|
||||
|
||||
BDD Functional Tests
|
||||
====================
|
||||
## BDD Functional Tests (`test/bdd`)
|
||||
|
||||
Functional tests are written as BDD instructions. For more information on
|
||||
the philosophy of BDD testing, see http://pythonhosted.org/behave/philosophy.html
|
||||
the philosophy of BDD testing, see the
|
||||
[Behave manual](http://pythonhosted.org/behave/philosophy.html).
|
||||
|
||||
Usage
|
||||
-----
|
||||
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
|
||||
|
||||
To run the functional tests, do
|
||||
|
||||
cd test/bdd
|
||||
behave
|
||||
|
||||
The tests can be configured with a set of environment variables:
|
||||
The tests can be configured with a set of environment variables (`behave -D key=val`):
|
||||
|
||||
* `BUILD_DIR` - build directory of Nominatim installation to test
|
||||
* `BUILDDIR` - build directory of Nominatim installation to test
|
||||
* `TEMPLATE_DB` - name of template database used as a skeleton for
|
||||
the test databases (db tests)
|
||||
* `TEST_DB` - name of test database (db tests)
|
||||
* `ABI_TEST_DB` - name of the database containing the API test data (api tests)
|
||||
* `API_TEST_DB` - name of the database containing the API test data (api tests)
|
||||
* `DB_HOST` - (optional) hostname of database host
|
||||
* `DB_PORT` - (optional) port of database on host
|
||||
* `DB_USER` - (optional) username of database login
|
||||
* `DB_PASS` - (optional) password for database login
|
||||
* `SERVER_MODULE_PATH` - (optional) path on the Postgres server to Nominatim
|
||||
module shared library file
|
||||
* `TEST_SETTINGS_TEMPLATE` - file to write temporary Nominatim settings to
|
||||
* `REMOVE_TEMPLATE` - if true, the template database will not be reused during
|
||||
the next run. Reusing the base templates speeds up tests
|
||||
@@ -83,63 +83,52 @@ The tests can be configured with a set of environment variables:
|
||||
run, otherwise the result is undefined.
|
||||
|
||||
Logging can be defined through command line parameters of behave itself. Check
|
||||
out `behave --help` for details. Also keep an eye out for the 'work-in-progress'
|
||||
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.
|
||||
|
||||
Writing Tests
|
||||
-------------
|
||||
|
||||
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.
|
||||
|
||||
### API Tests (`test/bdd/api`)
|
||||
|
||||
These tests are meant to test the different API endpoints and their parameters.
|
||||
They require a preimported test database, which consists of the import of a
|
||||
planet extract. A precompiled PBF with the necessary data can be downloaded from
|
||||
http://www.nominatim.org/data/test/nominatim-api-testdata.pbf
|
||||
They require to import several datasets into a test database.
|
||||
See the [Development Setup chapter](Development-Environment.md#preparing-the-test-database)
|
||||
for instructions on how to set up this database.
|
||||
|
||||
The polygons defining the extract can be found in the test/testdb
|
||||
directory. There is also a reduced set of wikipedia data for this extract,
|
||||
which you need to import as well. For Tiger tests the data of South Dakota
|
||||
is required. Get the Tiger files `46*`.
|
||||
The official test dataset was derived from the 180924 planet (note: such
|
||||
file no longer exists at https://planet.openstreetmap.org/planet/2018/).
|
||||
Newer planets are likely to work as well but you may see isolated test
|
||||
failures where the data has changed.
|
||||
|
||||
The official test dataset is derived from the 160725 planet. Newer
|
||||
planets are likely to work as well but you may see isolated test
|
||||
failures where the data has changed. To recreate the input data
|
||||
for the test database run:
|
||||
The official test dataset can always be downloaded from
|
||||
[nominatim.org](https://www.nominatim.org/data/test/nominatim-api-testdata.pbf)
|
||||
To recreate the input data for the test database run:
|
||||
|
||||
wget http://free.nchc.org.tw/osm.planet/pbf/planet-160725.osm.pbf
|
||||
osmconvert planet-160725.osm.pbf -B=test/testdb/testdb.polys -o=testdb.pbf
|
||||
|
||||
Before importing make sure to add the following to your local settings:
|
||||
|
||||
@define('CONST_Database_DSN', 'pgsql://@/test_api_nominatim');
|
||||
@define('CONST_Wikipedia_Data_Path', CONST_BasePath.'/test/testdb');
|
||||
```
|
||||
wget https://ftp5.gwdg.de/pub/misc/openstreetmap/planet.openstreetmap.org/pbf/planet-180924.osm.pbf
|
||||
osmconvert planet-180924.osm.pbf -B=test/testdb/testdb.polys -o=testdb.pbf
|
||||
```
|
||||
|
||||
#### Code Coverage
|
||||
|
||||
The API tests also support code coverage tests. You need to install
|
||||
PHP_CodeCoverage. On Debian/Ubuntu run:
|
||||
[PHP_CodeCoverage](https://github.com/sebastianbergmann/php-code-coverage).
|
||||
On Debian/Ubuntu run:
|
||||
|
||||
apt-get install php-codecoverage
|
||||
apt-get install php-codecoverage php-xdebug
|
||||
|
||||
The run the API tests as follows:
|
||||
Then run the API tests as follows:
|
||||
|
||||
behave api -DPHPCOV=<coverage output dir>
|
||||
|
||||
To generate reports, you can use the phpcov tool:
|
||||
The output directory must be an absolute path. To generate reports, you can use
|
||||
the [phpcov](https://github.com/sebastianbergmann/phpcov) tool:
|
||||
|
||||
phpcov merge --html=<report output dir> <coverage output dir>
|
||||
|
||||
### Indexing Tests (`test/bdd/db`)
|
||||
### DB Creation Tests (`test/bdd/db`)
|
||||
|
||||
These tests check the import and update of the Nominatim database. They do not
|
||||
test the correctness of osm2pgsql. Each test will write some data into the `place`
|
||||
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.
|
||||
|
||||
These tests need to create their own test databases. By default they will be
|
||||
@@ -150,4 +139,4 @@ needs superuser rights for postgres.
|
||||
### Import Tests (`test/bdd/osm2pgsql`)
|
||||
|
||||
These tests check that data is imported correctly into the place table. They
|
||||
use the same template database as the Indexing tests, so the same remarks apply.
|
||||
use the same template database as the DB Creation tests, so the same remarks apply.
|
||||
34
docs/develop/data-sources.md
Normal file
34
docs/develop/data-sources.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Additional Data Sources
|
||||
|
||||
This guide explains how data sources other than OpenStreetMap mentioned in
|
||||
the install instructions got obtained and converted.
|
||||
|
||||
## Country grid
|
||||
|
||||
Nominatim uses pre-generated country borders data. In case one imports only
|
||||
a subset of a country. And to assign each place a partition. Nominatim
|
||||
database tables are split into partitions for performance.
|
||||
|
||||
More details in [osm-search/country-grid-data](https://github.com/osm-search/country-grid-data).
|
||||
|
||||
## US Census TIGER
|
||||
|
||||
For the United States you can choose to import additonal street-level data.
|
||||
The data isn't mixed into OSM data but queried as fallback when no OSM
|
||||
result can be found.
|
||||
|
||||
More details in [osm-search/TIGER-data](https://github.com/osm-search/TIGER-data).
|
||||
|
||||
## GB postcodes
|
||||
|
||||
For Great Britain you can choose to import Royalmail postcode centroids.
|
||||
|
||||
More details in [osm-search/gb-postcode-data](https://github.com/osm-search/gb-postcode-data).
|
||||
|
||||
|
||||
## Wikipedia & Wikidata rankings
|
||||
|
||||
Nominatim can import "importance" data of place names. This greatly
|
||||
improves ranking of results.
|
||||
|
||||
More details in [osm-search/wikipedia-wikidata](https://github.com/osm-search/wikipedia-wikidata).
|
||||
24
docs/develop/overview.md
Normal file
24
docs/develop/overview.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Basic Architecture
|
||||
|
||||
Nominatim provides geocoding based on OpenStreetMap data. It uses a PostgreSQL
|
||||
database as a backend for storing the data.
|
||||
|
||||
There are three basic parts to Nominatim's architecture: the data import,
|
||||
the address computation and the search frontend.
|
||||
|
||||
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 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
|
||||
the import can be found in the database table `place`.
|
||||
|
||||
The __address computation__ or __indexing__ stage takes the data from `place`
|
||||
and adds additional information needed for geocoding. It ranks the places by
|
||||
importance, links objects that belong together and computes addresses and
|
||||
the search index. Most of this work is done in PL/pgSQL via database triggers
|
||||
and can be found in the files in the `sql/functions/` directory.
|
||||
|
||||
The __search frontend__ implements the actual API. It takes search
|
||||
and reverse geocoding queries from the user, looks up the data and
|
||||
returns the results in the requested format. This part is written in PHP
|
||||
and can be found in the `lib/` and `website/` directories.
|
||||
15
docs/extra.css
Normal file
15
docs/extra.css
Normal file
@@ -0,0 +1,15 @@
|
||||
.toctree-l3 {
|
||||
display: none!important
|
||||
}
|
||||
|
||||
table {
|
||||
margin-bottom: 12pt
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 1pt 12pt;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #eee;
|
||||
}
|
||||
8
docs/index.md
Normal file
8
docs/index.md
Normal file
@@ -0,0 +1,8 @@
|
||||
Nominatim (from the Latin, 'by name') is a tool to search OSM data by name and address and to generate synthetic addresses of OSM points (reverse geocoding).
|
||||
|
||||
This guide comes in three parts:
|
||||
|
||||
* __[API reference](api/Overview.md)__ for users of Nominatim
|
||||
* __[Administration Guide](admin/Installation.md)__ for those who want
|
||||
to install their own Nominatim server
|
||||
* __[Developer's Guide](develop/overview.md)__ for developers of the software
|
||||
44
docs/mkdocs.yml
Normal file
44
docs/mkdocs.yml
Normal file
@@ -0,0 +1,44 @@
|
||||
site_name: Nominatim 3.6.0
|
||||
theme: readthedocs
|
||||
docs_dir: ${CMAKE_CURRENT_BINARY_DIR}
|
||||
site_url: https://nominatim.org
|
||||
repo_url: https://github.com/openstreetmap/Nominatim
|
||||
pages:
|
||||
- 'Introduction' : 'index.md'
|
||||
- 'API Reference':
|
||||
- 'Overview': 'api/Overview.md'
|
||||
- 'Search': 'api/Search.md'
|
||||
- 'Reverse': 'api/Reverse.md'
|
||||
- 'Address Lookup': 'api/Lookup.md'
|
||||
- 'Details' : 'api/Details.md'
|
||||
- 'Status' : 'api/Status.md'
|
||||
- 'Place Output Formats': 'api/Output.md'
|
||||
- 'FAQ': 'api/Faq.md'
|
||||
- 'Administration Guide':
|
||||
- 'Basic Installation': 'admin/Installation.md'
|
||||
- 'Import' : 'admin/Import.md'
|
||||
- 'Update' : 'admin/Update.md'
|
||||
- 'Deploy' : 'admin/Deployment.md'
|
||||
- 'Nominatim UI' : 'admin/Setup-Nominatim-UI.md'
|
||||
- 'Advanced Installations' : 'admin/Advanced-Installations.md'
|
||||
- 'Migration from older Versions' : 'admin/Migration.md'
|
||||
- 'Troubleshooting' : 'admin/Faq.md'
|
||||
- 'Developers Guide':
|
||||
- 'Setup for Development' : 'develop/Development-Environment.md'
|
||||
- 'Architecture Overview' : 'develop/overview.md'
|
||||
- 'OSM Data Import' : 'develop/Import.md'
|
||||
- 'Place Ranking' : 'develop/Ranking.md'
|
||||
- 'Postcodes' : 'develop/Postcodes.md'
|
||||
- 'Testing' : 'develop/Testing.md'
|
||||
- 'External Data Sources': 'develop/data-sources.md'
|
||||
- 'Appendix':
|
||||
- 'Installation on CentOS 7' : 'appendix/Install-on-Centos-7.md'
|
||||
- 'Installation on CentOS 8' : 'appendix/Install-on-Centos-8.md'
|
||||
- 'Installation on Ubuntu 18' : 'appendix/Install-on-Ubuntu-18.md'
|
||||
- 'Installation on Ubuntu 20' : 'appendix/Install-on-Ubuntu-20.md'
|
||||
markdown_extensions:
|
||||
- codehilite
|
||||
- admonition
|
||||
- toc:
|
||||
permalink:
|
||||
extra_css: [extra.css, styles.css]
|
||||
69
docs/styles.css
Normal file
69
docs/styles.css
Normal file
@@ -0,0 +1,69 @@
|
||||
.codehilite .hll { background-color: #ffffcc }
|
||||
.codehilite { background: #f0f0f0; }
|
||||
.codehilite .c { color: #60a0b0; font-style: italic } /* Comment */
|
||||
.codehilite .err { /* border: 1px solid #FF0000 */ } /* Error */
|
||||
.codehilite .k { color: #007020; font-weight: bold } /* Keyword */
|
||||
.codehilite .o { color: #666666 } /* Operator */
|
||||
.codehilite .ch { color: #60a0b0; font-style: italic } /* Comment.Hashbang */
|
||||
.codehilite .cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */
|
||||
.codehilite .cp { color: #007020 } /* Comment.Preproc */
|
||||
.codehilite .cpf { color: #60a0b0; font-style: italic } /* Comment.PreprocFile */
|
||||
.codehilite .c1 { color: #60a0b0; font-style: italic } /* Comment.Single */
|
||||
.codehilite .cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */
|
||||
.codehilite .gd { color: #A00000 } /* Generic.Deleted */
|
||||
.codehilite .ge { font-style: italic } /* Generic.Emph */
|
||||
.codehilite .gr { color: #FF0000 } /* Generic.Error */
|
||||
.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||
.codehilite .gi { color: #00A000 } /* Generic.Inserted */
|
||||
.codehilite .go { color: #888888 } /* Generic.Output */
|
||||
.codehilite .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
|
||||
.codehilite .gs { font-weight: bold } /* Generic.Strong */
|
||||
.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||
.codehilite .gt { color: #0044DD } /* Generic.Traceback */
|
||||
.codehilite .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
|
||||
.codehilite .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
|
||||
.codehilite .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
|
||||
.codehilite .kp { color: #007020 } /* Keyword.Pseudo */
|
||||
.codehilite .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
|
||||
.codehilite .kt { color: #902000 } /* Keyword.Type */
|
||||
.codehilite .m { color: #40a070 } /* Literal.Number */
|
||||
.codehilite .s { color: #4070a0 } /* Literal.String */
|
||||
.codehilite .na { color: #4070a0 } /* Name.Attribute */
|
||||
.codehilite .nb { color: #007020 } /* Name.Builtin */
|
||||
.codehilite .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
|
||||
.codehilite .no { color: #60add5 } /* Name.Constant */
|
||||
.codehilite .nd { color: #555555; font-weight: bold } /* Name.Decorator */
|
||||
.codehilite .ni { color: #d55537; font-weight: bold } /* Name.Entity */
|
||||
.codehilite .ne { color: #007020 } /* Name.Exception */
|
||||
.codehilite .nf { color: #06287e } /* Name.Function */
|
||||
.codehilite .nl { color: #002070; font-weight: bold } /* Name.Label */
|
||||
.codehilite .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
|
||||
.codehilite .nt { color: #062873; font-weight: bold } /* Name.Tag */
|
||||
.codehilite .nv { color: #bb60d5 } /* Name.Variable */
|
||||
.codehilite .ow { color: #007020; font-weight: bold } /* Operator.Word */
|
||||
.codehilite .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.codehilite .mb { color: #40a070 } /* Literal.Number.Bin */
|
||||
.codehilite .mf { color: #40a070 } /* Literal.Number.Float */
|
||||
.codehilite .mh { color: #40a070 } /* Literal.Number.Hex */
|
||||
.codehilite .mi { color: #40a070 } /* Literal.Number.Integer */
|
||||
.codehilite .mo { color: #40a070 } /* Literal.Number.Oct */
|
||||
.codehilite .sa { color: #4070a0 } /* Literal.String.Affix */
|
||||
.codehilite .sb { color: #4070a0 } /* Literal.String.Backtick */
|
||||
.codehilite .sc { color: #4070a0 } /* Literal.String.Char */
|
||||
.codehilite .dl { color: #4070a0 } /* Literal.String.Delimiter */
|
||||
.codehilite .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
|
||||
.codehilite .s2 { color: #4070a0 } /* Literal.String.Double */
|
||||
.codehilite .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
|
||||
.codehilite .sh { color: #4070a0 } /* Literal.String.Heredoc */
|
||||
.codehilite .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
|
||||
.codehilite .sx { color: #c65d09 } /* Literal.String.Other */
|
||||
.codehilite .sr { color: #235388 } /* Literal.String.Regex */
|
||||
.codehilite .s1 { color: #4070a0 } /* Literal.String.Single */
|
||||
.codehilite .ss { color: #517918 } /* Literal.String.Symbol */
|
||||
.codehilite .bp { color: #007020 } /* Name.Builtin.Pseudo */
|
||||
.codehilite .fm { color: #06287e } /* Name.Function.Magic */
|
||||
.codehilite .vc { color: #bb60d5 } /* Name.Variable.Class */
|
||||
.codehilite .vg { color: #bb60d5 } /* Name.Variable.Global */
|
||||
.codehilite .vi { color: #bb60d5 } /* Name.Variable.Instance */
|
||||
.codehilite .vm { color: #bb60d5 } /* Name.Variable.Magic */
|
||||
.codehilite .il { color: #40a070 } /* Literal.Number.Integer.Long */
|
||||
171
lib/AddressDetails.php
Normal file
171
lib/AddressDetails.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim;
|
||||
|
||||
require_once(CONST_BasePath.'/lib/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($sCountry = null)
|
||||
{
|
||||
$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)) {
|
||||
$sTypeLabel = strtolower(str_replace(' ', '_', $sTypeLabel));
|
||||
if (!isset($aAddress[$sTypeLabel])
|
||||
|| $aLine['class'] == 'place'
|
||||
) {
|
||||
$aAddress[$sTypeLabel] = $sName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
568
lib/ClassTypes.php
Normal file
568
lib/ClassTypes.php
Normal file
@@ -0,0 +1,568 @@
|
||||
<?php
|
||||
|
||||
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;
|
||||
}
|
||||
421
lib/DB.php
Normal file
421
lib/DB.php
Normal file
@@ -0,0 +1,421 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim;
|
||||
|
||||
require_once(CONST_BasePath.'/lib/DatabaseError.php');
|
||||
|
||||
/**
|
||||
* Uses PDO to access the database specified in the CONST_Database_DSN
|
||||
* setting.
|
||||
*/
|
||||
class DB
|
||||
{
|
||||
protected $connection;
|
||||
|
||||
public function __construct($sDSN = CONST_Database_DSN)
|
||||
{
|
||||
$this->sDSN = $sDSN;
|
||||
}
|
||||
|
||||
public function connect($bNew = false, $bPersistent = true)
|
||||
{
|
||||
if (isset($this->connection) && !$bNew) {
|
||||
return true;
|
||||
}
|
||||
$aConnOptions = array(
|
||||
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
||||
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
|
||||
\PDO::ATTR_PERSISTENT => $bPersistent
|
||||
);
|
||||
|
||||
// https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php
|
||||
try {
|
||||
$conn = new \PDO($this->sDSN, null, null, $aConnOptions);
|
||||
} catch (\PDOException $e) {
|
||||
$sMsg = 'Failed to establish database connection:' . $e->getMessage();
|
||||
throw new \Nominatim\DatabaseError($sMsg, 500, null, $e->getMessage());
|
||||
}
|
||||
|
||||
$conn->exec("SET DateStyle TO 'sql,european'");
|
||||
$conn->exec("SET client_encoding TO 'utf-8'");
|
||||
$iMaxExecution = ini_get('max_execution_time');
|
||||
if ($iMaxExecution > 0) $conn->setAttribute(\PDO::ATTR_TIMEOUT, $iMaxExecution); // seconds
|
||||
|
||||
$this->connection = $conn;
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns the number of rows that were modified or deleted by the SQL
|
||||
// statement. If no rows were affected returns 0.
|
||||
public function exec($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
|
||||
{
|
||||
$val = null;
|
||||
try {
|
||||
if (isset($aInputVars)) {
|
||||
$stmt = $this->connection->prepare($sSQL);
|
||||
$stmt->execute($aInputVars);
|
||||
} else {
|
||||
$val = $this->connection->exec($sSQL);
|
||||
}
|
||||
} catch (\PDOException $e) {
|
||||
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes query. Returns first row as array.
|
||||
* Returns false if no result found.
|
||||
*
|
||||
* @param string $sSQL
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function getRow($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
|
||||
{
|
||||
try {
|
||||
$stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
|
||||
$row = $stmt->fetch();
|
||||
} catch (\PDOException $e) {
|
||||
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes query. Returns first value of first result.
|
||||
* Returns false if no results found.
|
||||
*
|
||||
* @param string $sSQL
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function getOne($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
|
||||
{
|
||||
try {
|
||||
$stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
|
||||
$row = $stmt->fetch(\PDO::FETCH_NUM);
|
||||
if ($row === false) return false;
|
||||
} catch (\PDOException $e) {
|
||||
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
||||
}
|
||||
return $row[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes query. Returns array of results (arrays).
|
||||
* Returns empty array if no results found.
|
||||
*
|
||||
* @param string $sSQL
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function getAll($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
|
||||
{
|
||||
try {
|
||||
$stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
|
||||
$rows = $stmt->fetchAll();
|
||||
} catch (\PDOException $e) {
|
||||
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes query. Returns array of the first value of each result.
|
||||
* Returns empty array if no results found.
|
||||
*
|
||||
* @param string $sSQL
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function getCol($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
|
||||
{
|
||||
$aVals = array();
|
||||
try {
|
||||
$stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
|
||||
|
||||
while (($val = $stmt->fetchColumn(0)) !== false) { // returns first column or false
|
||||
$aVals[] = $val;
|
||||
}
|
||||
} catch (\PDOException $e) {
|
||||
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
||||
}
|
||||
return $aVals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes query. Returns associate array mapping first value to second value of each result.
|
||||
* Returns empty array if no results found.
|
||||
*
|
||||
* @param string $sSQL
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function getAssoc($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
|
||||
{
|
||||
try {
|
||||
$stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
|
||||
|
||||
$aList = array();
|
||||
while ($aRow = $stmt->fetch(\PDO::FETCH_NUM)) {
|
||||
$aList[$aRow[0]] = $aRow[1];
|
||||
}
|
||||
} catch (\PDOException $e) {
|
||||
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
||||
}
|
||||
return $aList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes query. Returns a PDO statement to iterate over.
|
||||
*
|
||||
* @param string $sSQL
|
||||
*
|
||||
* @return PDOStatement
|
||||
*/
|
||||
public function getQueryStatement($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
|
||||
{
|
||||
try {
|
||||
if (isset($aInputVars)) {
|
||||
$stmt = $this->connection->prepare($sSQL);
|
||||
$stmt->execute($aInputVars);
|
||||
} else {
|
||||
$stmt = $this->connection->query($sSQL);
|
||||
}
|
||||
} catch (\PDOException $e) {
|
||||
throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
|
||||
}
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* St. John's Way => 'St. John\'s Way'
|
||||
*
|
||||
* @param string $sVal Text to be quoted.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDBQuoted($sVal)
|
||||
{
|
||||
return $this->connection->quote($sVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like getDBQuoted, but takes an array.
|
||||
*
|
||||
* @param array $aVals List of text to be quoted.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function getDBQuotedList($aVals)
|
||||
{
|
||||
return array_map(function ($sVal) {
|
||||
return $this->getDBQuoted($sVal);
|
||||
}, $aVals);
|
||||
}
|
||||
|
||||
/**
|
||||
* [1,2,'b'] => 'ARRAY[1,2,'b']''
|
||||
*
|
||||
* @param array $aVals List of text to be quoted.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getArraySQL($a)
|
||||
{
|
||||
return 'ARRAY['.join(',', $a).']';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a table exists in the database. Returns true if it does.
|
||||
*
|
||||
* @param string $sTableName
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function tableExists($sTableName)
|
||||
{
|
||||
$sSQL = 'SELECT count(*) FROM pg_tables WHERE tablename = :tablename';
|
||||
return ($this->getOne($sSQL, array(':tablename' => $sTableName)) == 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of table names in the database
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function getListOfTables()
|
||||
{
|
||||
return $this->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a table. Returns true if deleted or didn't exist.
|
||||
*
|
||||
* @param string $sTableName
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function deleteTable($sTableName)
|
||||
{
|
||||
return $this->exec('DROP TABLE IF EXISTS '.$sTableName.' CASCADE') == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an index exists in the database. Optional filtered by tablename
|
||||
*
|
||||
* @param string $sTableName
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function indexExists($sIndexName, $sTableName = null)
|
||||
{
|
||||
return in_array($sIndexName, $this->getListOfIndices($sTableName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of index names in the database, optional filtered by tablename
|
||||
*
|
||||
* @param string $sTableName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getListOfIndices($sTableName = null)
|
||||
{
|
||||
// table_name | index_name | column_name
|
||||
// -----------------------+---------------------------------+--------------
|
||||
// country_name | idx_country_name_country_code | country_code
|
||||
// country_osm_grid | idx_country_osm_grid_geometry | geometry
|
||||
// import_polygon_delete | idx_import_polygon_delete_osmid | osm_id
|
||||
// import_polygon_delete | idx_import_polygon_delete_osmid | osm_type
|
||||
// import_polygon_error | idx_import_polygon_error_osmid | osm_id
|
||||
// import_polygon_error | idx_import_polygon_error_osmid | osm_type
|
||||
$sSql = <<< END
|
||||
SELECT
|
||||
t.relname as table_name,
|
||||
i.relname as index_name,
|
||||
a.attname as column_name
|
||||
FROM
|
||||
pg_class t,
|
||||
pg_class i,
|
||||
pg_index ix,
|
||||
pg_attribute a
|
||||
WHERE
|
||||
t.oid = ix.indrelid
|
||||
and i.oid = ix.indexrelid
|
||||
and a.attrelid = t.oid
|
||||
and a.attnum = ANY(ix.indkey)
|
||||
and t.relkind = 'r'
|
||||
and i.relname NOT LIKE 'pg_%'
|
||||
FILTERS
|
||||
ORDER BY
|
||||
t.relname,
|
||||
i.relname,
|
||||
a.attname
|
||||
END;
|
||||
|
||||
$aRows = null;
|
||||
if ($sTableName) {
|
||||
$sSql = str_replace('FILTERS', 'and t.relname = :tablename', $sSql);
|
||||
$aRows = $this->getAll($sSql, array(':tablename' => $sTableName));
|
||||
} else {
|
||||
$sSql = str_replace('FILTERS', '', $sSql);
|
||||
$aRows = $this->getAll($sSql);
|
||||
}
|
||||
|
||||
$aIndexNames = array_unique(array_map(function ($aRow) {
|
||||
return $aRow['index_name'];
|
||||
}, $aRows));
|
||||
sort($aIndexNames);
|
||||
|
||||
return $aIndexNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to connect to the database but on failure doesn't throw an exception.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function checkConnection()
|
||||
{
|
||||
$bExists = true;
|
||||
try {
|
||||
$this->connect(true);
|
||||
} catch (\Nominatim\DatabaseError $e) {
|
||||
$bExists = false;
|
||||
}
|
||||
return $bExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* e.g. 9.6, 10, 11.2
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getPostgresVersion()
|
||||
{
|
||||
$sVersionString = $this->getOne('SHOW server_version_num');
|
||||
preg_match('#([0-9]?[0-9])([0-9][0-9])[0-9][0-9]#', $sVersionString, $aMatches);
|
||||
return (float) ($aMatches[1].'.'.$aMatches[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* e.g. 2, 2.2
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getPostgisVersion()
|
||||
{
|
||||
$sVersionString = $this->getOne('select postgis_lib_version()');
|
||||
preg_match('#^([0-9]+)[.]([0-9]+)[.]#', $sVersionString, $aMatches);
|
||||
return (float) ($aMatches[1].'.'.$aMatches[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an associate array of postgresql database connection settings. Keys can
|
||||
* be 'database', 'hostspec', 'port', 'username', 'password'.
|
||||
* Returns empty array on failure, thus check if at least 'database' is set.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public static function parseDSN($sDSN)
|
||||
{
|
||||
// https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php
|
||||
$aInfo = array();
|
||||
if (preg_match('/^pgsql:(.+)$/', $sDSN, $aMatches)) {
|
||||
foreach (explode(';', $aMatches[1]) as $sKeyVal) {
|
||||
list($sKey, $sVal) = explode('=', $sKeyVal, 2);
|
||||
if ($sKey == 'host') $sKey = 'hostspec';
|
||||
if ($sKey == 'dbname') $sKey = 'database';
|
||||
if ($sKey == 'user') $sKey = 'username';
|
||||
$aInfo[$sKey] = $sVal;
|
||||
}
|
||||
}
|
||||
return $aInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an array of settings and return the DNS string. Key names can be
|
||||
* 'database', 'hostspec', 'port', 'username', 'password' but aliases
|
||||
* 'dbname', 'host' and 'user' are also supported.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public static function generateDSN($aInfo)
|
||||
{
|
||||
$sDSN = sprintf(
|
||||
'pgsql:host=%s;port=%s;dbname=%s;user=%s;password=%s;',
|
||||
$aInfo['host'] ?? $aInfo['hostspec'] ?? '',
|
||||
$aInfo['port'] ?? '',
|
||||
$aInfo['dbname'] ?? $aInfo['database'] ?? '',
|
||||
$aInfo['user'] ?? '',
|
||||
$aInfo['password'] ?? ''
|
||||
);
|
||||
$sDSN = preg_replace('/\b\w+=;/', '', $sDSN);
|
||||
$sDSN = preg_replace('/;\Z/', '', $sDSN);
|
||||
|
||||
return $sDSN;
|
||||
}
|
||||
}
|
||||
34
lib/DatabaseError.php
Normal file
34
lib/DatabaseError.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim;
|
||||
|
||||
class DatabaseError extends \Exception
|
||||
{
|
||||
|
||||
public function __construct($message, $code = 500, Exception $previous = null, $oPDOErr, $sSql = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
// https://secure.php.net/manual/en/class.pdoexception.php
|
||||
$this->oPDOErr = $oPDOErr;
|
||||
$this->sSql = $sSql;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
|
||||
}
|
||||
|
||||
public function getSqlError()
|
||||
{
|
||||
return $this->oPDOErr->getMessage();
|
||||
}
|
||||
|
||||
public function getSqlDebugDump()
|
||||
{
|
||||
if (CONST_Debug) {
|
||||
return var_export($this->oPDOErr, true);
|
||||
} else {
|
||||
return $this->sSql;
|
||||
}
|
||||
}
|
||||
}
|
||||
180
lib/DebugHtml.php
Normal file
180
lib/DebugHtml.php
Normal file
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
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 $iRank => $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><font color="#aaa">'.$sSQL.'</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)) {
|
||||
echo "'$mVar'";
|
||||
return strlen($mVar) + 2;
|
||||
}
|
||||
|
||||
echo (string)$mVar;
|
||||
return strlen((string)$mVar);
|
||||
}
|
||||
}
|
||||
11
lib/DebugNone.php
Normal file
11
lib/DebugNone.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim;
|
||||
|
||||
class Debug
|
||||
{
|
||||
public static function __callStatic($name, $arguments)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
1781
lib/Geocode.php
1781
lib/Geocode.php
File diff suppressed because it is too large
Load Diff
@@ -1,157 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim;
|
||||
|
||||
/**
|
||||
* A geographic point with a search radius.
|
||||
*/
|
||||
class NearPoint
|
||||
{
|
||||
private $fLat;
|
||||
private $fLon;
|
||||
private $fRadius;
|
||||
|
||||
private $sSQL;
|
||||
|
||||
|
||||
public function __construct($lat, $lon, $radius = 0.1)
|
||||
{
|
||||
$this->fLat = (float)$lat;
|
||||
$this->fLon = (float)$lon;
|
||||
$this->fRadius = (float)$radius;
|
||||
$this->sSQL = 'ST_SetSRID(ST_Point('.$this->fLon.','.$this->fLat.'),4326)';
|
||||
}
|
||||
|
||||
public function lat()
|
||||
{
|
||||
return $this->fLat;
|
||||
}
|
||||
|
||||
public function lon()
|
||||
{
|
||||
return $this->fLon;
|
||||
}
|
||||
|
||||
public function radius()
|
||||
{
|
||||
return $this->fRadius;
|
||||
}
|
||||
|
||||
public function distanceSQL($sObj)
|
||||
{
|
||||
return 'ST_Distance('.$this->sSQL.", $sObj)";
|
||||
}
|
||||
|
||||
public function withinSQL($sObj)
|
||||
{
|
||||
return sprintf('ST_DWithin(%s, %s, %F)', $sObj, $this->sSQL, $this->fRadius);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the coordinates are valid WSG84 coordinates.
|
||||
*
|
||||
* @return bool True if the coordinates are correctly bounded.
|
||||
*/
|
||||
public function isValid()
|
||||
{
|
||||
return ($this->fLat <= 90.1
|
||||
&& $this->fLat >= -90.1
|
||||
&& $this->fLon <= 180.1
|
||||
&& $this->fLon >= -180.1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a coordinate point from a query string.
|
||||
*
|
||||
* If a coordinate is found an array of a new NearPoint and the
|
||||
* remaining query is returned or false otherwise.
|
||||
*
|
||||
* @param string $sQuery Query to scan.
|
||||
*
|
||||
* @return array|false If a coordinate was found, an array with
|
||||
* `pt` as the NearPoint coordinates and `query`
|
||||
* with the remaining query string. False otherwiese.
|
||||
*/
|
||||
public static function extractFromQuery($sQuery)
|
||||
{
|
||||
// Do we have anything that looks like a lat/lon pair?
|
||||
// returns array(lat,lon,query_with_lat_lon_removed)
|
||||
// or null
|
||||
$sFound = null;
|
||||
$fQueryLat = null;
|
||||
$fQueryLon = null;
|
||||
|
||||
if (preg_match('/\\b([NS])[ ]+([0-9]+[0-9.]*)[° ]+([0-9.]+)?[′\']*[, ]+([EW])[ ]+([0-9]+)[° ]+([0-9]+[0-9.]*)[′\']*?\\b/', $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('/\\b([0-9]+)[° ]+([0-9]+[0-9.]*)?[′\']*[ ]+([NS])[, ]+([0-9]+)[° ]+([0-9]+[0-9.]*)?[′\' ]+([EW])\\b/', $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('/\\b([NS])[ ]([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″"]*[, ]+([EW])[ ]([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″"]*\\b/', $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('/\\b([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″" ]+([NS])[, ]+([0-9]+)[° ]+([0-9]+)[′\' ]+([0-9]+)[″" ]+([EW])\\b/', $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
|
||||
*/
|
||||
$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('/\\b([NS])[ ]([0-9]+[0-9]*\\.[0-9]+)[°]*[, ]+([EW])[ ]([0-9]+[0-9]*\\.[0-9]+)[°]*\\b/', $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('/\\b([0-9]+[0-9]*\\.[0-9]+)[° ]+([NS])[, ]+([0-9]+[0-9]*\\.[0-9]+)[° ]+([EW])\\b/', $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('/(\\[|^|\\b)(-?[0-9]+[0-9]*\\.[0-9]+)[, ]+(-?[0-9]+[0-9]*\\.[0-9]+)(\\]|$|\\b)/', $sQuery, $aData)) {
|
||||
/* 1 2 3 4
|
||||
* degrees decimal
|
||||
* 12.34, 56.78
|
||||
* [12.456,-78.90]
|
||||
*/
|
||||
$sFound = $aData[0];
|
||||
$fQueryLat = $aData[2];
|
||||
$fQueryLon = $aData[3];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
$oPt = new NearPoint($fQueryLat, $fQueryLon);
|
||||
|
||||
if (!$oPt->isValid()) return false;
|
||||
|
||||
$sQuery = trim(str_replace($sFound, ' ', $sQuery));
|
||||
|
||||
return array('pt' => $oPt, 'query' => $sQuery);
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ class ParameterParser
|
||||
|
||||
public function getInt($sName, $bDefault = false)
|
||||
{
|
||||
if (!isset($this->aParams[$sName]) || strlen($this->aParams[$sName]) == 0) {
|
||||
if (!isset($this->aParams[$sName])) {
|
||||
return $bDefault;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class ParameterParser
|
||||
|
||||
public function getFloat($sName, $bDefault = false)
|
||||
{
|
||||
if (!isset($this->aParams[$sName]) || strlen($this->aParams[$sName]) == 0) {
|
||||
if (!isset($this->aParams[$sName])) {
|
||||
return $bDefault;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,8 @@ class ParameterParser
|
||||
$sValue = $this->getString($sName);
|
||||
|
||||
if ($sValue) {
|
||||
return explode(',', $sValue);
|
||||
// removes all NULL, FALSE and Empty Strings but leaves 0 (zero) values
|
||||
return array_values(array_filter(explode(',', $sValue), 'strlen'));
|
||||
}
|
||||
|
||||
return $aDefault;
|
||||
@@ -82,15 +83,15 @@ class ParameterParser
|
||||
|
||||
public function getPreferredLanguages($sFallback = null)
|
||||
{
|
||||
if ($sFallback === null && isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) {
|
||||
$sFallback = $_SERVER["HTTP_ACCEPT_LANGUAGE"];
|
||||
if ($sFallback === null && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
||||
$sFallback = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
|
||||
}
|
||||
|
||||
$aLanguages = array();
|
||||
$sLangString = $this->getString('accept-language', $sFallback);
|
||||
|
||||
if ($sLangString) {
|
||||
if (preg_match_all('/(([a-z]{1,8})(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $sLangString, $aLanguagesParse, PREG_SET_ORDER)) {
|
||||
if (preg_match_all('/(([a-z]{1,8})([-_][a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $sLangString, $aLanguagesParse, PREG_SET_ORDER)) {
|
||||
foreach ($aLanguagesParse as $iLang => $aLanguage) {
|
||||
$aLanguages[$aLanguage[1]] = isset($aLanguage[5])?(float)$aLanguage[5]:1 - ($iLang/100);
|
||||
if (!isset($aLanguages[$aLanguage[2]])) $aLanguages[$aLanguage[2]] = $aLanguages[$aLanguage[1]]/10;
|
||||
@@ -98,23 +99,34 @@ class ParameterParser
|
||||
arsort($aLanguages);
|
||||
}
|
||||
}
|
||||
if (!sizeof($aLanguages) && CONST_Default_Language) {
|
||||
if (empty($aLanguages) && CONST_Default_Language) {
|
||||
$aLanguages[CONST_Default_Language] = 1;
|
||||
}
|
||||
|
||||
foreach ($aLanguages as $sLanguage => $fLanguagePref) {
|
||||
$aLangPrefOrder['short_name:'.$sLanguage] = 'short_name:'.$sLanguage;
|
||||
$aLangPrefOrder['name:'.$sLanguage] = 'name:'.$sLanguage;
|
||||
}
|
||||
$aLangPrefOrder['short_name'] = 'short_name';
|
||||
$aLangPrefOrder['name'] = 'name';
|
||||
$aLangPrefOrder['brand'] = 'brand';
|
||||
foreach ($aLanguages as $sLanguage => $fLanguagePref) {
|
||||
$aLangPrefOrder['official_name:'.$sLanguage] = 'official_name:'.$sLanguage;
|
||||
$aLangPrefOrder['short_name:'.$sLanguage] = 'short_name:'.$sLanguage;
|
||||
}
|
||||
$aLangPrefOrder['official_name'] = 'official_name';
|
||||
$aLangPrefOrder['short_name'] = 'short_name';
|
||||
$aLangPrefOrder['ref'] = 'ref';
|
||||
$aLangPrefOrder['type'] = 'type';
|
||||
return $aLangPrefOrder;
|
||||
}
|
||||
|
||||
public function hasSetAny($aParamNames)
|
||||
{
|
||||
foreach ($aParamNames as $sName) {
|
||||
if ($this->getBool($sName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
160
lib/Phrase.php
Normal file
160
lib/Phrase.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim;
|
||||
|
||||
/**
|
||||
* Segment of a query string.
|
||||
*
|
||||
* The parts of a query strings are usually separated by commas.
|
||||
*/
|
||||
class Phrase
|
||||
{
|
||||
const MAX_WORDSET_LEN = 20;
|
||||
const MAX_WORDSETS = 100;
|
||||
|
||||
// Complete phrase as a string.
|
||||
private $sPhrase;
|
||||
// Element type for structured searches.
|
||||
private $sPhraseType;
|
||||
// Space-separated words of the phrase.
|
||||
private $aWords;
|
||||
// Possible segmentations of the phrase.
|
||||
private $aWordSets;
|
||||
|
||||
public static function cmpByArraylen($aA, $aB)
|
||||
{
|
||||
$iALen = count($aA);
|
||||
$iBLen = count($aB);
|
||||
|
||||
if ($iALen == $iBLen) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ($iALen < $iBLen) ? -1 : 1;
|
||||
}
|
||||
|
||||
|
||||
public function __construct($sPhrase, $sPhraseType)
|
||||
{
|
||||
$this->sPhrase = trim($sPhrase);
|
||||
$this->sPhraseType = $sPhraseType;
|
||||
$this->aWords = explode(' ', $this->sPhrase);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the tokens from this phrase to the given list of tokens.
|
||||
*
|
||||
* @param string[] $aTokens List of tokens to append.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addTokens(&$aTokens)
|
||||
{
|
||||
$iNumWords = count($this->aWords);
|
||||
|
||||
for ($i = 0; $i < $iNumWords; $i++) {
|
||||
$sPhrase = $this->aWords[$i];
|
||||
$aTokens[' '.$sPhrase] = ' '.$sPhrase;
|
||||
$aTokens[$sPhrase] = $sPhrase;
|
||||
|
||||
for ($j = $i + 1; $j < $iNumWords; $j++) {
|
||||
$sPhrase .= ' '.$this->aWords[$j];
|
||||
$aTokens[' '.$sPhrase] = ' '.$sPhrase;
|
||||
$aTokens[$sPhrase] = $sPhrase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 computeWordSets($oTokens)
|
||||
{
|
||||
$iNumWords = count($this->aWords);
|
||||
// Caches the word set for the partial phrase up to word i.
|
||||
$aSetCache = array_fill(0, $iNumWords, array());
|
||||
|
||||
// Initialise first element of cache. There can only be the word.
|
||||
if ($oTokens->containsAny($this->aWords[0])) {
|
||||
$aSetCache[0][] = array($this->aWords[0]);
|
||||
}
|
||||
|
||||
// Now do the next elements using what we already have.
|
||||
for ($i = 1; $i < $iNumWords; $i++) {
|
||||
for ($j = $i; $j > 0; $j--) {
|
||||
$sPartial = $j == $i ? $this->aWords[$j] : $this->aWords[$j].' '.$sPartial;
|
||||
if (!empty($aSetCache[$j - 1]) && $oTokens->containsAny($sPartial)) {
|
||||
$aPartial = array($sPartial);
|
||||
foreach ($aSetCache[$j - 1] as $aSet) {
|
||||
if (count($aSet) < Phrase::MAX_WORDSET_LEN) {
|
||||
$aSetCache[$i][] = array_merge($aSet, $aPartial);
|
||||
}
|
||||
}
|
||||
if (count($aSetCache[$i]) > 2 * Phrase::MAX_WORDSETS) {
|
||||
usort(
|
||||
$aSetCache[$i],
|
||||
array('\Nominatim\Phrase', 'cmpByArraylen')
|
||||
);
|
||||
$aSetCache[$i] = array_slice(
|
||||
$aSetCache[$i],
|
||||
0,
|
||||
Phrase::MAX_WORDSETS
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finally the current full phrase
|
||||
$sPartial = $this->aWords[0].' '.$sPartial;
|
||||
if ($oTokens->containsAny($sPartial)) {
|
||||
$aSetCache[$i][] = array($sPartial);
|
||||
}
|
||||
}
|
||||
|
||||
$this->aWordSets = $aSetCache[$iNumWords - 1];
|
||||
usort($this->aWordSets, array('\Nominatim\Phrase', 'cmpByArraylen'));
|
||||
$this->aWordSets = array_slice($this->aWordSets, 0, Phrase::MAX_WORDSETS);
|
||||
}
|
||||
|
||||
|
||||
public function debugInfo()
|
||||
{
|
||||
return array(
|
||||
'Type' => $this->sPhraseType,
|
||||
'Phrase' => $this->sPhrase,
|
||||
'Words' => $this->aWords,
|
||||
'WordSets' => $this->aWordSets
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,245 +2,472 @@
|
||||
|
||||
namespace Nominatim;
|
||||
|
||||
require_once(CONST_BasePath.'/lib/AddressDetails.php');
|
||||
require_once(CONST_BasePath.'/lib/Result.php');
|
||||
|
||||
class PlaceLookup
|
||||
{
|
||||
protected $oDB;
|
||||
|
||||
protected $aLangPrefOrder = array();
|
||||
protected $aLangPrefOrderSql = "''";
|
||||
|
||||
protected $bAddressDetails = false;
|
||||
protected $bExtraTags = false;
|
||||
protected $bNameDetails = false;
|
||||
|
||||
protected $bIncludePolygonAsPoints = 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.' polgyon 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->aLangPrefOrder = $aLangPrefOrder;
|
||||
$this->aLangPrefOrderSql = $this->oDB->getArraySQL(
|
||||
$this->oDB->getDBQuotedList($aLangPrefOrder)
|
||||
);
|
||||
}
|
||||
|
||||
public function setIncludeAddressDetails($bAddressDetails = true)
|
||||
private function addressImportanceSql($sGeometry, $sPlaceId)
|
||||
{
|
||||
$this->bAddressDetails = $bAddressDetails;
|
||||
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,';
|
||||
}
|
||||
|
||||
public function setIncludeExtraTags($bExtraTags = false)
|
||||
private function langAddressSql($sHousenumber)
|
||||
{
|
||||
$this->bExtraTags = $bExtraTags;
|
||||
}
|
||||
if ($this->bAddressDetails)
|
||||
return ''; // langaddress will be computed from address details
|
||||
|
||||
public function setIncludeNameDetails($bNameDetails = false)
|
||||
{
|
||||
$this->bNameDetails = $bNameDetails;
|
||||
}
|
||||
|
||||
|
||||
public function setIncludePolygonAsPoints($b = true)
|
||||
{
|
||||
$this->bIncludePolygonAsPoints = $b;
|
||||
}
|
||||
|
||||
public function getIncludePolygonAsPoints()
|
||||
{
|
||||
return $this->bIncludePolygonAsPoints;
|
||||
}
|
||||
|
||||
public function setIncludePolygonAsText($b = true)
|
||||
{
|
||||
$this->bIncludePolygonAsText = $b;
|
||||
}
|
||||
|
||||
public function getIncludePolygonAsText()
|
||||
{
|
||||
return $this->bIncludePolygonAsText;
|
||||
}
|
||||
|
||||
public function setIncludePolygonAsGeoJSON($b = true)
|
||||
{
|
||||
$this->bIncludePolygonAsGeoJSON = $b;
|
||||
}
|
||||
|
||||
public function setIncludePolygonAsKML($b = true)
|
||||
{
|
||||
$this->bIncludePolygonAsKML = $b;
|
||||
}
|
||||
|
||||
public function setIncludePolygonAsSVG($b = true)
|
||||
{
|
||||
$this->bIncludePolygonAsSVG = $b;
|
||||
}
|
||||
|
||||
public function setPolygonSimplificationThreshold($f)
|
||||
{
|
||||
$this->fPolygonSimplificationThreshold = $f;
|
||||
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 = '".pg_escape_string($sType)."' and osm_id = ".(int)$iID." order by type = 'postcode' asc";
|
||||
$iPlaceID = chksql($this->oDB->getOne($sSQL));
|
||||
$sSQL = 'select place_id from placex where osm_type = :type and osm_id = :id';
|
||||
$iPlaceID = $this->oDB->getOne($sSQL, array(':type' => $sType, ':id' => $iID));
|
||||
|
||||
return $this->lookup((int)$iPlaceID);
|
||||
if (!$iPlaceID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$aResults = $this->lookup(array($iPlaceID => new Result($iPlaceID)));
|
||||
|
||||
return empty($aResults) ? null : reset($aResults);
|
||||
}
|
||||
|
||||
public function lookup($iPlaceID, $sType = '', $fInterpolFraction = 0.0)
|
||||
public function lookup($aResults, $iMinRank = 0, $iMaxRank = 30)
|
||||
{
|
||||
if (!$iPlaceID) return null;
|
||||
Debug::newFunction('Place lookup');
|
||||
|
||||
$sLanguagePrefArraySQL = "ARRAY[".join(',', array_map("getDBQuoted", $this->aLangPrefOrder))."]";
|
||||
$bIsTiger = CONST_Use_US_Tiger_Data && $sType == 'tiger';
|
||||
$bIsInterpolation = $sType == 'interpolation';
|
||||
|
||||
if ($bIsTiger) {
|
||||
$sSQL = "select place_id,partition, 'T' as osm_type, place_id as osm_id, 'place' as class, 'house' as type, null as admin_level, housenumber, postcode,";
|
||||
$sSQL .= " 'us' as country_code, parent_place_id, null as linked_place_id, 30 as rank_address, 30 as rank_search,";
|
||||
$sSQL .= " coalesce(null,0.75-(30::float/40)) as importance, null as indexed_status, null as indexed_date, null as wikipedia, 'us' as country_code, ";
|
||||
$sSQL .= " get_address_by_language(place_id, housenumber, $sLanguagePrefArraySQL) as langaddress,";
|
||||
$sSQL .= " null as placename,";
|
||||
$sSQL .= " null as ref,";
|
||||
if ($this->bExtraTags) $sSQL .= " null as extra,";
|
||||
if ($this->bNameDetails) $sSQL .= " null as names,";
|
||||
$sSQL .= " ST_X(point) as lon, ST_Y(point) as lat from (select *, ST_LineInterpolatePoint(linegeo, (housenumber-startnumber::float)/(endnumber-startnumber)::float) as point from ";
|
||||
$sSQL .= " (select *, ";
|
||||
$sSQL .= " CASE WHEN interpolationtype='odd' THEN floor((".$fInterpolFraction."*(endnumber-startnumber)+startnumber)/2)::int*2+1";
|
||||
$sSQL .= " WHEN interpolationtype='even' THEN ((".$fInterpolFraction."*(endnumber-startnumber)+startnumber)/2)::int*2";
|
||||
$sSQL .= " WHEN interpolationtype='all' THEN (".$fInterpolFraction."*(endnumber-startnumber)+startnumber)::int";
|
||||
$sSQL .= " END as housenumber";
|
||||
$sSQL .= " from location_property_tiger where place_id = ".$iPlaceID.") as blub1) as blub2";
|
||||
} elseif ($bIsInterpolation) {
|
||||
$sSQL = "select place_id, partition, 'W' as osm_type, osm_id, 'place' as class, 'house' as type, null admin_level, housenumber, postcode,";
|
||||
$sSQL .= " country_code, parent_place_id, null as linked_place_id, 30 as rank_address, 30 as rank_search,";
|
||||
$sSQL .= " (0.75-(30::float/40)) as importance, null as indexed_status, null as indexed_date, null as wikipedia, country_code, ";
|
||||
$sSQL .= " get_address_by_language(place_id, housenumber, $sLanguagePrefArraySQL) as langaddress,";
|
||||
$sSQL .= " null as placename,";
|
||||
$sSQL .= " null as ref,";
|
||||
if ($this->bExtraTags) $sSQL .= " null as extra,";
|
||||
if ($this->bNameDetails) $sSQL .= " null as names,";
|
||||
$sSQL .= " ST_X(point) as lon, ST_Y(point) as lat from (select *, ST_LineInterpolatePoint(linegeo, (housenumber-startnumber::float)/(endnumber-startnumber)::float) as point from ";
|
||||
$sSQL .= " (select *, ";
|
||||
$sSQL .= " CASE WHEN interpolationtype='odd' THEN floor((".$fInterpolFraction."*(endnumber-startnumber)+startnumber)/2)::int*2+1";
|
||||
$sSQL .= " WHEN interpolationtype='even' THEN ((".$fInterpolFraction."*(endnumber-startnumber)+startnumber)/2)::int*2";
|
||||
$sSQL .= " WHEN interpolationtype='all' THEN (".$fInterpolFraction."*(endnumber-startnumber)+startnumber)::int";
|
||||
$sSQL .= " END as housenumber";
|
||||
$sSQL .= " from location_property_osmline where place_id = ".$iPlaceID.") as blub1) as blub2";
|
||||
// testcase: interpolationtype=odd, startnumber=1000, endnumber=1006, fInterpolFraction=1 => housenumber=1007 => error in st_lineinterpolatepoint
|
||||
// but this will never happen, because if the searched point is that close to the endnumber, the endnumber house will be directly taken from placex (in ReverseGeocode.php line 220)
|
||||
// and not interpolated
|
||||
} else {
|
||||
$sSQL = "select placex.place_id, partition, osm_type, osm_id, class,";
|
||||
$sSQL .= " type, admin_level, housenumber, postcode, country_code,";
|
||||
$sSQL .= " parent_place_id, linked_place_id, rank_address, rank_search, ";
|
||||
$sSQL .= " coalesce(importance,0.75-(rank_search::float/40)) as importance, indexed_status, indexed_date, wikipedia, country_code, ";
|
||||
$sSQL .= " get_address_by_language(place_id, -1, $sLanguagePrefArraySQL) as langaddress,";
|
||||
$sSQL .= " get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
|
||||
$sSQL .= " get_name_by_language(name, ARRAY['ref']) as ref,";
|
||||
if ($this->bExtraTags) $sSQL .= " hstore_to_json(extratags) as extra,";
|
||||
if ($this->bNameDetails) $sSQL .= " hstore_to_json(name) as names,";
|
||||
$sSQL .= " (case when centroid is null then st_y(st_centroid(geometry)) else st_y(centroid) end) as lat,";
|
||||
$sSQL .= " (case when centroid is null then st_x(st_centroid(geometry)) else st_x(centroid) end) as lon";
|
||||
$sSQL .= " from placex where place_id = ".$iPlaceID;
|
||||
if (empty($aResults)) {
|
||||
return array();
|
||||
}
|
||||
$aSubSelects = array();
|
||||
|
||||
$aPlace = chksql($this->oDB->getRow($sSQL), "Could not lookup place");
|
||||
|
||||
if (!$aPlace['place_id']) return null;
|
||||
|
||||
if ($this->bAddressDetails) {
|
||||
// to get addressdetails for tiger data, the housenumber is needed
|
||||
$iHousenumber = ($bIsTiger || $bIsInterpolation) ? $aPlace['housenumber'] : -1;
|
||||
$aPlace['aAddress'] = $this->getAddressNames($aPlace['place_id'], $iHousenumber);
|
||||
}
|
||||
|
||||
if ($this->bExtraTags) {
|
||||
if ($aPlace['extra']) {
|
||||
$aPlace['sExtraTags'] = json_decode($aPlace['extra']);
|
||||
} else {
|
||||
$aPlace['sExtraTags'] = (object) 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) {
|
||||
if ($aPlace['names']) {
|
||||
$aPlace['sNameDetails'] = json_decode($aPlace['names']);
|
||||
} else {
|
||||
$aPlace['sNameDetails'] = (object) array();
|
||||
if ($this->bNameDetails) {
|
||||
$sSQL .= 'hstore_to_json(name)::text AS names,';
|
||||
}
|
||||
}
|
||||
|
||||
$aClassType = getClassTypes();
|
||||
$sAddressType = '';
|
||||
$sClassType = $aPlace['class'].':'.$aPlace['type'].':'.$aPlace['admin_level'];
|
||||
if (isset($aClassType[$sClassType]) && isset($aClassType[$sClassType]['simplelabel'])) {
|
||||
$sAddressType = $aClassType[$aClassType]['simplelabel'];
|
||||
} else {
|
||||
$sClassType = $aPlace['class'].':'.$aPlace['type'];
|
||||
if (isset($aClassType[$sClassType]) && isset($aClassType[$sClassType]['simplelabel']))
|
||||
$sAddressType = $aClassType[$sClassType]['simplelabel'];
|
||||
else $sAddressType = $aPlace['class'];
|
||||
}
|
||||
|
||||
$aPlace['addresstype'] = $sAddressType;
|
||||
|
||||
return $aPlace;
|
||||
}
|
||||
|
||||
public function getAddressDetails($iPlaceID, $bAll = false, $housenumber = -1)
|
||||
{
|
||||
$sLanguagePrefArraySQL = "ARRAY[".join(',', array_map("getDBQuoted", $this->aLangPrefOrder))."]";
|
||||
|
||||
$sSQL = "select *,get_name_by_language(name,$sLanguagePrefArraySQL) as localname from get_addressdata(".$iPlaceID.",".$housenumber.")";
|
||||
if (!$bAll) $sSQL .= " WHERE isaddress OR type = 'country_code'";
|
||||
$sSQL .= " order by rank_address desc,isaddress desc";
|
||||
|
||||
return chksql($this->oDB->getAll($sSQL));
|
||||
}
|
||||
|
||||
public function getAddressNames($iPlaceID, $housenumber = -1)
|
||||
{
|
||||
$aAddressLines = $this->getAddressDetails($iPlaceID, false, $housenumber);
|
||||
|
||||
$aAddress = array();
|
||||
$aFallback = array();
|
||||
$aClassType = getClassTypes();
|
||||
foreach ($aAddressLines as $aLine) {
|
||||
$bFallback = false;
|
||||
$aTypeLabel = false;
|
||||
if (isset($aClassType[$aLine['class'].':'.$aLine['type'].':'.$aLine['admin_level']])) {
|
||||
$aTypeLabel = $aClassType[$aLine['class'].':'.$aLine['type'].':'.$aLine['admin_level']];
|
||||
} elseif (isset($aClassType[$aLine['class'].':'.$aLine['type']])) {
|
||||
$aTypeLabel = $aClassType[$aLine['class'].':'.$aLine['type']];
|
||||
} elseif (isset($aClassType['boundary:administrative:'.((int)($aLine['rank_address']/2))])) {
|
||||
$aTypeLabel = $aClassType['boundary:administrative:'.((int)($aLine['rank_address']/2))];
|
||||
$bFallback = true;
|
||||
} else {
|
||||
$aTypeLabel = array('simplelabel' => 'address'.$aLine['rank_address']);
|
||||
$bFallback = true;
|
||||
$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 ($aTypeLabel && ((isset($aLine['localname']) && $aLine['localname']) || (isset($aLine['housenumber']) && $aLine['housenumber']))) {
|
||||
$sTypeLabel = strtolower(isset($aTypeLabel['simplelabel'])?$aTypeLabel['simplelabel']:$aTypeLabel['label']);
|
||||
$sTypeLabel = str_replace(' ', '_', $sTypeLabel);
|
||||
if (!isset($aAddress[$sTypeLabel]) || (isset($aFallback[$sTypeLabel]) && $aFallback[$sTypeLabel]) || $aLine['class'] == 'place') {
|
||||
$aAddress[$sTypeLabel] = $aLine['localname']?$aLine['localname']:$aLine['housenumber'];
|
||||
if ($this->sAddressRankListSql) {
|
||||
$sSQL .= ' OR placex.rank_address in '.$this->sAddressRankListSql;
|
||||
}
|
||||
$sSQL .= ' ) ';
|
||||
if ($this->sAllowedTypesSQLList) {
|
||||
$sSQL .= 'AND placex.class in '.$this->sAllowedTypesSQLList;
|
||||
}
|
||||
$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 .= ' ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) 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 ST_LineInterpolatePoint(linegeo, 0.5) ';
|
||||
$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 (CONST_Use_Aux_Location_data) {
|
||||
$sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_AUX);
|
||||
if ($sPlaceIDs) {
|
||||
$sHousenumbers = Result::sqlHouseNumberTable($aResults, Result::TABLE_AUX);
|
||||
$sSQL = ' SELECT ';
|
||||
$sSQL .= " 'L' AS osm_type, ";
|
||||
$sSQL .= ' 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,';
|
||||
$sSQL .= " 'us' AS country_code, ";
|
||||
$sSQL .= $this->langAddressSql('-1');
|
||||
$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.10 AS importance, ';
|
||||
$sSQL .= $this->addressImportanceSql(
|
||||
'centroid',
|
||||
'location_property_aux.parent_place_id'
|
||||
);
|
||||
$sSQL .= ' null::text AS extra_place ';
|
||||
$sSQL .= ' FROM location_property_aux ';
|
||||
$sSQL .= " WHERE place_id in ($sPlaceIDs) ";
|
||||
|
||||
$aSubSelects[] = $sSQL;
|
||||
}
|
||||
$aFallback[$sTypeLabel] = $bFallback;
|
||||
}
|
||||
}
|
||||
return $aAddress;
|
||||
|
||||
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']);
|
||||
} else {
|
||||
$aPlace['sExtraTags'] = (object) array();
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->bNameDetails) {
|
||||
if ($aPlace['names']) {
|
||||
$aPlace['sNameDetails'] = json_decode($aPlace['names']);
|
||||
} else {
|
||||
$aPlace['sNameDetails'] = (object) array();
|
||||
}
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* returns an array which will contain the keys
|
||||
* aBoundingBox
|
||||
* and may also contain one or more of the keys
|
||||
@@ -253,7 +480,7 @@ class PlaceLookup
|
||||
*/
|
||||
|
||||
|
||||
public function getOutlines($iPlaceID, $fLon = null, $fLat = null, $fRadius = null)
|
||||
public function getOutlines($iPlaceID, $fLon = null, $fLat = null, $fRadius = null, $fLonReverse = null, $fLatReverse = null)
|
||||
{
|
||||
|
||||
$aOutlineResult = array();
|
||||
@@ -261,24 +488,36 @@ class PlaceLookup
|
||||
|
||||
if (CONST_Search_AreaPolygons) {
|
||||
// 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 || $this->bIncludePolygonAsPoints) $sSQL .= ",ST_AsText(geometry) as astext";
|
||||
$sFrom = " from placex where place_id = ".$iPlaceID;
|
||||
$sSQL = 'select place_id,0 as numfeatures,st_area(geometry) as area,';
|
||||
if ($fLonReverse != null && $fLatReverse != null) {
|
||||
$sSQL .= ' ST_Y(closest_point) as centrelat,';
|
||||
$sSQL .= ' ST_X(closest_point) as centrelon,';
|
||||
} else {
|
||||
$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';
|
||||
if ($fLonReverse != null && $fLatReverse != null) {
|
||||
$sFrom = ' from (SELECT * , CASE WHEN (class = \'highway\') AND (ST_GeometryType(geometry) = \'ST_LineString\') THEN ';
|
||||
$sFrom .=' ST_ClosestPoint(geometry, ST_SetSRID(ST_Point('.$fLatReverse.','.$fLonReverse.'),4326))';
|
||||
$sFrom .=' ELSE centroid END AS closest_point';
|
||||
$sFrom .= ' from placex where place_id = '.$iPlaceID.') as plx';
|
||||
} else {
|
||||
$sFrom = ' from placex where place_id = '.$iPlaceID;
|
||||
}
|
||||
if ($this->fPolygonSimplificationThreshold > 0) {
|
||||
$sSQL .= " from (select place_id,centroid,ST_SimplifyPreserveTopology(geometry,".$this->fPolygonSimplificationThreshold.") as geometry".$sFrom.") as plx";
|
||||
$sSQL .= ' from (select place_id,centroid,ST_SimplifyPreserveTopology(geometry,'.$this->fPolygonSimplificationThreshold.') as geometry'.$sFrom.') as plx';
|
||||
} else {
|
||||
$sSQL .= $sFrom;
|
||||
}
|
||||
|
||||
$aPointPolygon = chksql($this->oDB->getRow($sSQL), "Could not get outline");
|
||||
$aPointPolygon = $this->oDB->getRow($sSQL, null, 'Could not get outline');
|
||||
|
||||
if ($aPointPolygon['place_id']) {
|
||||
if ($aPointPolygon && $aPointPolygon['place_id']) {
|
||||
if ($aPointPolygon['centrelon'] !== null && $aPointPolygon['centrelat'] !== null) {
|
||||
$aOutlineResult['lat'] = $aPointPolygon['centrelat'];
|
||||
$aOutlineResult['lon'] = $aPointPolygon['centrelon'];
|
||||
@@ -288,8 +527,6 @@ class PlaceLookup
|
||||
if ($this->bIncludePolygonAsKML) $aOutlineResult['askml'] = $aPointPolygon['askml'];
|
||||
if ($this->bIncludePolygonAsSVG) $aOutlineResult['assvg'] = $aPointPolygon['assvg'];
|
||||
if ($this->bIncludePolygonAsText) $aOutlineResult['astext'] = $aPointPolygon['astext'];
|
||||
if ($this->bIncludePolygonAsPoints) $aOutlineResult['aPolyPoints'] = geometryText2Points($aPointPolygon['astext'], $fRadius);
|
||||
|
||||
|
||||
if (abs($aPointPolygon['minlat'] - $aPointPolygon['maxlat']) < 0.0000001) {
|
||||
$aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius;
|
||||
@@ -312,17 +549,12 @@ class PlaceLookup
|
||||
|
||||
// as a fallback we generate a bounding box without knowing the size of the geometry
|
||||
if ((!isset($aOutlineResult['aBoundingBox'])) && isset($fLon)) {
|
||||
//
|
||||
if ($this->bIncludePolygonAsPoints) {
|
||||
$sGeometryText = 'POINT('.$fLon.','.$fLat.')';
|
||||
$aOutlineResult['aPolyPoints'] = geometryText2Points($sGeometryText, $fRadius);
|
||||
}
|
||||
|
||||
$aBounds = array();
|
||||
$aBounds['minlat'] = $fLat - $fRadius;
|
||||
$aBounds['maxlat'] = $fLat + $fRadius;
|
||||
$aBounds['minlon'] = $fLon - $fRadius;
|
||||
$aBounds['maxlon'] = $fLon + $fRadius;
|
||||
$aBounds = array(
|
||||
'minlat' => $fLat - $fRadius,
|
||||
'maxlat' => $fLat + $fRadius,
|
||||
'minlon' => $fLon - $fRadius,
|
||||
'maxlon' => $fLon + $fRadius
|
||||
);
|
||||
|
||||
$aOutlineResult['aBoundingBox'] = array(
|
||||
(string)$aBounds['minlat'],
|
||||
|
||||
99
lib/Result.php
Normal file
99
lib/Result.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
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_AUX = 3;
|
||||
const TABLE_TIGER = 4;
|
||||
|
||||
/// 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;
|
||||
|
||||
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 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 = array_merge($aTail, $aHead);
|
||||
$aHead = array($oRes->iId => $oRes);
|
||||
$iMinRank = $oRes->iResultRank;
|
||||
} elseif ($oRes->iResultRank == $iMinRank) {
|
||||
$aHead[$oRes->iId] = $oRes;
|
||||
} else {
|
||||
$aTail[$oRes->iId] = $oRes;
|
||||
}
|
||||
}
|
||||
|
||||
return array('head' => $aHead, 'tail' => $aTail);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Nominatim;
|
||||
|
||||
require_once(CONST_BasePath.'/lib/Result.php');
|
||||
|
||||
class ReverseGeocode
|
||||
{
|
||||
protected $oDB;
|
||||
@@ -34,8 +36,8 @@ class ReverseGeocode
|
||||
13 => 18,
|
||||
14 => 22, // Suburb
|
||||
15 => 22,
|
||||
16 => 26, // Street, TODO: major street?
|
||||
17 => 26,
|
||||
16 => 26, // major street
|
||||
17 => 27, // minor street
|
||||
18 => 30, // or >, Building
|
||||
19 => 30, // or >, Building
|
||||
);
|
||||
@@ -52,172 +54,317 @@ class ReverseGeocode
|
||||
*/
|
||||
protected function lookupInterpolation($sPointSQL, $fSearchDiam)
|
||||
{
|
||||
Debug::newFunction('lookupInterpolation');
|
||||
$sSQL = 'SELECT place_id, parent_place_id, 30 as rank_search,';
|
||||
$sSQL .= ' ST_LineLocatePoint(linegeo,'.$sPointSQL.') as fraction';
|
||||
$sSQL .= ' , ST_Distance(linegeo,'.$sPointSQL.') as distance';
|
||||
$sSQL .= ' ST_LineLocatePoint(linegeo,'.$sPointSQL.') as fraction,';
|
||||
$sSQL .= ' startnumber, endnumber, interpolationtype,';
|
||||
$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 .= ' ORDER BY ST_distance('.$sPointSQL.', linegeo) ASC limit 1';
|
||||
$sSQL .= ' ORDER BY distance ASC limit 1';
|
||||
Debug::printSQL($sSQL);
|
||||
|
||||
return chksql(
|
||||
$this->oDB->getRow($sSQL),
|
||||
"Could not determine closest housenumber on an osm interpolation line."
|
||||
return $this->oDB->getRow(
|
||||
$sSQL,
|
||||
null,
|
||||
'Could not determine closest housenumber on an osm interpolation line.'
|
||||
);
|
||||
}
|
||||
|
||||
/* lookup()
|
||||
* returns { place_id =>, type => '(osm|tiger)' }
|
||||
* fails if no place was found
|
||||
protected function lookupLargeArea($sPointSQL, $iMaxRank)
|
||||
{
|
||||
$oResult = 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);
|
||||
}
|
||||
|
||||
protected function lookupInCountry($sPointSQL, $iMaxRank)
|
||||
{
|
||||
Debug::newFunction('lookupInCountry');
|
||||
// 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.'
|
||||
);
|
||||
Debug::printVar('Country code', $sCountryCode);
|
||||
|
||||
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_search between 5 and ' .min(25, $iMaxRank);
|
||||
$sSQL .= ' AND class = \'place\' AND type != \'postcode\'';
|
||||
$sSQL .= ' AND name IS NOT NULL ';
|
||||
$sSQL .= ' and indexed_status = 0 and linked_place_id is null';
|
||||
$sSQL .= ' AND ST_DWithin('.$sPointSQL.', geometry, 1.8)) p ';
|
||||
$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\')';
|
||||
$sSQL .= ' AND rank_address Between 5 AND ' .$iMaxRank;
|
||||
$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_address DESC LIMIT 50 ) as a';
|
||||
$sSQL .= ' WHERE ST_CONTAINS(geometry, '.$sPointSQL.' )';
|
||||
$sSQL .= ' ORDER BY rank_address 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 ...
|
||||
$iParentPlaceID = $aPoly['parent_place_id'];
|
||||
$iRankAddress = $aPoly['rank_address'];
|
||||
$iRankSearch = $aPoly['rank_search'];
|
||||
$iPlaceID = $aPoly['place_id'];
|
||||
|
||||
if ($iRankAddress != $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\'';
|
||||
// using rank_search because of a better differentiation
|
||||
// for place nodes at rank_address 16
|
||||
$sSQL .= ' AND rank_search > '.$iRankSearch;
|
||||
$sSQL .= ' AND rank_search <= '.$iMaxRank;
|
||||
$sSQL .= ' AND rank_address > 0';
|
||||
$sSQL .= ' AND class = \'place\'';
|
||||
$sSQL .= ' AND type != \'postcode\'';
|
||||
$sSQL .= ' AND name IS NOT NULL ';
|
||||
$sSQL .= ' AND indexed_status = 0 AND linked_place_id is null';
|
||||
$sSQL .= ' AND ST_DWithin('.$sPointSQL.', geometry, reverse_place_diameter('.$iRankSearch.'::smallint))';
|
||||
$sSQL .= ' ORDER BY distance ASC,';
|
||||
$sSQL .= ' rank_address DESC';
|
||||
$sSQL .= ' limit 500) 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 distance ASC, rank_search DESC';
|
||||
$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)
|
||||
{
|
||||
$sPointSQL = 'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)';
|
||||
return $this->lookupPoint(
|
||||
'ST_SetSRID(ST_Point('.$fLon.','.$fLat.'),4326)',
|
||||
$bDoInterpolation
|
||||
);
|
||||
}
|
||||
|
||||
public function lookupPoint($sPointSQL, $bDoInterpolation = true)
|
||||
{
|
||||
Debug::newFunction('lookupPoint');
|
||||
// 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 choosen.
|
||||
$iMaxRank = $this->iMaxRank;
|
||||
|
||||
// Find the nearest point
|
||||
$fSearchDiam = 0.0004;
|
||||
$iPlaceID = null;
|
||||
$fMaxAreaDistance = 1;
|
||||
$bIsInUnitedStates = false;
|
||||
$bPlaceIsTiger = false;
|
||||
$bPlaceIsLine = false;
|
||||
while (!$iPlaceID && $fSearchDiam < $fMaxAreaDistance) {
|
||||
$fSearchDiam = $fSearchDiam * 2;
|
||||
$fSearchDiam = 0.006;
|
||||
$oResult = null;
|
||||
$aPlace = null;
|
||||
|
||||
// If we have to expand the search area by a large amount then we need a larger feature
|
||||
// then there is a limit to how small the feature should be
|
||||
if ($fSearchDiam > 2 && $iMaxRank > 4) $iMaxRank = 4;
|
||||
if ($fSearchDiam > 1 && $iMaxRank > 9) $iMaxRank = 8;
|
||||
if ($fSearchDiam > 0.8 && $iMaxRank > 10) $iMaxRank = 10;
|
||||
if ($fSearchDiam > 0.6 && $iMaxRank > 12) $iMaxRank = 12;
|
||||
if ($fSearchDiam > 0.2 && $iMaxRank > 17) $iMaxRank = 17;
|
||||
if ($fSearchDiam > 0.1 && $iMaxRank > 18) $iMaxRank = 18;
|
||||
if ($fSearchDiam > 0.008 && $iMaxRank > 22) $iMaxRank = 22;
|
||||
if ($fSearchDiam > 0.001 && $iMaxRank > 26) {
|
||||
// try with interpolations before continuing
|
||||
if ($bDoInterpolation) {
|
||||
// no house found, try with interpolations
|
||||
$aPlaceLine = $this->lookupInterpolation($sPointSQL, $fSearchDiam/2);
|
||||
|
||||
if ($aPlaceLine) {
|
||||
// interpolation is closer to point than placex house
|
||||
$bPlaceIsLine = true;
|
||||
$aPlace = $aPlaceLine;
|
||||
$iPlaceID = $aPlaceLine['place_id'];
|
||||
$iParentPlaceID = $aPlaceLine['parent_place_id']; // the street
|
||||
$fFraction = $aPlaceLine['fraction'];
|
||||
$iMaxRank = 30;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
// no interpolation found, continue search
|
||||
$iMaxRank = 26;
|
||||
}
|
||||
|
||||
$sSQL = 'select place_id,parent_place_id,rank_search,country_code';
|
||||
$sSQL .= ' FROM placex';
|
||||
$sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, '.$fSearchDiam.')';
|
||||
$sSQL .= ' and rank_search != 28 and rank_search >= '.$iMaxRank;
|
||||
$sSQL .= ' and (name is not null or housenumber is not null)';
|
||||
$sSQL .= ' and class not in (\'waterway\',\'railway\',\'tunnel\',\'bridge\',\'man_made\')';
|
||||
$sSQL .= ' and indexed_status = 0 ';
|
||||
// for POI or street level
|
||||
if ($iMaxRank >= 26) {
|
||||
$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 '.$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 ST_distance('.$sPointSQL.', geometry) ASC limit 1';
|
||||
if (CONST_Debug) var_dump($sSQL);
|
||||
$aPlace = chksql(
|
||||
$this->oDB->getRow($sSQL),
|
||||
"Could not determine closest place."
|
||||
);
|
||||
$iPlaceID = $aPlace['place_id'];
|
||||
$iParentPlaceID = $aPlace['parent_place_id'];
|
||||
$bIsInUnitedStates = ($aPlace['country_code'] == 'us');
|
||||
}
|
||||
$sSQL .= ' ORDER BY distance ASC limit 1';
|
||||
Debug::printSQL($sSQL);
|
||||
|
||||
// If a house was found make sure there isn't an interpolation line
|
||||
// that is closer
|
||||
if ($bDoInterpolation && !$bPlaceIsLine && $aPlace && $aPlace['rank_search'] == 30) {
|
||||
// get the distance of the house to the search point
|
||||
$sSQL = 'SELECT ST_distance('.$sPointSQL.', house.geometry)';
|
||||
$sSQL .= ' FROM placex as house WHERE house.place_id='.$iPlaceID;
|
||||
$aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine closest place.');
|
||||
|
||||
$fDistancePlacex = chksql(
|
||||
$this->oDB->getOne($sSQL),
|
||||
"Could not determine distance between searched point and placex house."
|
||||
);
|
||||
|
||||
// look for an interpolation that is closer
|
||||
$aPlaceLine = $this->lookupInterpolation($sPointSQL, $fDistancePlacex);
|
||||
|
||||
if ($aPlaceLine && (float) $aPlaceLine['distance'] < (float) $fDistancePlacex) {
|
||||
// interpolation is closer to point than placex house
|
||||
$bPlaceIsLine = true;
|
||||
$aPlace = $aPlaceLine;
|
||||
$iPlaceID = $aPlaceLine['place_id'];
|
||||
$iParentPlaceID = $aPlaceLine['parent_place_id']; // the street
|
||||
$fFraction = $aPlaceLine['fraction'];
|
||||
Debug::printVar('POI/street level result', $aPlace);
|
||||
if ($aPlace) {
|
||||
$iPlaceID = $aPlace['place_id'];
|
||||
$oResult = new Result($iPlaceID);
|
||||
$iRankAddress = $aPlace['rank_address'];
|
||||
$iParentPlaceID = $aPlace['parent_place_id'];
|
||||
}
|
||||
}
|
||||
|
||||
// Only street found? If it's in the US we can check TIGER data for nearest housenumber
|
||||
if (CONST_Use_US_Tiger_Data && $bDoInterpolation && $bIsInUnitedStates && $this->iMaxRank >= 28 && $iPlaceID && ($aPlace['rank_search'] == 26 || $aPlace['rank_search'] == 27 )) {
|
||||
$fSearchDiam = 0.001;
|
||||
$sSQL = 'SELECT place_id,parent_place_id,30 as rank_search, ST_LineLocatePoint(linegeo,'.$sPointSQL.') as fraction';
|
||||
//if (CONST_Debug) { $sSQL .= ', housenumber, ST_distance('.$sPointSQL.', centroid) as distance, st_y(centroid) as lat, st_x(centroid) as lon'; }
|
||||
$sSQL .= ' FROM location_property_tiger WHERE parent_place_id = '.$iPlaceID;
|
||||
$sSQL .= ' AND ST_DWithin('.$sPointSQL.', linegeo, '.$fSearchDiam.')'; //no centroid anymore in Tiger data, now we have lines
|
||||
$sSQL .= ' ORDER BY ST_distance('.$sPointSQL.', linegeo) ASC limit 1';
|
||||
if ($bDoInterpolation && $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 precendence
|
||||
// even when the street is closer.
|
||||
$fDistance = $iRankAddress < 28 ? 0.001 : $aPlace['distance'];
|
||||
}
|
||||
|
||||
if (CONST_Debug) {
|
||||
$sSQL = preg_replace('/limit 1/', 'limit 100', $sSQL);
|
||||
var_dump($sSQL);
|
||||
$aHouse = $this->lookupInterpolation($sPointSQL, $fDistance);
|
||||
Debug::printVar('Interpolation result', $aPlace);
|
||||
|
||||
$aAllHouses = chksql($this->oDB->getAll($sSQL));
|
||||
foreach ($aAllHouses as $i) {
|
||||
echo $i['housenumber'] . ' | ' . $i['distance'] * 1000 . ' | ' . $i['lat'] . ' | ' . $i['lon']. ' | '. "<br>\n";
|
||||
if ($aHouse) {
|
||||
$oResult = new Result($aHouse['place_id'], Result::TABLE_OSMLINE);
|
||||
$oResult->iHouseNumber = closestHouseNumber($aHouse);
|
||||
$aPlace = $aHouse;
|
||||
$iRankAddress = 30;
|
||||
}
|
||||
}
|
||||
|
||||
$aPlaceTiger = chksql(
|
||||
$this->oDB->getRow($sSQL),
|
||||
"Could not determine closest Tiger place."
|
||||
);
|
||||
if ($aPlaceTiger) {
|
||||
if (CONST_Debug) var_dump('found Tiger housenumber', $aPlaceTiger);
|
||||
$bPlaceIsTiger = true;
|
||||
$aPlace = $aPlaceTiger;
|
||||
$iPlaceID = $aPlaceTiger['place_id'];
|
||||
$iParentPlaceID = $aPlaceTiger['parent_place_id']; // the street
|
||||
$fFraction = $aPlaceTiger['fraction'];
|
||||
$iMaxRank = 30;
|
||||
if ($aPlace) {
|
||||
// if street and maxrank > streetlevel
|
||||
if ($iRankAddress <= 27 && $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) {
|
||||
$oResult = new Result($aStreet['place_id']);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 .= 'ST_LineLocatePoint(linegeo,'.$sPointSQL.') as fraction,';
|
||||
$sSQL .= 'ST_distance('.$sPointSQL.', linegeo) as distance,';
|
||||
$sSQL .= 'startnumber,endnumber,interpolationtype';
|
||||
$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) {
|
||||
$oResult = new Result($aPlaceTiger['place_id'], Result::TABLE_TIGER);
|
||||
$oResult->iHouseNumber = closestHouseNumber($aPlaceTiger);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if no POI or street is found ...
|
||||
$oResult = $this->lookupLargeArea($sPointSQL, 25);
|
||||
}
|
||||
} else {
|
||||
// lower than street level ($iMaxRank < 26 )
|
||||
$oResult = $this->lookupLargeArea($sPointSQL, $iMaxRank);
|
||||
}
|
||||
|
||||
// The point we found might be too small - use the address to find what it is a child of
|
||||
if ($iPlaceID && $iMaxRank < 28) {
|
||||
if (($aPlace['rank_search'] > 28 || $bPlaceIsTiger || $bPlaceIsLine) && $iParentPlaceID) {
|
||||
$iPlaceID = $iParentPlaceID;
|
||||
$bPlaceIsLine = false;
|
||||
$bPlaceIsTiger = false;
|
||||
}
|
||||
$sSQL = 'select address_place_id';
|
||||
$sSQL .= ' FROM place_addressline';
|
||||
$sSQL .= " WHERE place_id = $iPlaceID";
|
||||
$sSQL .= " ORDER BY abs(cached_rank_address - $iMaxRank) asc,cached_rank_address desc,isaddress desc,distance desc";
|
||||
$sSQL .= ' LIMIT 1';
|
||||
$iPlaceID = chksql($this->oDB->getOne($sSQL), "Could not get parent for place.");
|
||||
if (!$iPlaceID) {
|
||||
$iPlaceID = $aPlace['place_id'];
|
||||
}
|
||||
}
|
||||
return array(
|
||||
'place_id' => $iPlaceID,
|
||||
'type' => $bPlaceIsTiger ? 'tiger' : ($bPlaceIsLine ? 'interpolation' : 'osm'),
|
||||
'fraction' => ($bPlaceIsTiger || $bPlaceIsLine) ? $fFraction : -1
|
||||
);
|
||||
Debug::printVar('Final result', $oResult);
|
||||
return $oResult;
|
||||
}
|
||||
}
|
||||
|
||||
295
lib/SearchContext.php
Normal file
295
lib/SearchContext.php
Normal file
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim;
|
||||
|
||||
require_once(CONST_BasePath.'/lib/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 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)).')';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 '';
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
1060
lib/SearchDescription.php
Normal file
1060
lib/SearchDescription.php
Normal file
File diff suppressed because it is too large
Load Diff
80
lib/Shell.php
Normal file
80
lib/Shell.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim;
|
||||
|
||||
class Shell
|
||||
{
|
||||
public function __construct($sBaseCmd, ...$aParams)
|
||||
{
|
||||
if (!$sBaseCmd) {
|
||||
throw new Exception('Command missing in new() call');
|
||||
}
|
||||
$this->baseCmd = $sBaseCmd;
|
||||
$this->aParams = array();
|
||||
$this->aEnv = null; // null = use the same environment as the current PHP process
|
||||
|
||||
$this->stdoutString = null;
|
||||
|
||||
foreach ($aParams as $sParam) {
|
||||
$this->addParams($sParam);
|
||||
}
|
||||
}
|
||||
|
||||
public function addParams(...$aParams)
|
||||
{
|
||||
foreach ($aParams as $sParam) {
|
||||
if (isset($sParam) && $sParam !== null && $sParam !== '') {
|
||||
array_push($this->aParams, $sParam);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addEnvPair($sKey, $sVal)
|
||||
{
|
||||
if (isset($sKey) && $sKey && isset($sVal)) {
|
||||
if (!isset($this->aEnv)) $this->aEnv = $_ENV;
|
||||
$this->aEnv = array_merge($this->aEnv, array($sKey => $sVal), $_ENV);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function escapedCmd()
|
||||
{
|
||||
$aEscaped = array_map(function ($sParam) {
|
||||
return $this->escapeParam($sParam);
|
||||
}, array_merge(array($this->baseCmd), $this->aParams));
|
||||
|
||||
return join(' ', $aEscaped);
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$sCmd = $this->escapedCmd();
|
||||
// $aEnv does not need escaping, proc_open seems to handle it fine
|
||||
|
||||
$aFDs = array(
|
||||
0 => array('pipe', 'r'),
|
||||
1 => STDOUT,
|
||||
2 => STDERR
|
||||
);
|
||||
$aPipes = null;
|
||||
$hProc = @proc_open($sCmd, $aFDs, $aPipes, null, $this->aEnv);
|
||||
if (!is_resource($hProc)) {
|
||||
throw new \Exception('Unable to run command: ' . $sCmd);
|
||||
}
|
||||
|
||||
fclose($aPipes[0]); // no stdin
|
||||
|
||||
$iStat = proc_close($hProc);
|
||||
return $iStat;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function escapeParam($sParam)
|
||||
{
|
||||
if (preg_match('/^-*\w+$/', $sParam)) return $sParam;
|
||||
return escapeshellarg($sParam);
|
||||
}
|
||||
}
|
||||
44
lib/SpecialSearchOperator.php
Normal file
44
lib/SpecialSearchOperator.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
59
lib/Status.php
Normal file
59
lib/Status.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
$sStandardWord = $this->oDB->getOne("SELECT make_standard_name('a')");
|
||||
if ($sStandardWord === false) {
|
||||
throw new Exception('Module failed', 701);
|
||||
}
|
||||
|
||||
if ($sStandardWord != 'a') {
|
||||
throw new Exception('Module call failed', 702);
|
||||
}
|
||||
|
||||
$sSQL = 'SELECT word_id, word_token, word, class, type, country_code, ';
|
||||
$sSQL .= "operator, search_name_count FROM word WHERE word_token IN (' a')";
|
||||
$iWordID = $this->oDB->getOne($sSQL);
|
||||
if ($iWordID === false) {
|
||||
throw new Exception('Query failed', 703);
|
||||
}
|
||||
if (!$iWordID) {
|
||||
throw new Exception('No value', 704);
|
||||
}
|
||||
}
|
||||
|
||||
public function dataDate()
|
||||
{
|
||||
$sSQL = 'SELECT EXTRACT(EPOCH FROM lastimportdate) FROM import_status LIMIT 1';
|
||||
$iDataDateEpoch = $this->oDB->getOne($sSQL);
|
||||
|
||||
if ($iDataDateEpoch === false) {
|
||||
throw Exception('Data date query failed '.$iDataDateEpoch->getMessage(), 705);
|
||||
}
|
||||
|
||||
return $iDataDateEpoch;
|
||||
}
|
||||
}
|
||||
29
lib/TokenCountry.php
Normal file
29
lib/TokenCountry.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim\Token;
|
||||
|
||||
/**
|
||||
* A country token.
|
||||
*/
|
||||
class Country
|
||||
{
|
||||
/// Database word id, if available.
|
||||
public $iId;
|
||||
/// Two-letter country code (lower-cased).
|
||||
public $sCountryCode;
|
||||
|
||||
public function __construct($iId, $sCountryCode)
|
||||
{
|
||||
$this->iId = $iId;
|
||||
$this->sCountryCode = $sCountryCode;
|
||||
}
|
||||
|
||||
public function debugInfo()
|
||||
{
|
||||
return array(
|
||||
'ID' => $this->iId,
|
||||
'Type' => 'country',
|
||||
'Info' => $this->sCountryCode
|
||||
);
|
||||
}
|
||||
}
|
||||
29
lib/TokenHousenumber.php
Normal file
29
lib/TokenHousenumber.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim\Token;
|
||||
|
||||
/**
|
||||
* A house number token.
|
||||
*/
|
||||
class HouseNumber
|
||||
{
|
||||
/// Database word id, if available.
|
||||
public $iId;
|
||||
/// Normalized house number.
|
||||
public $sToken;
|
||||
|
||||
public function __construct($iId, $sToken)
|
||||
{
|
||||
$this->iId = $iId;
|
||||
$this->sToken = $sToken;
|
||||
}
|
||||
|
||||
public function debugInfo()
|
||||
{
|
||||
return array(
|
||||
'ID' => $this->iId,
|
||||
'Type' => 'house number',
|
||||
'Info' => array('nr' => $this->sToken)
|
||||
);
|
||||
}
|
||||
}
|
||||
216
lib/TokenList.php
Normal file
216
lib/TokenList.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim;
|
||||
|
||||
require_once(CONST_BasePath.'/lib/TokenCountry.php');
|
||||
require_once(CONST_BasePath.'/lib/TokenHousenumber.php');
|
||||
require_once(CONST_BasePath.'/lib/TokenPostcode.php');
|
||||
require_once(CONST_BasePath.'/lib/TokenSpecialTerm.php');
|
||||
require_once(CONST_BasePath.'/lib/TokenWord.php');
|
||||
require_once(CONST_BasePath.'/lib/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.
|
||||
*
|
||||
* There are two different kinds of token words: full words and partial terms.
|
||||
*
|
||||
* Full words start with a space. They represent a complete name of a place.
|
||||
* All special tokens are normally full words.
|
||||
*
|
||||
* Partial terms have no space at the beginning. They may represent a part of
|
||||
* a name of a place (e.g. in the name 'World Trade Center' a partial term
|
||||
* would be 'Trade' or 'Trade Center'). They are only used in TokenWord.
|
||||
*/
|
||||
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]) || 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') && !$oToken->bPartial) {
|
||||
$ids[$oToken->iId] = $oToken->iId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add token information from the word table in the database.
|
||||
*
|
||||
* @param object $oDB Nominatim::DB instance.
|
||||
* @param string[] $aTokens List of tokens to look up in the database.
|
||||
* @param string[] $aCountryCodes List of country restrictions.
|
||||
* @param string $sNormQuery Normalized query string.
|
||||
* @param object $oNormalizer Normalizer function to use on tokens.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addTokensFromDB(&$oDB, &$aTokens, &$aCountryCodes, $sNormQuery, $oNormalizer)
|
||||
{
|
||||
// Check which tokens we have, get the ID numbers
|
||||
$sSQL = 'SELECT word_id, word_token, word, class, type, country_code,';
|
||||
$sSQL .= ' operator, coalesce(search_name_count, 0) as count';
|
||||
$sSQL .= ' FROM word WHERE word_token in (';
|
||||
$sSQL .= join(',', $oDB->getDBQuotedList($aTokens)).')';
|
||||
|
||||
Debug::printSQL($sSQL);
|
||||
|
||||
$aDBWords = $oDB->getAll($sSQL, null, 'Could not get word tokens.');
|
||||
|
||||
foreach ($aDBWords as $aWord) {
|
||||
$oToken = null;
|
||||
$iId = (int) $aWord['word_id'];
|
||||
|
||||
if ($aWord['class']) {
|
||||
// Special terms need to appear in their normalized form.
|
||||
if ($aWord['word']) {
|
||||
$sNormWord = $aWord['word'];
|
||||
if ($oNormalizer != null) {
|
||||
$sNormWord = $oNormalizer->transliterate($aWord['word']);
|
||||
}
|
||||
if (strpos($sNormQuery, $sNormWord) === false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($aWord['class'] == 'place' && $aWord['type'] == 'house') {
|
||||
$oToken = new Token\HouseNumber($iId, trim($aWord['word_token']));
|
||||
} elseif ($aWord['class'] == 'place' && $aWord['type'] == 'postcode') {
|
||||
if ($aWord['word']
|
||||
&& pg_escape_string($aWord['word']) == $aWord['word']
|
||||
) {
|
||||
$oToken = new Token\Postcode(
|
||||
$iId,
|
||||
$aWord['word'],
|
||||
$aWord['country_code']
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// near and in operator the same at the moment
|
||||
$oToken = new Token\SpecialTerm(
|
||||
$iId,
|
||||
$aWord['class'],
|
||||
$aWord['type'],
|
||||
$aWord['operator'] ? Operator::NEAR : Operator::NONE
|
||||
);
|
||||
}
|
||||
} elseif ($aWord['country_code']) {
|
||||
// Filter country tokens that do not match restricted countries.
|
||||
if (!$aCountryCodes
|
||||
|| in_array($aWord['country_code'], $aCountryCodes)
|
||||
) {
|
||||
$oToken = new Token\Country($iId, $aWord['country_code']);
|
||||
}
|
||||
} else {
|
||||
$oToken = new Token\Word(
|
||||
$iId,
|
||||
$aWord['word_token'][0] != ' ',
|
||||
(int) $aWord['count'],
|
||||
substr_count($aWord['word_token'], ' ')
|
||||
);
|
||||
}
|
||||
|
||||
if ($oToken) {
|
||||
$this->addToken($aWord['word_token'], $oToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if ($aToken->iId !== null) {
|
||||
$aWordsIDs[$aToken->iId] =
|
||||
'#'.$sToken.'('.$aToken->iId.')#';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $aWordsIDs;
|
||||
}
|
||||
|
||||
public function debugInfo()
|
||||
{
|
||||
return $this->aTokens;
|
||||
}
|
||||
}
|
||||
32
lib/TokenPostcode.php
Normal file
32
lib/TokenPostcode.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim\Token;
|
||||
|
||||
/**
|
||||
* A postcode token.
|
||||
*/
|
||||
class Postcode
|
||||
{
|
||||
/// Database word id, if available.
|
||||
public $iId;
|
||||
/// Full nomralized postcode (upper cased).
|
||||
public $sPostcode;
|
||||
// Optional country code the postcode belongs to (currently unused).
|
||||
public $sCountryCode;
|
||||
|
||||
public function __construct($iId, $sPostcode, $sCountryCode = '')
|
||||
{
|
||||
$this->iId = $iId;
|
||||
$this->sPostcode = $sPostcode;
|
||||
$this->sCountryCode = empty($sCountryCode) ? '' : $sCountryCode;
|
||||
}
|
||||
|
||||
public function debugInfo()
|
||||
{
|
||||
return array(
|
||||
'ID' => $this->iId,
|
||||
'Type' => 'postcode',
|
||||
'Info' => $this->sPostcode.'('.$this->sCountryCode.')'
|
||||
);
|
||||
}
|
||||
}
|
||||
41
lib/TokenSpecialTerm.php
Normal file
41
lib/TokenSpecialTerm.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim\Token;
|
||||
|
||||
require_once(CONST_BasePath.'/lib/SpecialSearchOperator.php');
|
||||
|
||||
/**
|
||||
* A word token describing a place type.
|
||||
*/
|
||||
class SpecialTerm
|
||||
{
|
||||
/// Database word id, if applicable.
|
||||
public $iId;
|
||||
/// Class (or OSM tag key) of the place to look for.
|
||||
public $sClass;
|
||||
/// Type (or OSM tag value) of the place to look for.
|
||||
public $sType;
|
||||
/// Relationship of the operator to the object (see Operator class).
|
||||
public $iOperator;
|
||||
|
||||
public function __construct($iID, $sClass, $sType, $iOperator)
|
||||
{
|
||||
$this->iId = $iID;
|
||||
$this->sClass = $sClass;
|
||||
$this->sType = $sType;
|
||||
$this->iOperator = $iOperator;
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
38
lib/TokenWord.php
Normal file
38
lib/TokenWord.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim\Token;
|
||||
|
||||
/**
|
||||
* A standard word token.
|
||||
*/
|
||||
class Word
|
||||
{
|
||||
/// Database word id, if applicable.
|
||||
public $iId;
|
||||
/// If true, the word may represent only part of a place name.
|
||||
public $bPartial;
|
||||
/// Number of appearances in the database.
|
||||
public $iSearchNameCount;
|
||||
/// Number of terms in the word.
|
||||
public $iTermCount;
|
||||
|
||||
public function __construct($iId, $bPartial, $iSearchNameCount, $iTermCount)
|
||||
{
|
||||
$this->iId = $iId;
|
||||
$this->bPartial = $bPartial;
|
||||
$this->iSearchNameCount = $iSearchNameCount;
|
||||
$this->iTermCount = $iTermCount;
|
||||
}
|
||||
|
||||
public function debugInfo()
|
||||
{
|
||||
return array(
|
||||
'ID' => $this->iId,
|
||||
'Type' => 'word',
|
||||
'Info' => array(
|
||||
'partial' => $this->bPartial,
|
||||
'count' => $this->iSearchNameCount
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
79
lib/cmd.php
79
lib/cmd.php
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
require_once(CONST_BasePath.'/lib/Shell.php');
|
||||
|
||||
function getCmdOpt($aArg, $aSpec, &$aResult, $bExitOnError = false, $bExitOnUnknown = false)
|
||||
{
|
||||
@@ -16,7 +17,7 @@ function getCmdOpt($aArg, $aSpec, &$aResult, $bExitOnError = false, $bExitOnUnkn
|
||||
|
||||
$aResult = array();
|
||||
$bUnknown = false;
|
||||
$iSize = sizeof($aArg);
|
||||
$iSize = count($aArg);
|
||||
for ($i = 1; $i < $iSize; $i++) {
|
||||
if (isset($aQuick[$aArg[$i]])) {
|
||||
$aLine = $aQuick[$aArg[$i]];
|
||||
@@ -99,7 +100,7 @@ function showUsage($aSpec, $bExit = false, $sError = false)
|
||||
echo 'Try `'.basename($_SERVER['argv'][0]).' --help` for more information.'."\n";
|
||||
exit;
|
||||
}
|
||||
echo "Usage: ".basename($_SERVER['argv'][0])."\n";
|
||||
echo 'Usage: '.basename($_SERVER['argv'][0])."\n";
|
||||
$bFirst = true;
|
||||
foreach ($aSpec as $aLine) {
|
||||
if (is_array($aLine)) {
|
||||
@@ -120,11 +121,77 @@ function showUsage($aSpec, $bExit = false, $sError = false)
|
||||
exit;
|
||||
}
|
||||
|
||||
function chksql($oSql, $sMsg = false)
|
||||
function info($sMsg)
|
||||
{
|
||||
if (PEAR::isError($oSql)) {
|
||||
fail($sMsg || $oSql->getMessage(), $oSql->userinfo);
|
||||
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 runSQLScript($sScript, $bfatal = true, $bVerbose = false, $bIgnoreErrors = false)
|
||||
{
|
||||
// Convert database DSN to psql parameters
|
||||
$aDSNInfo = \Nominatim\DB::parseDSN(CONST_Database_DSN);
|
||||
if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
|
||||
|
||||
$oCmd = new \Nominatim\Shell('psql');
|
||||
$oCmd->addParams('--port', $aDSNInfo['port']);
|
||||
$oCmd->addParams('--dbname', $aDSNInfo['database']);
|
||||
if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
|
||||
$oCmd->addParams('--host', $aDSNInfo['hostspec']);
|
||||
}
|
||||
if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
|
||||
$oCmd->addParams('--username', $aDSNInfo['username']);
|
||||
}
|
||||
if (isset($aDSNInfo['password'])) {
|
||||
$oCmd->addEnvPair('PGPASSWORD', $aDSNInfo['password']);
|
||||
}
|
||||
if (!$bVerbose) {
|
||||
$oCmd->addParams('--quiet');
|
||||
}
|
||||
if ($bfatal && !$bIgnoreErrors) {
|
||||
$oCmd->addParams('-v', 'ON_ERROR_STOP=1');
|
||||
}
|
||||
|
||||
return $oSql;
|
||||
$aDescriptors = array(
|
||||
0 => array('pipe', 'r'),
|
||||
1 => STDOUT,
|
||||
2 => STDERR
|
||||
);
|
||||
$ahPipes = null;
|
||||
$hProcess = @proc_open($oCmd->escapedCmd(), $aDescriptors, $ahPipes, null, $oCmd->aEnv);
|
||||
if (!is_resource($hProcess)) {
|
||||
fail('unable to start pgsql');
|
||||
}
|
||||
|
||||
if (!$bVerbose) {
|
||||
fwrite($ahPipes[0], 'set client_min_messages to WARNING;');
|
||||
}
|
||||
|
||||
while (strlen($sScript)) {
|
||||
$iWritten = fwrite($ahPipes[0], $sScript);
|
||||
if ($iWritten <= 0) break;
|
||||
$sScript = substr($sScript, $iWritten);
|
||||
}
|
||||
fclose($ahPipes[0]);
|
||||
$iReturn = proc_close($hProcess);
|
||||
if ($bfatal && $iReturn > 0) {
|
||||
fail("pgsql returned with error code ($iReturn)");
|
||||
}
|
||||
}
|
||||
|
||||
38
lib/db.php
38
lib/db.php
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once('DB.php');
|
||||
|
||||
|
||||
function &getDB($bNew = false, $bPersistent = false)
|
||||
{
|
||||
// Get the database object
|
||||
$oDB = chksql(
|
||||
DB::connect(CONST_Database_DSN.($bNew?'?new_link=true':''), $bPersistent),
|
||||
"Failed to establish database connection"
|
||||
);
|
||||
$oDB->setFetchMode(DB_FETCHMODE_ASSOC);
|
||||
$oDB->query("SET DateStyle TO 'sql,european'");
|
||||
$oDB->query("SET client_encoding TO 'utf-8'");
|
||||
$iMaxExecution = ini_get('max_execution_time') * 1000;
|
||||
if ($iMaxExecution > 0) $oDB->query("SET statement_timeout TO $iMaxExecution");
|
||||
return $oDB;
|
||||
}
|
||||
|
||||
function getDBQuoted($s)
|
||||
{
|
||||
return "'".pg_escape_string($s)."'";
|
||||
}
|
||||
|
||||
function getPostgresVersion(&$oDB)
|
||||
{
|
||||
$sVersionString = $oDB->getOne('select version()');
|
||||
preg_match('#PostgreSQL ([0-9]+)[.]([0-9]+)[^0-9]#', $sVersionString, $aMatches);
|
||||
return (float) ($aMatches[1].'.'.$aMatches[2]);
|
||||
}
|
||||
|
||||
function getPostgisVersion(&$oDB)
|
||||
{
|
||||
$sVersionString = $oDB->getOne('select postgis_full_version()');
|
||||
preg_match('#POSTGIS="([0-9]+)[.]([0-9]+)[.]([0-9]+)( r([0-9]+))?"#', $sVersionString, $aMatches);
|
||||
return (float) ($aMatches[1].'.'.$aMatches[2]);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
require_once('init.php');
|
||||
require_once('cmd.php');
|
||||
require_once('DebugNone.php');
|
||||
|
||||
// handle http proxy when using file_get_contents
|
||||
if (CONST_HTTP_Proxy) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
require_once('init.php');
|
||||
require_once('ParameterParser.php');
|
||||
require_once(CONST_Debug ? 'DebugHtml.php' : 'DebugNone.php');
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
@@ -9,79 +10,64 @@ require_once('ParameterParser.php');
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
function chksql($oSql, $sMsg = "Database request failed")
|
||||
function userError($sMsg)
|
||||
{
|
||||
if (!PEAR::isError($oSql)) return $oSql;
|
||||
throw new Exception($sMsg, 400);
|
||||
}
|
||||
|
||||
header('HTTP/1.0 500 Internal Server Error');
|
||||
header('Content-type: text/html; charset=utf-8');
|
||||
|
||||
$sSqlError = $oSql->getMessage();
|
||||
function exception_handler_json($exception)
|
||||
{
|
||||
http_response_code($exception->getCode());
|
||||
header('Content-type: application/json; charset=utf-8');
|
||||
include(CONST_BasePath.'/lib/template/error-json.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
echo <<<INTERNALFAIL
|
||||
<html>
|
||||
<head><title>Internal Server Error</title></head>
|
||||
<body>
|
||||
<h1>Internal Server Error</h1>
|
||||
<p>Nominatim has encountered an internal error while accessing the database.
|
||||
This may happen because the database is broken or because of a bug in
|
||||
the software. If you think it is a bug, feel free to report
|
||||
it over on <a href="https://github.com/openstreetmap/Nominatim/issues">
|
||||
Github</a>. Please include the URL that caused the problem and the
|
||||
complete error details below.</p>
|
||||
<p><b>Message:</b> $sMsg</p>
|
||||
<p><b>SQL Error:</b> $sSqlError</p>
|
||||
<p><b>Details:</b> <pre>
|
||||
INTERNALFAIL;
|
||||
function exception_handler_xml($exception)
|
||||
{
|
||||
http_response_code($exception->getCode());
|
||||
header('Content-type: text/xml; charset=utf-8');
|
||||
echo '<?xml version="1.0" encoding="UTF-8" ?>'."\n";
|
||||
include(CONST_BasePath.'/lib/template/error-xml.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
if (CONST_Debug) {
|
||||
var_dump($oSql);
|
||||
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 {
|
||||
echo "<pre>\n".$oSql->getUserInfo()."</pre>";
|
||||
set_exception_handler('exception_handler_json');
|
||||
register_shutdown_function('shutdown_exception_handler_json');
|
||||
}
|
||||
|
||||
echo "</pre></p></body></html>";
|
||||
exit;
|
||||
}
|
||||
|
||||
function failInternalError($sError, $sSQL = false, $vDumpVar = false)
|
||||
{
|
||||
header('HTTP/1.0 500 Internal Server Error');
|
||||
header('Content-type: text/html; charset=utf-8');
|
||||
echo "<html><body><h1>Internal Server Error</h1>";
|
||||
echo '<p>Nominatim has encountered an internal error while processing your request. This is most likely because of a bug in the software.</p>';
|
||||
echo "<p><b>Details:</b> ".$sError,"</p>";
|
||||
echo '<p>Feel free to file an issue on <a href="https://github.com/openstreetmap/Nominatim/issues">Github</a>. ';
|
||||
echo 'Please include the error message above and the URL you used.</p>';
|
||||
if (CONST_Debug) {
|
||||
echo "<hr><h2>Debugging Information</h2><br>";
|
||||
if ($sSQL) {
|
||||
echo "<h3>SQL query</h3><code>".$sSQL."</code>";
|
||||
}
|
||||
if ($vDumpVar) {
|
||||
echo "<h3>Result</h3> <code>";
|
||||
var_dump($vDumpVar);
|
||||
echo "</code>";
|
||||
}
|
||||
}
|
||||
echo "\n</body></html>\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
function userError($sError)
|
||||
{
|
||||
header('HTTP/1.0 400 Bad Request');
|
||||
header('Content-type: text/html; charset=utf-8');
|
||||
echo "<html><body><h1>Bad Request</h1>";
|
||||
echo '<p>Nominatim has encountered an error with your request.</p>';
|
||||
echo "<p><b>Details:</b> ".$sError."</p>";
|
||||
echo '<p>If you feel this error is incorrect feel file an issue on <a href="https://github.com/openstreetmap/Nominatim/issues">Github</a>. ';
|
||||
echo 'Please include the error message above and the URL you used.</p>';
|
||||
echo "\n</body></html>\n";
|
||||
exit;
|
||||
}
|
||||
// set a default
|
||||
set_exception_handler_by_format();
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
@@ -89,12 +75,12 @@ function userError($sError)
|
||||
*/
|
||||
|
||||
if (CONST_NoAccessControl) {
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Access-Control-Allow-Methods: OPTIONS,GET");
|
||||
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']);
|
||||
header('Access-Control-Allow-Headers: '.$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
|
||||
}
|
||||
}
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') exit;
|
||||
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS') exit;
|
||||
|
||||
if (CONST_Debug) header('Content-type: text/html; charset=utf-8');
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
<?php
|
||||
|
||||
require_once(CONST_BasePath.'/lib/lib.php');
|
||||
require_once(CONST_BasePath.'/lib/db.php');
|
||||
|
||||
if (get_magic_quotes_gpc()) {
|
||||
echo "Please disable magic quotes in your php.ini configuration\n";
|
||||
exit;
|
||||
}
|
||||
require_once(CONST_BasePath.'/lib/DB.php');
|
||||
|
||||
705
lib/lib.php
705
lib/lib.php
@@ -1,11 +1,10 @@
|
||||
<?php
|
||||
|
||||
|
||||
function fail($sError, $sUserError = false)
|
||||
{
|
||||
if (!$sUserError) $sUserError = $sError;
|
||||
error_log('ERROR: '.$sError);
|
||||
echo $sUserError."\n";
|
||||
var_dump($sUserError)."\n";
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
@@ -14,7 +13,7 @@ function getProcessorCount()
|
||||
{
|
||||
$sCPU = file_get_contents('/proc/cpuinfo');
|
||||
preg_match_all('#processor\s+: [0-9]+#', $sCPU, $aMatches);
|
||||
return sizeof($aMatches[0]);
|
||||
return count($aMatches[0]);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +37,7 @@ 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";
|
||||
$sLastNodeURL = 'https://www.openstreetmap.org/api/0.6/node/'.$iLastOSMID.'/1';
|
||||
$sLastNodeXML = file_get_contents($sLastNodeURL);
|
||||
|
||||
if ($sLastNodeXML === false) {
|
||||
@@ -51,605 +50,119 @@ function getDatabaseDate(&$oDB)
|
||||
}
|
||||
|
||||
|
||||
function bySearchRank($a, $b)
|
||||
{
|
||||
if ($a['iSearchRank'] == $b['iSearchRank'])
|
||||
return strlen($a['sOperator']) + strlen($a['sHouseNumber']) - strlen($b['sOperator']) - strlen($b['sHouseNumber']);
|
||||
return ($a['iSearchRank'] < $b['iSearchRank']?-1:1);
|
||||
}
|
||||
|
||||
|
||||
function byImportance($a, $b)
|
||||
{
|
||||
if ($a['importance'] != $b['importance'])
|
||||
return ($a['importance'] > $b['importance']?-1:1);
|
||||
|
||||
return ($a['foundorder'] < $b['foundorder']?-1:1);
|
||||
}
|
||||
|
||||
|
||||
function getWordSets($aWords, $iDepth)
|
||||
{
|
||||
$aResult = array(array(join(' ', $aWords)));
|
||||
$sFirstToken = '';
|
||||
if ($iDepth < 7) {
|
||||
while (sizeof($aWords) > 1) {
|
||||
$sWord = array_shift($aWords);
|
||||
$sFirstToken .= ($sFirstToken?' ':'').$sWord;
|
||||
$aRest = getWordSets($aWords, $iDepth+1);
|
||||
foreach ($aRest as $aSet) {
|
||||
$aResult[] = array_merge(array($sFirstToken), $aSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
function getInverseWordSets($aWords, $iDepth)
|
||||
{
|
||||
$aResult = array(array(join(' ', $aWords)));
|
||||
$sFirstToken = '';
|
||||
if ($iDepth < 8) {
|
||||
while (sizeof($aWords) > 1) {
|
||||
$sWord = array_pop($aWords);
|
||||
$sFirstToken = $sWord.($sFirstToken?' ':'').$sFirstToken;
|
||||
$aRest = getInverseWordSets($aWords, $iDepth+1);
|
||||
foreach ($aRest as $aSet) {
|
||||
$aResult[] = array_merge(array($sFirstToken), $aSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
|
||||
function getTokensFromSets($aSets)
|
||||
{
|
||||
$aTokens = array();
|
||||
foreach ($aSets as $aSet) {
|
||||
foreach ($aSet as $sWord) {
|
||||
$aTokens[' '.$sWord] = ' '.$sWord;
|
||||
$aTokens[$sWord] = $sWord;
|
||||
}
|
||||
}
|
||||
return $aTokens;
|
||||
}
|
||||
|
||||
|
||||
function gbPostcodeCalculate($sPostcode, $sPostcodeSector, $sPostcodeEnd, &$oDB)
|
||||
{
|
||||
// Try an exact match on the gb_postcode table
|
||||
$sSQL = 'select \'AA\', ST_X(ST_Centroid(geometry)) as lon,ST_Y(ST_Centroid(geometry)) as lat from gb_postcode where postcode = \''.$sPostcode.'\'';
|
||||
$aNearPostcodes = chksql($oDB->getAll($sSQL));
|
||||
|
||||
if (sizeof($aNearPostcodes)) {
|
||||
$aPostcodes = array();
|
||||
foreach ($aNearPostcodes as $aPostcode) {
|
||||
$aPostcodes[] = array('lat' => $aPostcode['lat'], 'lon' => $aPostcode['lon'], 'radius' => 0.005);
|
||||
}
|
||||
|
||||
return $aPostcodes;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function getClassTypes()
|
||||
{
|
||||
return array(
|
||||
'boundary:administrative:1' => array('label' => 'Continent', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'boundary:administrative:2' => array('label' => 'Country', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'place:country' => array('label' => 'Country', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defzoom' => 6, 'defdiameter' => 15),
|
||||
'boundary:administrative:3' => array('label' => 'State', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'boundary:administrative:4' => array('label' => 'State', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'place:state' => array('label' => 'State', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defzoom' => 8, 'defdiameter' => 5.12),
|
||||
'boundary:administrative:5' => array('label' => 'State District', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'boundary:administrative:6' => array('label' => 'County', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'boundary:administrative:7' => array('label' => 'County', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'place:county' => array('label' => 'County', 'frequency' => 108, 'icon' => 'poi_boundary_administrative', 'defzoom' => 10, 'defdiameter' => 1.28),
|
||||
'boundary:administrative:8' => array('label' => 'City', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'place:city' => array('label' => 'City', 'frequency' => 66, 'icon' => 'poi_place_city', 'defzoom' => 12, 'defdiameter' => 0.32),
|
||||
'boundary:administrative:9' => array('label' => 'City District', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'boundary:administrative:10' => array('label' => 'Suburb', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'boundary:administrative:11' => array('label' => 'Neighbourhood', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'place:region' => array('label' => 'Region', 'frequency' => 0, 'icon' => 'poi_boundary_administrative', 'defzoom' => 8, 'defdiameter' => 0.04),
|
||||
'place:island' => array('label' => 'Island', 'frequency' => 288, 'icon' => '', 'defzoom' => 11, 'defdiameter' => 0.64),
|
||||
'boundary:administrative' => array('label' => 'Administrative', 'frequency' => 413, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'boundary:postal_code' => array('label' => 'Postcode', 'frequency' => 413, 'icon' => 'poi_boundary_administrative', 'defdiameter' => 0.32),
|
||||
'place:town' => array('label' => 'Town', 'frequency' => 1497, 'icon' => 'poi_place_town', 'defzoom' => 14, 'defdiameter' => 0.08),
|
||||
'place:village' => array('label' => 'Village', 'frequency' => 11230, 'icon' => 'poi_place_village', 'defzoom' => 15, 'defdiameter' => 0.04),
|
||||
'place:hamlet' => array('label' => 'Hamlet', 'frequency' => 7075, 'icon' => 'poi_place_village', 'defzoom' => 15, 'defdiameter' => 0.04),
|
||||
'place:suburb' => array('label' => 'Suburb', 'frequency' => 2528, 'icon' => 'poi_place_village', 'defdiameter' => 0.04),
|
||||
'place:locality' => array('label' => 'Locality', 'frequency' => 4113, 'icon' => 'poi_place_village', 'defdiameter' => 0.02),
|
||||
'landuse:farm' => array('label' => 'Farm', 'frequency' => 1201, 'icon' => '', 'defdiameter' => 0.02),
|
||||
'place:farm' => array('label' => 'Farm', 'frequency' => 1162, 'icon' => '', 'defdiameter' => 0.02),
|
||||
|
||||
'highway:motorway_junction' => array('label' => 'Motorway Junction', 'frequency' => 1126, 'icon' => '', 'simplelabel' => 'Junction'),
|
||||
'highway:motorway' => array('label' => 'Motorway', 'frequency' => 4627, 'icon' => '', 'simplelabel' => 'Road'),
|
||||
'highway:trunk' => array('label' => 'Trunk', 'frequency' => 23084, 'icon' => '', 'simplelabel' => 'Road'),
|
||||
'highway:primary' => array('label' => 'Primary', 'frequency' => 32138, 'icon' => '', 'simplelabel' => 'Road'),
|
||||
'highway:secondary' => array('label' => 'Secondary', 'frequency' => 25807, 'icon' => '', 'simplelabel' => 'Road'),
|
||||
'highway:tertiary' => array('label' => 'Tertiary', 'frequency' => 29829, 'icon' => '', 'simplelabel' => 'Road'),
|
||||
'highway:residential' => array('label' => 'Residential', 'frequency' => 361498, 'icon' => '', 'simplelabel' => 'Road'),
|
||||
'highway:unclassified' => array('label' => 'Unclassified', 'frequency' => 66441, 'icon' => '', 'simplelabel' => 'Road'),
|
||||
'highway:living_street' => array('label' => 'Living Street', 'frequency' => 710, 'icon' => '', 'simplelabel' => 'Road'),
|
||||
'highway:service' => array('label' => 'Service', 'frequency' => 9963, 'icon' => '', 'simplelabel' => 'Road'),
|
||||
'highway:track' => array('label' => 'Track', 'frequency' => 2565, 'icon' => '', 'simplelabel' => 'Road'),
|
||||
'highway:road' => array('label' => 'Road', 'frequency' => 591, 'icon' => '', 'simplelabel' => 'Road'),
|
||||
'highway:byway' => array('label' => 'Byway', 'frequency' => 346, 'icon' => '', 'simplelabel' => 'Road'),
|
||||
'highway:bridleway' => array('label' => 'Bridleway', 'frequency' => 1556, 'icon' => ''),
|
||||
'highway:cycleway' => array('label' => 'Cycleway', 'frequency' => 2419, 'icon' => ''),
|
||||
'highway:pedestrian' => array('label' => 'Pedestrian', 'frequency' => 2757, 'icon' => ''),
|
||||
'highway:footway' => array('label' => 'Footway', 'frequency' => 15008, 'icon' => ''),
|
||||
'highway:steps' => array('label' => 'Steps', 'frequency' => 444, 'icon' => '', 'simplelabel' => 'Footway'),
|
||||
'highway:motorway_link' => array('label' => 'Motorway Link', 'frequency' => 795, 'icon' => '', 'simplelabel' => 'Road'),
|
||||
'highway:trunk_link' => array('label' => 'Trunk Link', 'frequency' => 1258, 'icon' => '', 'simplelabel' => 'Road'),
|
||||
'highway:primary_link' => array('label' => 'Primary Link', 'frequency' => 313, 'icon' => '', 'simplelabel' => 'Road'),
|
||||
|
||||
'landuse:industrial' => array('label' => 'Industrial', 'frequency' => 1062, 'icon' => ''),
|
||||
'landuse:residential' => array('label' => 'Residential', 'frequency' => 886, 'icon' => ''),
|
||||
'landuse:retail' => array('label' => 'Retail', 'frequency' => 754, 'icon' => ''),
|
||||
'landuse:commercial' => array('label' => 'Commercial', 'frequency' => 657, 'icon' => ''),
|
||||
|
||||
'place:airport' => array('label' => 'Airport', 'frequency' => 36, 'icon' => 'transport_airport2', 'defdiameter' => 0.03),
|
||||
'aeroway:aerodrome' => array('label' => 'Aerodrome', 'frequency' => 36, 'icon' => 'transport_airport2', 'defdiameter' => 0.03),
|
||||
'aeroway' => array('label' => 'Aeroway', 'frequency' => 36, 'icon' => 'transport_airport2', 'defdiameter' => 0.03),
|
||||
'railway:station' => array('label' => 'Station', 'frequency' => 3431, 'icon' => 'transport_train_station2', 'defdiameter' => 0.01),
|
||||
'amenity:place_of_worship' => array('label' => 'Place Of Worship', 'frequency' => 9049, 'icon' => 'place_of_worship_unknown3'),
|
||||
'amenity:pub' => array('label' => 'Pub', 'frequency' => 18969, 'icon' => 'food_pub'),
|
||||
'amenity:bar' => array('label' => 'Bar', 'frequency' => 164, 'icon' => 'food_bar'),
|
||||
'amenity:university' => array('label' => 'University', 'frequency' => 607, 'icon' => 'education_university'),
|
||||
'tourism:museum' => array('label' => 'Museum', 'frequency' => 543, 'icon' => 'tourist_museum'),
|
||||
'amenity:arts_centre' => array('label' => 'Arts Centre', 'frequency' => 136, 'icon' => 'tourist_art_gallery2'),
|
||||
'tourism:zoo' => array('label' => 'Zoo', 'frequency' => 47, 'icon' => 'tourist_zoo'),
|
||||
'tourism:theme_park' => array('label' => 'Theme Park', 'frequency' => 24, 'icon' => 'poi_point_of_interest'),
|
||||
'tourism:attraction' => array('label' => 'Attraction', 'frequency' => 1463, 'icon' => 'poi_point_of_interest'),
|
||||
'leisure:golf_course' => array('label' => 'Golf Course', 'frequency' => 712, 'icon' => 'sport_golf'),
|
||||
'historic:castle' => array('label' => 'Castle', 'frequency' => 316, 'icon' => 'tourist_castle'),
|
||||
'amenity:hospital' => array('label' => 'Hospital', 'frequency' => 879, 'icon' => 'health_hospital'),
|
||||
'amenity:school' => array('label' => 'School', 'frequency' => 8192, 'icon' => 'education_school'),
|
||||
'amenity:theatre' => array('label' => 'Theatre', 'frequency' => 371, 'icon' => 'tourist_theatre'),
|
||||
'amenity:public_building' => array('label' => 'Public Building', 'frequency' => 985, 'icon' => ''),
|
||||
'amenity:library' => array('label' => 'Library', 'frequency' => 794, 'icon' => 'amenity_library'),
|
||||
'amenity:townhall' => array('label' => 'Townhall', 'frequency' => 242, 'icon' => ''),
|
||||
'amenity:community_centre' => array('label' => 'Community Centre', 'frequency' => 157, 'icon' => ''),
|
||||
'amenity:fire_station' => array('label' => 'Fire Station', 'frequency' => 221, 'icon' => 'amenity_firestation3'),
|
||||
'amenity:police' => array('label' => 'Police', 'frequency' => 334, 'icon' => 'amenity_police2'),
|
||||
'amenity:bank' => array('label' => 'Bank', 'frequency' => 1248, 'icon' => 'money_bank2'),
|
||||
'amenity:post_office' => array('label' => 'Post Office', 'frequency' => 859, 'icon' => 'amenity_post_office'),
|
||||
'leisure:park' => array('label' => 'Park', 'frequency' => 2378, 'icon' => ''),
|
||||
'amenity:park' => array('label' => 'Park', 'frequency' => 53, 'icon' => ''),
|
||||
'landuse:park' => array('label' => 'Park', 'frequency' => 50, 'icon' => ''),
|
||||
'landuse:recreation_ground' => array('label' => 'Recreation Ground', 'frequency' => 517, 'icon' => ''),
|
||||
'tourism:hotel' => array('label' => 'Hotel', 'frequency' => 2150, 'icon' => 'accommodation_hotel2'),
|
||||
'tourism:motel' => array('label' => 'Motel', 'frequency' => 43, 'icon' => ''),
|
||||
'amenity:cinema' => array('label' => 'Cinema', 'frequency' => 277, 'icon' => 'tourist_cinema'),
|
||||
'tourism:artwork' => array('label' => 'Artwork', 'frequency' => 171, 'icon' => 'tourist_art_gallery2'),
|
||||
'historic:archaeological_site' => array('label' => 'Archaeological Site', 'frequency' => 407, 'icon' => 'tourist_archaeological2'),
|
||||
'amenity:doctors' => array('label' => 'Doctors', 'frequency' => 581, 'icon' => 'health_doctors'),
|
||||
'leisure:sports_centre' => array('label' => 'Sports Centre', 'frequency' => 767, 'icon' => 'sport_leisure_centre'),
|
||||
'leisure:swimming_pool' => array('label' => 'Swimming Pool', 'frequency' => 24, 'icon' => 'sport_swimming_outdoor'),
|
||||
'shop:supermarket' => array('label' => 'Supermarket', 'frequency' => 2673, 'icon' => 'shopping_supermarket'),
|
||||
'shop:convenience' => array('label' => 'Convenience', 'frequency' => 1469, 'icon' => 'shopping_convenience'),
|
||||
'amenity:restaurant' => array('label' => 'Restaurant', 'frequency' => 3179, 'icon' => 'food_restaurant'),
|
||||
'amenity:fast_food' => array('label' => 'Fast Food', 'frequency' => 2289, 'icon' => 'food_fastfood'),
|
||||
'amenity:cafe' => array('label' => 'Cafe', 'frequency' => 1780, 'icon' => 'food_cafe'),
|
||||
'tourism:guest_house' => array('label' => 'Guest House', 'frequency' => 223, 'icon' => 'accommodation_bed_and_breakfast'),
|
||||
'amenity:pharmacy' => array('label' => 'Pharmacy', 'frequency' => 733, 'icon' => 'health_pharmacy_dispensing'),
|
||||
'amenity:fuel' => array('label' => 'Fuel', 'frequency' => 1308, 'icon' => 'transport_fuel'),
|
||||
'natural:peak' => array('label' => 'Peak', 'frequency' => 3212, 'icon' => 'poi_peak'),
|
||||
'waterway:waterfall' => array('label' => 'Waterfall', 'frequency' => 24, 'icon' => ''),
|
||||
'natural:wood' => array('label' => 'Wood', 'frequency' => 1845, 'icon' => 'landuse_coniferous_and_deciduous'),
|
||||
'natural:water' => array('label' => 'Water', 'frequency' => 1790, 'icon' => ''),
|
||||
'landuse:forest' => array('label' => 'Forest', 'frequency' => 467, 'icon' => ''),
|
||||
'landuse:cemetery' => array('label' => 'Cemetery', 'frequency' => 463, 'icon' => ''),
|
||||
'landuse:allotments' => array('label' => 'Allotments', 'frequency' => 408, 'icon' => ''),
|
||||
'landuse:farmyard' => array('label' => 'Farmyard', 'frequency' => 397, 'icon' => ''),
|
||||
'railway:rail' => array('label' => 'Rail', 'frequency' => 4894, 'icon' => ''),
|
||||
'waterway:canal' => array('label' => 'Canal', 'frequency' => 1723, 'icon' => ''),
|
||||
'waterway:river' => array('label' => 'River', 'frequency' => 4089, 'icon' => ''),
|
||||
'waterway:stream' => array('label' => 'Stream', 'frequency' => 2684, 'icon' => ''),
|
||||
'shop:bicycle' => array('label' => 'Bicycle', 'frequency' => 349, 'icon' => 'shopping_bicycle'),
|
||||
'shop:clothes' => array('label' => 'Clothes', 'frequency' => 315, 'icon' => 'shopping_clothes'),
|
||||
'shop:hairdresser' => array('label' => 'Hairdresser', 'frequency' => 312, 'icon' => 'shopping_hairdresser'),
|
||||
'shop:doityourself' => array('label' => 'Doityourself', 'frequency' => 247, 'icon' => 'shopping_diy'),
|
||||
'shop:estate_agent' => array('label' => 'Estate Agent', 'frequency' => 162, 'icon' => 'shopping_estateagent2'),
|
||||
'shop:car' => array('label' => 'Car', 'frequency' => 159, 'icon' => 'shopping_car'),
|
||||
'shop:garden_centre' => array('label' => 'Garden Centre', 'frequency' => 143, 'icon' => 'shopping_garden_centre'),
|
||||
'shop:car_repair' => array('label' => 'Car Repair', 'frequency' => 141, 'icon' => 'shopping_car_repair'),
|
||||
'shop:newsagent' => array('label' => 'Newsagent', 'frequency' => 132, 'icon' => ''),
|
||||
'shop:bakery' => array('label' => 'Bakery', 'frequency' => 129, 'icon' => 'shopping_bakery'),
|
||||
'shop:furniture' => array('label' => 'Furniture', 'frequency' => 124, 'icon' => ''),
|
||||
'shop:butcher' => array('label' => 'Butcher', 'frequency' => 105, 'icon' => 'shopping_butcher'),
|
||||
'shop:apparel' => array('label' => 'Apparel', 'frequency' => 98, 'icon' => 'shopping_clothes'),
|
||||
'shop:electronics' => array('label' => 'Electronics', 'frequency' => 96, 'icon' => ''),
|
||||
'shop:department_store' => array('label' => 'Department Store', 'frequency' => 86, 'icon' => ''),
|
||||
'shop:books' => array('label' => 'Books', 'frequency' => 85, 'icon' => ''),
|
||||
'shop:yes' => array('label' => 'Shop', 'frequency' => 68, 'icon' => ''),
|
||||
'shop:outdoor' => array('label' => 'Outdoor', 'frequency' => 67, 'icon' => ''),
|
||||
'shop:mall' => array('label' => 'Mall', 'frequency' => 63, 'icon' => ''),
|
||||
'shop:florist' => array('label' => 'Florist', 'frequency' => 61, 'icon' => ''),
|
||||
'shop:charity' => array('label' => 'Charity', 'frequency' => 60, 'icon' => ''),
|
||||
'shop:hardware' => array('label' => 'Hardware', 'frequency' => 59, 'icon' => ''),
|
||||
'shop:laundry' => array('label' => 'Laundry', 'frequency' => 51, 'icon' => 'shopping_laundrette'),
|
||||
'shop:shoes' => array('label' => 'Shoes', 'frequency' => 49, 'icon' => ''),
|
||||
'shop:beverages' => array('label' => 'Beverages', 'frequency' => 48, 'icon' => 'shopping_alcohol'),
|
||||
'shop:dry_cleaning' => array('label' => 'Dry Cleaning', 'frequency' => 46, 'icon' => ''),
|
||||
'shop:carpet' => array('label' => 'Carpet', 'frequency' => 45, 'icon' => ''),
|
||||
'shop:computer' => array('label' => 'Computer', 'frequency' => 44, 'icon' => ''),
|
||||
'shop:alcohol' => array('label' => 'Alcohol', 'frequency' => 44, 'icon' => 'shopping_alcohol'),
|
||||
'shop:optician' => array('label' => 'Optician', 'frequency' => 55, 'icon' => 'health_opticians'),
|
||||
'shop:chemist' => array('label' => 'Chemist', 'frequency' => 42, 'icon' => 'health_pharmacy'),
|
||||
'shop:gallery' => array('label' => 'Gallery', 'frequency' => 38, 'icon' => 'tourist_art_gallery2'),
|
||||
'shop:mobile_phone' => array('label' => 'Mobile Phone', 'frequency' => 37, 'icon' => ''),
|
||||
'shop:sports' => array('label' => 'Sports', 'frequency' => 37, 'icon' => ''),
|
||||
'shop:jewelry' => array('label' => 'Jewelry', 'frequency' => 32, 'icon' => 'shopping_jewelry'),
|
||||
'shop:pet' => array('label' => 'Pet', 'frequency' => 29, 'icon' => ''),
|
||||
'shop:beauty' => array('label' => 'Beauty', 'frequency' => 28, 'icon' => ''),
|
||||
'shop:stationery' => array('label' => 'Stationery', 'frequency' => 25, 'icon' => ''),
|
||||
'shop:shopping_centre' => array('label' => 'Shopping Centre', 'frequency' => 25, 'icon' => ''),
|
||||
'shop:general' => array('label' => 'General', 'frequency' => 25, 'icon' => ''),
|
||||
'shop:electrical' => array('label' => 'Electrical', 'frequency' => 25, 'icon' => ''),
|
||||
'shop:toys' => array('label' => 'Toys', 'frequency' => 23, 'icon' => ''),
|
||||
'shop:jeweller' => array('label' => 'Jeweller', 'frequency' => 23, 'icon' => ''),
|
||||
'shop:betting' => array('label' => 'Betting', 'frequency' => 23, 'icon' => ''),
|
||||
'shop:household' => array('label' => 'Household', 'frequency' => 21, 'icon' => ''),
|
||||
'shop:travel_agency' => array('label' => 'Travel Agency', 'frequency' => 21, 'icon' => ''),
|
||||
'shop:hifi' => array('label' => 'Hifi', 'frequency' => 21, 'icon' => ''),
|
||||
'amenity:shop' => array('label' => 'Shop', 'frequency' => 61, 'icon' => ''),
|
||||
'tourism:information' => array('label' => 'Information', 'frequency' => 224, 'icon' => 'amenity_information'),
|
||||
|
||||
'place:house' => array('label' => 'House', 'frequency' => 2086, 'icon' => '', 'defzoom' => 18),
|
||||
'place:house_name' => array('label' => 'House', 'frequency' => 2086, 'icon' => '', 'defzoom' => 18),
|
||||
'place:house_number' => array('label' => 'House Number', 'frequency' => 2086, 'icon' => '', 'defzoom' => 18),
|
||||
'place:country_code' => array('label' => 'Country Code', 'frequency' => 2086, 'icon' => '', 'defzoom' => 18),
|
||||
|
||||
//
|
||||
|
||||
'leisure:pitch' => array('label' => 'Pitch', 'frequency' => 762, 'icon' => ''),
|
||||
'highway:unsurfaced' => array('label' => 'Unsurfaced', 'frequency' => 492, 'icon' => ''),
|
||||
'historic:ruins' => array('label' => 'Ruins', 'frequency' => 483, 'icon' => 'tourist_ruin'),
|
||||
'amenity:college' => array('label' => 'College', 'frequency' => 473, 'icon' => 'education_school'),
|
||||
'historic:monument' => array('label' => 'Monument', 'frequency' => 470, 'icon' => 'tourist_monument'),
|
||||
'railway:subway' => array('label' => 'Subway', 'frequency' => 385, 'icon' => ''),
|
||||
'historic:memorial' => array('label' => 'Memorial', 'frequency' => 382, 'icon' => 'tourist_monument'),
|
||||
'leisure:nature_reserve' => array('label' => 'Nature Reserve', 'frequency' => 342, 'icon' => ''),
|
||||
'leisure:common' => array('label' => 'Common', 'frequency' => 322, 'icon' => ''),
|
||||
'waterway:lock_gate' => array('label' => 'Lock Gate', 'frequency' => 321, 'icon' => ''),
|
||||
'natural:fell' => array('label' => 'Fell', 'frequency' => 308, 'icon' => ''),
|
||||
'amenity:nightclub' => array('label' => 'Nightclub', 'frequency' => 292, 'icon' => ''),
|
||||
'highway:path' => array('label' => 'Path', 'frequency' => 287, 'icon' => ''),
|
||||
'leisure:garden' => array('label' => 'Garden', 'frequency' => 285, 'icon' => ''),
|
||||
'landuse:reservoir' => array('label' => 'Reservoir', 'frequency' => 276, 'icon' => ''),
|
||||
'leisure:playground' => array('label' => 'Playground', 'frequency' => 264, 'icon' => ''),
|
||||
'leisure:stadium' => array('label' => 'Stadium', 'frequency' => 212, 'icon' => ''),
|
||||
'historic:mine' => array('label' => 'Mine', 'frequency' => 193, 'icon' => 'poi_mine'),
|
||||
'natural:cliff' => array('label' => 'Cliff', 'frequency' => 193, 'icon' => ''),
|
||||
'tourism:caravan_site' => array('label' => 'Caravan Site', 'frequency' => 183, 'icon' => 'accommodation_caravan_park'),
|
||||
'amenity:bus_station' => array('label' => 'Bus Station', 'frequency' => 181, 'icon' => 'transport_bus_station'),
|
||||
'amenity:kindergarten' => array('label' => 'Kindergarten', 'frequency' => 179, 'icon' => ''),
|
||||
'highway:construction' => array('label' => 'Construction', 'frequency' => 176, 'icon' => ''),
|
||||
'amenity:atm' => array('label' => 'Atm', 'frequency' => 172, 'icon' => 'money_atm2'),
|
||||
'amenity:emergency_phone' => array('label' => 'Emergency Phone', 'frequency' => 164, 'icon' => ''),
|
||||
'waterway:lock' => array('label' => 'Lock', 'frequency' => 146, 'icon' => ''),
|
||||
'waterway:riverbank' => array('label' => 'Riverbank', 'frequency' => 143, 'icon' => ''),
|
||||
'natural:coastline' => array('label' => 'Coastline', 'frequency' => 142, 'icon' => ''),
|
||||
'tourism:viewpoint' => array('label' => 'Viewpoint', 'frequency' => 140, 'icon' => 'tourist_view_point'),
|
||||
'tourism:hostel' => array('label' => 'Hostel', 'frequency' => 140, 'icon' => ''),
|
||||
'tourism:bed_and_breakfast' => array('label' => 'Bed And Breakfast', 'frequency' => 140, 'icon' => 'accommodation_bed_and_breakfast'),
|
||||
'railway:halt' => array('label' => 'Halt', 'frequency' => 135, 'icon' => ''),
|
||||
'railway:platform' => array('label' => 'Platform', 'frequency' => 134, 'icon' => ''),
|
||||
'railway:tram' => array('label' => 'Tram', 'frequency' => 130, 'icon' => 'transport_tram_stop'),
|
||||
'amenity:courthouse' => array('label' => 'Courthouse', 'frequency' => 129, 'icon' => 'amenity_court'),
|
||||
'amenity:recycling' => array('label' => 'Recycling', 'frequency' => 126, 'icon' => 'amenity_recycling'),
|
||||
'amenity:dentist' => array('label' => 'Dentist', 'frequency' => 124, 'icon' => 'health_dentist'),
|
||||
'natural:beach' => array('label' => 'Beach', 'frequency' => 121, 'icon' => 'tourist_beach'),
|
||||
'place:moor' => array('label' => 'Moor', 'frequency' => 118, 'icon' => ''),
|
||||
'amenity:grave_yard' => array('label' => 'Grave Yard', 'frequency' => 110, 'icon' => ''),
|
||||
'waterway:drain' => array('label' => 'Drain', 'frequency' => 108, 'icon' => ''),
|
||||
'landuse:grass' => array('label' => 'Grass', 'frequency' => 106, 'icon' => ''),
|
||||
'landuse:village_green' => array('label' => 'Village Green', 'frequency' => 106, 'icon' => ''),
|
||||
'natural:bay' => array('label' => 'Bay', 'frequency' => 102, 'icon' => ''),
|
||||
'railway:tram_stop' => array('label' => 'Tram Stop', 'frequency' => 101, 'icon' => 'transport_tram_stop'),
|
||||
'leisure:marina' => array('label' => 'Marina', 'frequency' => 98, 'icon' => ''),
|
||||
'highway:stile' => array('label' => 'Stile', 'frequency' => 97, 'icon' => ''),
|
||||
'natural:moor' => array('label' => 'Moor', 'frequency' => 95, 'icon' => ''),
|
||||
'railway:light_rail' => array('label' => 'Light Rail', 'frequency' => 91, 'icon' => ''),
|
||||
'railway:narrow_gauge' => array('label' => 'Narrow Gauge', 'frequency' => 90, 'icon' => ''),
|
||||
'natural:land' => array('label' => 'Land', 'frequency' => 86, 'icon' => ''),
|
||||
'amenity:village_hall' => array('label' => 'Village Hall', 'frequency' => 82, 'icon' => ''),
|
||||
'waterway:dock' => array('label' => 'Dock', 'frequency' => 80, 'icon' => ''),
|
||||
'amenity:veterinary' => array('label' => 'Veterinary', 'frequency' => 79, 'icon' => ''),
|
||||
'landuse:brownfield' => array('label' => 'Brownfield', 'frequency' => 77, 'icon' => ''),
|
||||
'leisure:track' => array('label' => 'Track', 'frequency' => 76, 'icon' => ''),
|
||||
'railway:historic_station' => array('label' => 'Historic Station', 'frequency' => 74, 'icon' => ''),
|
||||
'landuse:construction' => array('label' => 'Construction', 'frequency' => 72, 'icon' => ''),
|
||||
'amenity:prison' => array('label' => 'Prison', 'frequency' => 71, 'icon' => 'amenity_prison'),
|
||||
'landuse:quarry' => array('label' => 'Quarry', 'frequency' => 71, 'icon' => ''),
|
||||
'amenity:telephone' => array('label' => 'Telephone', 'frequency' => 70, 'icon' => ''),
|
||||
'highway:traffic_signals' => array('label' => 'Traffic Signals', 'frequency' => 66, 'icon' => ''),
|
||||
'natural:heath' => array('label' => 'Heath', 'frequency' => 62, 'icon' => ''),
|
||||
'historic:house' => array('label' => 'House', 'frequency' => 61, 'icon' => ''),
|
||||
'amenity:social_club' => array('label' => 'Social Club', 'frequency' => 61, 'icon' => ''),
|
||||
'landuse:military' => array('label' => 'Military', 'frequency' => 61, 'icon' => ''),
|
||||
'amenity:health_centre' => array('label' => 'Health Centre', 'frequency' => 59, 'icon' => ''),
|
||||
'historic:building' => array('label' => 'Building', 'frequency' => 58, 'icon' => ''),
|
||||
'amenity:clinic' => array('label' => 'Clinic', 'frequency' => 57, 'icon' => ''),
|
||||
'highway:services' => array('label' => 'Services', 'frequency' => 56, 'icon' => ''),
|
||||
'amenity:ferry_terminal' => array('label' => 'Ferry Terminal', 'frequency' => 55, 'icon' => ''),
|
||||
'natural:marsh' => array('label' => 'Marsh', 'frequency' => 55, 'icon' => ''),
|
||||
'natural:hill' => array('label' => 'Hill', 'frequency' => 54, 'icon' => ''),
|
||||
'highway:raceway' => array('label' => 'Raceway', 'frequency' => 53, 'icon' => ''),
|
||||
'amenity:taxi' => array('label' => 'Taxi', 'frequency' => 47, 'icon' => ''),
|
||||
'amenity:take_away' => array('label' => 'Take Away', 'frequency' => 45, 'icon' => ''),
|
||||
'amenity:car_rental' => array('label' => 'Car Rental', 'frequency' => 44, 'icon' => ''),
|
||||
'place:islet' => array('label' => 'Islet', 'frequency' => 44, 'icon' => ''),
|
||||
'amenity:nursery' => array('label' => 'Nursery', 'frequency' => 44, 'icon' => ''),
|
||||
'amenity:nursing_home' => array('label' => 'Nursing Home', 'frequency' => 43, 'icon' => ''),
|
||||
'amenity:toilets' => array('label' => 'Toilets', 'frequency' => 38, 'icon' => ''),
|
||||
'amenity:hall' => array('label' => 'Hall', 'frequency' => 38, 'icon' => ''),
|
||||
'waterway:boatyard' => array('label' => 'Boatyard', 'frequency' => 36, 'icon' => ''),
|
||||
'highway:mini_roundabout' => array('label' => 'Mini Roundabout', 'frequency' => 35, 'icon' => ''),
|
||||
'historic:manor' => array('label' => 'Manor', 'frequency' => 35, 'icon' => ''),
|
||||
'tourism:chalet' => array('label' => 'Chalet', 'frequency' => 34, 'icon' => ''),
|
||||
'amenity:bicycle_parking' => array('label' => 'Bicycle Parking', 'frequency' => 34, 'icon' => ''),
|
||||
'amenity:hotel' => array('label' => 'Hotel', 'frequency' => 34, 'icon' => ''),
|
||||
'waterway:weir' => array('label' => 'Weir', 'frequency' => 33, 'icon' => ''),
|
||||
'natural:wetland' => array('label' => 'Wetland', 'frequency' => 33, 'icon' => ''),
|
||||
'natural:cave_entrance' => array('label' => 'Cave Entrance', 'frequency' => 32, 'icon' => ''),
|
||||
'amenity:crematorium' => array('label' => 'Crematorium', 'frequency' => 31, 'icon' => ''),
|
||||
'tourism:picnic_site' => array('label' => 'Picnic Site', 'frequency' => 31, 'icon' => ''),
|
||||
'landuse:wood' => array('label' => 'Wood', 'frequency' => 30, 'icon' => ''),
|
||||
'landuse:basin' => array('label' => 'Basin', 'frequency' => 30, 'icon' => ''),
|
||||
'natural:tree' => array('label' => 'Tree', 'frequency' => 30, 'icon' => ''),
|
||||
'leisure:slipway' => array('label' => 'Slipway', 'frequency' => 29, 'icon' => ''),
|
||||
'landuse:meadow' => array('label' => 'Meadow', 'frequency' => 29, 'icon' => ''),
|
||||
'landuse:piste' => array('label' => 'Piste', 'frequency' => 28, 'icon' => ''),
|
||||
'amenity:care_home' => array('label' => 'Care Home', 'frequency' => 28, 'icon' => ''),
|
||||
'amenity:club' => array('label' => 'Club', 'frequency' => 28, 'icon' => ''),
|
||||
'amenity:medical_centre' => array('label' => 'Medical Centre', 'frequency' => 27, 'icon' => ''),
|
||||
'historic:roman_road' => array('label' => 'Roman Road', 'frequency' => 27, 'icon' => ''),
|
||||
'historic:fort' => array('label' => 'Fort', 'frequency' => 26, 'icon' => ''),
|
||||
'railway:subway_entrance' => array('label' => 'Subway Entrance', 'frequency' => 26, 'icon' => ''),
|
||||
'historic:yes' => array('label' => 'Historic', 'frequency' => 25, 'icon' => ''),
|
||||
'highway:gate' => array('label' => 'Gate', 'frequency' => 25, 'icon' => ''),
|
||||
'leisure:fishing' => array('label' => 'Fishing', 'frequency' => 24, 'icon' => ''),
|
||||
'historic:museum' => array('label' => 'Museum', 'frequency' => 24, 'icon' => ''),
|
||||
'amenity:car_wash' => array('label' => 'Car Wash', 'frequency' => 24, 'icon' => ''),
|
||||
'railway:level_crossing' => array('label' => 'Level Crossing', 'frequency' => 23, 'icon' => ''),
|
||||
'leisure:bird_hide' => array('label' => 'Bird Hide', 'frequency' => 23, 'icon' => ''),
|
||||
'natural:headland' => array('label' => 'Headland', 'frequency' => 21, 'icon' => ''),
|
||||
'tourism:apartments' => array('label' => 'Apartments', 'frequency' => 21, 'icon' => ''),
|
||||
'amenity:shopping' => array('label' => 'Shopping', 'frequency' => 21, 'icon' => ''),
|
||||
'natural:scrub' => array('label' => 'Scrub', 'frequency' => 20, 'icon' => ''),
|
||||
'natural:fen' => array('label' => 'Fen', 'frequency' => 20, 'icon' => ''),
|
||||
'building:yes' => array('label' => 'Building', 'frequency' => 200, 'icon' => ''),
|
||||
'mountain_pass:yes' => array('label' => 'Mountain Pass', 'frequency' => 200, 'icon' => ''),
|
||||
|
||||
'amenity:parking' => array('label' => 'Parking', 'frequency' => 3157, 'icon' => ''),
|
||||
'highway:bus_stop' => array('label' => 'Bus Stop', 'frequency' => 35777, 'icon' => 'transport_bus_stop2'),
|
||||
'place:postcode' => array('label' => 'Postcode', 'frequency' => 27267, 'icon' => ''),
|
||||
'amenity:post_box' => array('label' => 'Post Box', 'frequency' => 9613, 'icon' => ''),
|
||||
|
||||
'place:houses' => array('label' => 'Houses', 'frequency' => 85, 'icon' => ''),
|
||||
'railway:preserved' => array('label' => 'Preserved', 'frequency' => 227, 'icon' => ''),
|
||||
'waterway:derelict_canal' => array('label' => 'Derelict Canal', 'frequency' => 21, 'icon' => ''),
|
||||
'amenity:dead_pub' => array('label' => 'Dead Pub', 'frequency' => 20, 'icon' => ''),
|
||||
'railway:disused_station' => array('label' => 'Disused Station', 'frequency' => 114, 'icon' => ''),
|
||||
'railway:abandoned' => array('label' => 'Abandoned', 'frequency' => 641, 'icon' => ''),
|
||||
'railway:disused' => array('label' => 'Disused', 'frequency' => 72, 'icon' => ''),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function getClassTypesWithImportance()
|
||||
{
|
||||
$aOrders = getClassTypes();
|
||||
$i = 1;
|
||||
foreach ($aOrders as $sID => $a) {
|
||||
$aOrders[$sID]['importance'] = $i++;
|
||||
}
|
||||
return $aOrders;
|
||||
}
|
||||
|
||||
function getResultDiameter($aResult)
|
||||
{
|
||||
$aClassType = getClassTypes();
|
||||
|
||||
$fDiameter = 0.0001;
|
||||
|
||||
if (isset($aResult['class'])
|
||||
&& isset($aResult['type'])
|
||||
&& isset($aResult['admin_level'])
|
||||
&& isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'])
|
||||
&& $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter']
|
||||
) {
|
||||
$fDiameter = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter'];
|
||||
} elseif (isset($aResult['class'])
|
||||
&& isset($aResult['type'])
|
||||
&& isset($aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'])
|
||||
&& $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter']
|
||||
) {
|
||||
$fDiameter = $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter'];
|
||||
}
|
||||
|
||||
return $fDiameter;
|
||||
return $a['foundorder'] <=> $b['foundorder'];
|
||||
}
|
||||
|
||||
|
||||
function javascript_renderData($xVal, $iOptions = 0)
|
||||
{
|
||||
$iOptions |= JSON_UNESCAPED_UNICODE;
|
||||
$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 (!isset($_GET['json_callback'])) {
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
echo $jsonout;
|
||||
if ($sCallback) {
|
||||
header('Content-Type: application/javascript; charset=UTF-8');
|
||||
echo $_GET['json_callback'].'('.$jsonout.')';
|
||||
} else {
|
||||
if (preg_match('/^[$_\p{L}][$_\p{L}\p{Nd}.[\]]*$/u', $_GET['json_callback'])) {
|
||||
header("Content-Type: application/javascript; charset=UTF-8");
|
||||
echo $_GET['json_callback'].'('.$jsonout.')';
|
||||
} else {
|
||||
header('HTTP/1.0 400 Bad Request');
|
||||
}
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
echo $jsonout;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _debugDumpGroupedSearches($aData, $aTokens)
|
||||
{
|
||||
$aWordsIDs = array();
|
||||
if ($aTokens) {
|
||||
foreach ($aTokens as $sToken => $aWords) {
|
||||
if ($aWords) {
|
||||
foreach ($aWords as $aToken) {
|
||||
$aWordsIDs[$aToken['word_id']] = $sToken.'('.$aToken['word_id'].')';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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><th>country</th>";
|
||||
echo "<th>operator</th><th>class</th><th>type</th><th>house#</th>";
|
||||
echo "<th>Lat</th><th>Lon</th><th>Radius</th></tr>";
|
||||
foreach ($aData as $iRank => $aRankedSet) {
|
||||
foreach ($aRankedSet as $aRow) {
|
||||
echo "<tr>";
|
||||
echo "<td>$iRank</td>";
|
||||
|
||||
echo "<td>";
|
||||
$sSep = '';
|
||||
foreach ($aRow['aName'] as $iWordID) {
|
||||
echo $sSep.'#'.$aWordsIDs[$iWordID].'#';
|
||||
$sSep = ', ';
|
||||
}
|
||||
echo "</td>";
|
||||
|
||||
echo "<td>";
|
||||
$sSep = '';
|
||||
foreach ($aRow['aNameNonSearch'] as $iWordID) {
|
||||
echo $sSep.'#'.$aWordsIDs[$iWordID].'#';
|
||||
$sSep = ', ';
|
||||
}
|
||||
echo "</td>";
|
||||
|
||||
echo "<td>";
|
||||
$sSep = '';
|
||||
foreach ($aRow['aAddress'] as $iWordID) {
|
||||
echo $sSep.'#'.$aWordsIDs[$iWordID].'#';
|
||||
$sSep = ', ';
|
||||
}
|
||||
echo "</td>";
|
||||
|
||||
echo "<td>";
|
||||
$sSep = '';
|
||||
foreach ($aRow['aAddressNonSearch'] as $iWordID) {
|
||||
echo $sSep.'#'.$aWordsIDs[$iWordID].'#';
|
||||
$sSep = ', ';
|
||||
}
|
||||
echo "</td>";
|
||||
|
||||
echo "<td>".$aRow['sCountryCode']."</td>";
|
||||
|
||||
echo "<td>".$aRow['sOperator']."</td>";
|
||||
echo "<td>".$aRow['sClass']."</td>";
|
||||
echo "<td>".$aRow['sType']."</td>";
|
||||
|
||||
echo "<td>".$aRow['sHouseNumber']."</td>";
|
||||
|
||||
echo "<td>".$aRow['fLat']."</td>";
|
||||
echo "<td>".$aRow['fLon']."</td>";
|
||||
echo "<td>".$aRow['fRadius']."</td>";
|
||||
|
||||
echo "</tr>";
|
||||
}
|
||||
}
|
||||
echo "</table>";
|
||||
}
|
||||
|
||||
|
||||
function getAddressDetails(&$oDB, $sLanguagePrefArraySQL, $iPlaceID, $sCountryCode = false, $housenumber = -1, $bRaw = false)
|
||||
{
|
||||
$sSQL = "select *,get_name_by_language(name,$sLanguagePrefArraySQL) as localname from get_addressdata($iPlaceID, $housenumber)";
|
||||
if (!$bRaw) $sSQL .= " WHERE isaddress OR type = 'country_code'";
|
||||
$sSQL .= " order by rank_address desc,isaddress desc";
|
||||
|
||||
$aAddressLines = chksql($oDB->getAll($sSQL));
|
||||
if ($bRaw) return $aAddressLines;
|
||||
//echo "<pre>";
|
||||
//var_dump($aAddressLines);
|
||||
$aAddress = array();
|
||||
$aFallback = array();
|
||||
$aClassType = getClassTypes();
|
||||
foreach ($aAddressLines as $aLine) {
|
||||
$bFallback = false;
|
||||
$aTypeLabel = false;
|
||||
if (isset($aClassType[$aLine['class'].':'.$aLine['type'].':'.$aLine['admin_level']])) {
|
||||
$aTypeLabel = $aClassType[$aLine['class'].':'.$aLine['type'].':'.$aLine['admin_level']];
|
||||
} elseif (isset($aClassType[$aLine['class'].':'.$aLine['type']])) {
|
||||
$aTypeLabel = $aClassType[$aLine['class'].':'.$aLine['type']];
|
||||
} elseif (isset($aClassType['boundary:administrative:'.((int)($aLine['rank_address']/2))])) {
|
||||
$aTypeLabel = $aClassType['boundary:administrative:'.((int)($aLine['rank_address']/2))];
|
||||
$bFallback = true;
|
||||
} else {
|
||||
$aTypeLabel = array('simplelabel' => 'address'.$aLine['rank_address']);
|
||||
$bFallback = true;
|
||||
}
|
||||
if ($aTypeLabel && ((isset($aLine['localname']) && $aLine['localname']) || (isset($aLine['housenumber']) && $aLine['housenumber']))) {
|
||||
$sTypeLabel = strtolower(isset($aTypeLabel['simplelabel'])?$aTypeLabel['simplelabel']:$aTypeLabel['label']);
|
||||
$sTypeLabel = str_replace(' ', '_', $sTypeLabel);
|
||||
if (!isset($aAddress[$sTypeLabel]) || (isset($aFallback[$sTypeLabel]) && $aFallback[$sTypeLabel]) || $aLine['class'] == 'place') {
|
||||
$aAddress[$sTypeLabel] = $aLine['localname']?$aLine['localname']:$aLine['housenumber'];
|
||||
}
|
||||
$aFallback[$sTypeLabel] = $bFallback;
|
||||
}
|
||||
}
|
||||
return $aAddress;
|
||||
}
|
||||
|
||||
|
||||
function addQuotes($s)
|
||||
{
|
||||
return "'".$s."'";
|
||||
}
|
||||
|
||||
|
||||
function geometryText2Points($geometry_as_text, $fRadius)
|
||||
function parseLatLon($sQuery)
|
||||
{
|
||||
$aPolyPoints = null;
|
||||
if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch)) {
|
||||
//
|
||||
preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/', $aMatch[1], $aPolyPoints, PREG_SET_ORDER);
|
||||
//
|
||||
} elseif (preg_match('#LINESTRING\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch)) {
|
||||
//
|
||||
preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/', $aMatch[1], $aPolyPoints, PREG_SET_ORDER);
|
||||
//
|
||||
} elseif (preg_match('#MULTIPOLYGON\\(\\(\\(([- 0-9.,]+)#', $geometry_as_text, $aMatch)) {
|
||||
//
|
||||
preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/', $aMatch[1], $aPolyPoints, PREG_SET_ORDER);
|
||||
//
|
||||
} elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#', $geometry_as_text, $aMatch)) {
|
||||
//
|
||||
$aPolyPoints = createPointsAroundCenter($aMatch[1], $aMatch[2], $fRadius);
|
||||
//
|
||||
$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;
|
||||
}
|
||||
|
||||
if (isset($aPolyPoints)) {
|
||||
$aResultPoints = array();
|
||||
foreach ($aPolyPoints as $aPoint) {
|
||||
$aResultPoints[] = array($aPoint[1], $aPoint[2]);
|
||||
}
|
||||
return $aResultPoints;
|
||||
}
|
||||
|
||||
return;
|
||||
return array($sFound, $fQueryLat, $fQueryLon);
|
||||
}
|
||||
|
||||
function createPointsAroundCenter($fLon, $fLat, $fRadius)
|
||||
@@ -662,3 +175,45 @@ function createPointsAroundCenter($fLon, $fLat, $fRadius)
|
||||
}
|
||||
return $aPolyPoints;
|
||||
}
|
||||
|
||||
function closestHouseNumber($aRow)
|
||||
{
|
||||
$fHouse = $aRow['startnumber']
|
||||
+ ($aRow['endnumber'] - $aRow['startnumber']) * $aRow['fraction'];
|
||||
|
||||
switch ($aRow['interpolationtype']) {
|
||||
case 'odd':
|
||||
$iHn = (int)($fHouse/2) * 2 + 1;
|
||||
break;
|
||||
case 'even':
|
||||
$iHn = (int)(round($fHouse/2)) * 2;
|
||||
break;
|
||||
default:
|
||||
$iHn = (int)(round($fHouse));
|
||||
break;
|
||||
}
|
||||
|
||||
return max(min($aRow['endnumber'], $iHn), $aRow['startnumber']);
|
||||
}
|
||||
|
||||
function getSearchRankLabel($iRank)
|
||||
{
|
||||
if (!isset($iRank)) return 'unknown';
|
||||
if ($iRank < 2) return 'continent';
|
||||
if ($iRank < 4) return 'sea';
|
||||
if ($iRank < 8) return 'country';
|
||||
if ($iRank < 12) return 'state';
|
||||
if ($iRank < 16) return 'county';
|
||||
if ($iRank == 16) return 'city';
|
||||
if ($iRank == 17) return 'town / island';
|
||||
if ($iRank == 18) return 'village / hamlet';
|
||||
if ($iRank == 20) return 'suburb';
|
||||
if ($iRank == 21) return 'postcode area';
|
||||
if ($iRank == 22) return 'croft / farm / locality / islet';
|
||||
if ($iRank == 23) return 'postcode area';
|
||||
if ($iRank == 25) return 'postcode point';
|
||||
if ($iRank == 26) return 'street / major landmark';
|
||||
if ($iRank == 27) return 'minory street / path';
|
||||
if ($iRank == 28) return 'house / building';
|
||||
return 'other: ' . $iRank;
|
||||
}
|
||||
|
||||
28
lib/log.php
28
lib/log.php
@@ -20,7 +20,7 @@ function logStart(&$oDB, $sType = '', $sQuery = '', $aLanguageList = array())
|
||||
|
||||
$hLog = array(
|
||||
date('Y-m-d H:i:s', $aStartTime[0]).'.'.$aStartTime[1],
|
||||
$_SERVER["REMOTE_ADDR"],
|
||||
$_SERVER['REMOTE_ADDR'],
|
||||
$_SERVER['QUERY_STRING'],
|
||||
$sOutQuery,
|
||||
$sType,
|
||||
@@ -36,9 +36,19 @@ function logStart(&$oDB, $sType = '', $sQuery = '', $aLanguageList = array())
|
||||
$sUserAgent = $_SERVER['HTTP_USER_AGENT'];
|
||||
else $sUserAgent = '';
|
||||
$sSQL = 'insert into new_query_log (type,starttime,query,ipaddress,useragent,language,format,searchterm)';
|
||||
$sSQL .= ' values ('.getDBQuoted($sType).','.getDBQuoted($hLog[0]).','.getDBQuoted($hLog[2]);
|
||||
$sSQL .= ','.getDBQuoted($hLog[1]).','.getDBQuoted($sUserAgent).','.getDBQuoted(join(',', $aLanguageList)).','.getDBQuoted($sOutputFormat).','.getDBQuoted($hLog[3]).')';
|
||||
$oDB->query($sSQL);
|
||||
$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;
|
||||
@@ -53,11 +63,11 @@ function logEnd(&$oDB, $hLog, $iNumResults)
|
||||
if (!$aEndTime[1]) $aEndTime[1] = '0';
|
||||
$sEndTime = date('Y-m-d H:i:s', $aEndTime[0]).'.'.$aEndTime[1];
|
||||
|
||||
$sSQL = 'update new_query_log set endtime = '.getDBQuoted($sEndTime).', results = '.$iNumResults;
|
||||
$sSQL .= ' where starttime = '.getDBQuoted($hLog[0]);
|
||||
$sSQL .= ' and ipaddress = '.getDBQuoted($hLog[1]);
|
||||
$sSQL .= ' and query = '.getDBQuoted($hLog[2]);
|
||||
$oDB->query($sSQL);
|
||||
$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) {
|
||||
|
||||
@@ -12,6 +12,8 @@ function formatOSMType($sType, $bIncludeExternal = true)
|
||||
if ($sType == 'T') return 'way';
|
||||
if ($sType == 'I') return 'way';
|
||||
|
||||
// not handled: P, L
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -33,9 +35,39 @@ function wikipediaLink($aFeature)
|
||||
return '';
|
||||
}
|
||||
|
||||
function detailsLink($aFeature, $sTitle = false)
|
||||
function detailsLink($aFeature, $sTitle = false, $sExtraProperties = false)
|
||||
{
|
||||
if (!$aFeature['place_id']) return '';
|
||||
|
||||
return '<a href="details.php?place_id='.$aFeature['place_id'].'">'.($sTitle?$sTitle:$aFeature['place_id']).'</a>';
|
||||
$sHtml = '<a ';
|
||||
if ($sExtraProperties) {
|
||||
$sHtml .= $sExtraProperties.' ';
|
||||
}
|
||||
|
||||
$sHtml .= 'href="details.php?place_id='.$aFeature['place_id'].'">'.($sTitle?$sTitle:$aFeature['place_id']).'</a>';
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
function detailsPermaLink($aFeature, $sRefText = false, $sExtraProperties = false)
|
||||
{
|
||||
$sOSMType = formatOSMType($aFeature['osm_type'], false);
|
||||
|
||||
if ($sOSMType) {
|
||||
$sHtml = '<a ';
|
||||
if ($sExtraProperties) {
|
||||
$sHtml .= $sExtraProperties.' ';
|
||||
}
|
||||
$sHtml .= 'href="details.php?osmtype='.$aFeature['osm_type']
|
||||
.'&osmid='.$aFeature['osm_id'].'&class='.$aFeature['class'].'">';
|
||||
|
||||
if ($sRefText) {
|
||||
$sHtml .= $sRefText.'</a>';
|
||||
} else {
|
||||
$sHtml .= $sOSMType.' '.$aFeature['osm_id'].'</a>';
|
||||
}
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
return detailsLink($aFeature, $sRefText, $sExtraProperties);
|
||||
}
|
||||
|
||||
98
lib/setup/AddressLevelParser.php
Normal file
98
lib/setup/AddressLevelParser.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim\Setup;
|
||||
|
||||
/**
|
||||
* Parses an address level description.
|
||||
*/
|
||||
class AddressLevelParser
|
||||
{
|
||||
private $aLevels;
|
||||
|
||||
public function __construct($sDescriptionFile)
|
||||
{
|
||||
$sJson = file_get_contents($sDescriptionFile);
|
||||
$this->aLevels = json_decode($sJson, true);
|
||||
if (!$this->aLevels) {
|
||||
switch (json_last_error()) {
|
||||
case JSON_ERROR_NONE:
|
||||
break;
|
||||
case JSON_ERROR_DEPTH:
|
||||
fail('JSON error - Maximum stack depth exceeded');
|
||||
break;
|
||||
case JSON_ERROR_STATE_MISMATCH:
|
||||
fail('JSON error - Underflow or the modes mismatch');
|
||||
break;
|
||||
case JSON_ERROR_CTRL_CHAR:
|
||||
fail('JSON error - Unexpected control character found');
|
||||
break;
|
||||
case JSON_ERROR_SYNTAX:
|
||||
fail('JSON error - Syntax error, malformed JSON');
|
||||
break;
|
||||
case JSON_ERROR_UTF8:
|
||||
fail('JSON error - Malformed UTF-8 characters, possibly incorrectly encoded');
|
||||
break;
|
||||
default:
|
||||
fail('JSON error - Unknown error');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the description into a database table.
|
||||
*
|
||||
* @param object $oDB Database conneciton to use.
|
||||
* @param string $sTable Name of table to create.
|
||||
*
|
||||
* @return null
|
||||
*
|
||||
* A new table is created. Any previously existing table is dropped.
|
||||
* The table has the following columns:
|
||||
* country, class, type, rank_search, rank_address.
|
||||
*/
|
||||
public function createTable($oDB, $sTable)
|
||||
{
|
||||
$oDB->exec('DROP TABLE IF EXISTS '.$sTable);
|
||||
$sSql = 'CREATE TABLE '.$sTable;
|
||||
$sSql .= '(country_code varchar(2), class TEXT, type TEXT,';
|
||||
$sSql .= ' rank_search SMALLINT, rank_address SMALLINT)';
|
||||
$oDB->exec($sSql);
|
||||
|
||||
$sSql = 'CREATE UNIQUE INDEX ON '.$sTable.' (country_code, class, type)';
|
||||
$oDB->exec($sSql);
|
||||
|
||||
$sSql = 'INSERT INTO '.$sTable.' VALUES ';
|
||||
foreach ($this->aLevels as $aLevel) {
|
||||
$aCountries = array();
|
||||
if (isset($aLevel['countries'])) {
|
||||
foreach ($aLevel['countries'] as $sCountry) {
|
||||
$aCountries[$sCountry] = $oDB->getDBQuoted($sCountry);
|
||||
}
|
||||
} else {
|
||||
$aCountries['NULL'] = 'NULL';
|
||||
}
|
||||
foreach ($aLevel['tags'] as $sKey => $aValues) {
|
||||
foreach ($aValues as $sValue => $mRanks) {
|
||||
$aFields = array(
|
||||
$oDB->getDBQuoted($sKey),
|
||||
$sValue ? $oDB->getDBQuoted($sValue) : 'NULL'
|
||||
);
|
||||
if (is_array($mRanks)) {
|
||||
$aFields[] = (string) $mRanks[0];
|
||||
$aFields[] = (string) $mRanks[1];
|
||||
} else {
|
||||
$aFields[] = (string) $mRanks;
|
||||
$aFields[] = (string) $mRanks;
|
||||
}
|
||||
$sLine = ','.join(',', $aFields).'),';
|
||||
|
||||
foreach ($aCountries as $sCountries) {
|
||||
$sSql .= '('.$sCountries.$sLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$oDB->exec(rtrim($sSql, ','));
|
||||
}
|
||||
}
|
||||
942
lib/setup/SetupClass.php
Executable file
942
lib/setup/SetupClass.php
Executable file
@@ -0,0 +1,942 @@
|
||||
<?php
|
||||
|
||||
namespace Nominatim\Setup;
|
||||
|
||||
require_once(CONST_BasePath.'/lib/setup/AddressLevelParser.php');
|
||||
require_once(CONST_BasePath.'/lib/Shell.php');
|
||||
|
||||
class SetupFunctions
|
||||
{
|
||||
protected $iCacheMemory;
|
||||
protected $iInstances;
|
||||
protected $sModulePath;
|
||||
protected $aDSNInfo;
|
||||
protected $bQuiet;
|
||||
protected $bVerbose;
|
||||
protected $sIgnoreErrors;
|
||||
protected $bEnableDiffUpdates;
|
||||
protected $bEnableDebugStatements;
|
||||
protected $bNoPartitions;
|
||||
protected $bDrop;
|
||||
protected $oDB = null;
|
||||
|
||||
public function __construct(array $aCMDResult)
|
||||
{
|
||||
// by default, use all but one processor, but never more than 15.
|
||||
$this->iInstances = isset($aCMDResult['threads'])
|
||||
? $aCMDResult['threads']
|
||||
: (min(16, getProcessorCount()) - 1);
|
||||
|
||||
if ($this->iInstances < 1) {
|
||||
$this->iInstances = 1;
|
||||
warn('resetting threads to '.$this->iInstances);
|
||||
}
|
||||
|
||||
if (isset($aCMDResult['osm2pgsql-cache'])) {
|
||||
$this->iCacheMemory = $aCMDResult['osm2pgsql-cache'];
|
||||
} elseif (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
|
||||
// When flatnode files are enabled then disable cache per default.
|
||||
$this->iCacheMemory = 0;
|
||||
} else {
|
||||
// Otherwise: Assume we can steal all the cache memory in the box.
|
||||
$this->iCacheMemory = getCacheMemoryMB();
|
||||
}
|
||||
|
||||
$this->sModulePath = CONST_Database_Module_Path;
|
||||
info('module path: ' . $this->sModulePath);
|
||||
|
||||
// parse database string
|
||||
$this->aDSNInfo = \Nominatim\DB::parseDSN(CONST_Database_DSN);
|
||||
if (!isset($this->aDSNInfo['port'])) {
|
||||
$this->aDSNInfo['port'] = 5432;
|
||||
}
|
||||
|
||||
// setting member variables based on command line options stored in $aCMDResult
|
||||
$this->bQuiet = isset($aCMDResult['quiet']) && $aCMDResult['quiet'];
|
||||
$this->bVerbose = $aCMDResult['verbose'];
|
||||
|
||||
//setting default values which are not set by the update.php array
|
||||
if (isset($aCMDResult['ignore-errors'])) {
|
||||
$this->sIgnoreErrors = $aCMDResult['ignore-errors'];
|
||||
} else {
|
||||
$this->sIgnoreErrors = false;
|
||||
}
|
||||
if (isset($aCMDResult['enable-debug-statements'])) {
|
||||
$this->bEnableDebugStatements = $aCMDResult['enable-debug-statements'];
|
||||
} else {
|
||||
$this->bEnableDebugStatements = false;
|
||||
}
|
||||
if (isset($aCMDResult['no-partitions'])) {
|
||||
$this->bNoPartitions = $aCMDResult['no-partitions'];
|
||||
} else {
|
||||
$this->bNoPartitions = false;
|
||||
}
|
||||
if (isset($aCMDResult['enable-diff-updates'])) {
|
||||
$this->bEnableDiffUpdates = $aCMDResult['enable-diff-updates'];
|
||||
} else {
|
||||
$this->bEnableDiffUpdates = false;
|
||||
}
|
||||
|
||||
$this->bDrop = isset($aCMDResult['drop']) && $aCMDResult['drop'];
|
||||
}
|
||||
|
||||
public function createDB()
|
||||
{
|
||||
info('Create DB');
|
||||
$oDB = new \Nominatim\DB;
|
||||
|
||||
if ($oDB->checkConnection()) {
|
||||
fail('database already exists ('.CONST_Database_DSN.')');
|
||||
}
|
||||
|
||||
$oCmd = (new \Nominatim\Shell('createdb'))
|
||||
->addParams('-E', 'UTF-8')
|
||||
->addParams('-p', $this->aDSNInfo['port']);
|
||||
|
||||
if (isset($this->aDSNInfo['username'])) {
|
||||
$oCmd->addParams('-U', $this->aDSNInfo['username']);
|
||||
}
|
||||
if (isset($this->aDSNInfo['password'])) {
|
||||
$oCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
|
||||
}
|
||||
if (isset($this->aDSNInfo['hostspec'])) {
|
||||
$oCmd->addParams('-h', $this->aDSNInfo['hostspec']);
|
||||
}
|
||||
$oCmd->addParams($this->aDSNInfo['database']);
|
||||
|
||||
$result = $oCmd->run();
|
||||
if ($result != 0) fail('Error executing external command: '.$oCmd->escapedCmd());
|
||||
}
|
||||
|
||||
public function setupDB()
|
||||
{
|
||||
info('Setup DB');
|
||||
|
||||
$fPostgresVersion = $this->db()->getPostgresVersion();
|
||||
echo 'Postgres version found: '.$fPostgresVersion."\n";
|
||||
|
||||
if ($fPostgresVersion < 9.03) {
|
||||
fail('Minimum supported version of Postgresql is 9.3.');
|
||||
}
|
||||
|
||||
$this->pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
|
||||
$this->pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
|
||||
|
||||
$fPostgisVersion = $this->db()->getPostgisVersion();
|
||||
echo 'Postgis version found: '.$fPostgisVersion."\n";
|
||||
|
||||
if ($fPostgisVersion < 2.2) {
|
||||
echo "Minimum required Postgis version 2.2\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$i = $this->db()->getOne("select count(*) from pg_user where usename = '".CONST_Database_Web_User."'");
|
||||
if ($i == 0) {
|
||||
echo "\nERROR: Web user '".CONST_Database_Web_User."' does not exist. Create it with:\n";
|
||||
echo "\n createuser ".CONST_Database_Web_User."\n\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Try accessing the C module, so we know early if something is wrong
|
||||
checkModulePresence(); // raises exception on failure
|
||||
|
||||
if (!file_exists(CONST_ExtraDataPath.'/country_osm_grid.sql.gz')) {
|
||||
echo 'Error: you need to download the country_osm_grid first:';
|
||||
echo "\n wget -O ".CONST_ExtraDataPath."/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz\n";
|
||||
exit(1);
|
||||
}
|
||||
$this->pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
|
||||
$this->pgsqlRunScriptFile(CONST_ExtraDataPath.'/country_osm_grid.sql.gz');
|
||||
$this->pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
|
||||
$this->pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode_table.sql');
|
||||
|
||||
$sPostcodeFilename = CONST_BasePath.'/data/gb_postcode_data.sql.gz';
|
||||
if (file_exists($sPostcodeFilename)) {
|
||||
$this->pgsqlRunScriptFile($sPostcodeFilename);
|
||||
} else {
|
||||
warn('optional external GB postcode table file ('.$sPostcodeFilename.') not found. Skipping.');
|
||||
}
|
||||
|
||||
$sPostcodeFilename = CONST_BasePath.'/data/us_postcode_data.sql.gz';
|
||||
if (file_exists($sPostcodeFilename)) {
|
||||
$this->pgsqlRunScriptFile($sPostcodeFilename);
|
||||
} else {
|
||||
warn('optional external US postcode table file ('.$sPostcodeFilename.') not found. Skipping.');
|
||||
}
|
||||
|
||||
if ($this->bNoPartitions) {
|
||||
$this->pgsqlRunScript('update country_name set partition = 0');
|
||||
}
|
||||
}
|
||||
|
||||
public function importData($sOSMFile)
|
||||
{
|
||||
info('Import data');
|
||||
|
||||
if (!file_exists(CONST_Osm2pgsql_Binary)) {
|
||||
echo "Check CONST_Osm2pgsql_Binary in your local settings file.\n";
|
||||
echo "Normally you should not need to set this manually.\n";
|
||||
fail("osm2pgsql not found in '".CONST_Osm2pgsql_Binary."'");
|
||||
}
|
||||
|
||||
$oCmd = new \Nominatim\Shell(CONST_Osm2pgsql_Binary);
|
||||
$oCmd->addParams('--style', CONST_Import_Style);
|
||||
|
||||
if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
|
||||
$oCmd->addParams('--flat-nodes', CONST_Osm2pgsql_Flatnode_File);
|
||||
}
|
||||
if (CONST_Tablespace_Osm2pgsql_Data) {
|
||||
$oCmd->addParams('--tablespace-slim-data', CONST_Tablespace_Osm2pgsql_Data);
|
||||
}
|
||||
if (CONST_Tablespace_Osm2pgsql_Index) {
|
||||
$oCmd->addParams('--tablespace-slim-index', CONST_Tablespace_Osm2pgsql_Index);
|
||||
}
|
||||
if (CONST_Tablespace_Place_Data) {
|
||||
$oCmd->addParams('--tablespace-main-data', CONST_Tablespace_Place_Data);
|
||||
}
|
||||
if (CONST_Tablespace_Place_Index) {
|
||||
$oCmd->addParams('--tablespace-main-index', CONST_Tablespace_Place_Index);
|
||||
}
|
||||
$oCmd->addParams('--latlong', '--slim', '--create');
|
||||
$oCmd->addParams('--output', 'gazetteer');
|
||||
$oCmd->addParams('--hstore');
|
||||
$oCmd->addParams('--number-processes', 1);
|
||||
$oCmd->addParams('--with-forward-dependencies', 'false');
|
||||
$oCmd->addParams('--log-progress', 'true');
|
||||
$oCmd->addParams('--cache', $this->iCacheMemory);
|
||||
$oCmd->addParams('--port', $this->aDSNInfo['port']);
|
||||
|
||||
if (isset($this->aDSNInfo['username'])) {
|
||||
$oCmd->addParams('--username', $this->aDSNInfo['username']);
|
||||
}
|
||||
if (isset($this->aDSNInfo['password'])) {
|
||||
$oCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
|
||||
}
|
||||
if (isset($this->aDSNInfo['hostspec'])) {
|
||||
$oCmd->addParams('--host', $this->aDSNInfo['hostspec']);
|
||||
}
|
||||
$oCmd->addParams('--database', $this->aDSNInfo['database']);
|
||||
$oCmd->addParams($sOSMFile);
|
||||
$oCmd->run();
|
||||
|
||||
if (!$this->sIgnoreErrors && !$this->db()->getRow('select * from place limit 1')) {
|
||||
fail('No Data');
|
||||
}
|
||||
|
||||
if ($this->bDrop) {
|
||||
$this->dropTable('planet_osm_nodes');
|
||||
$this->removeFlatnodeFile();
|
||||
}
|
||||
}
|
||||
|
||||
public function createFunctions()
|
||||
{
|
||||
info('Create Functions');
|
||||
|
||||
// Try accessing the C module, so we know early if something is wrong
|
||||
checkModulePresence(); // raises exception on failure
|
||||
|
||||
$this->createSqlFunctions();
|
||||
}
|
||||
|
||||
public function createTables($bReverseOnly = false)
|
||||
{
|
||||
info('Create Tables');
|
||||
|
||||
$sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
|
||||
$sTemplate = $this->replaceSqlPatterns($sTemplate);
|
||||
|
||||
$this->pgsqlRunScript($sTemplate, false);
|
||||
|
||||
if ($bReverseOnly) {
|
||||
$this->dropTable('search_name');
|
||||
}
|
||||
|
||||
$oAlParser = new AddressLevelParser(CONST_Address_Level_Config);
|
||||
$oAlParser->createTable($this->db(), 'address_levels');
|
||||
}
|
||||
|
||||
public function createTableTriggers()
|
||||
{
|
||||
info('Create Tables');
|
||||
|
||||
$sTemplate = file_get_contents(CONST_BasePath.'/sql/table-triggers.sql');
|
||||
$sTemplate = $this->replaceSqlPatterns($sTemplate);
|
||||
|
||||
$this->pgsqlRunScript($sTemplate, false);
|
||||
}
|
||||
|
||||
public function createPartitionTables()
|
||||
{
|
||||
info('Create Partition Tables');
|
||||
|
||||
$sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
|
||||
$sTemplate = $this->replaceSqlPatterns($sTemplate);
|
||||
|
||||
$this->pgsqlRunPartitionScript($sTemplate);
|
||||
}
|
||||
|
||||
public function createPartitionFunctions()
|
||||
{
|
||||
info('Create Partition Functions');
|
||||
|
||||
$sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
|
||||
$this->pgsqlRunPartitionScript($sTemplate);
|
||||
}
|
||||
|
||||
public function importWikipediaArticles()
|
||||
{
|
||||
$sWikiArticlesFile = CONST_Wikipedia_Data_Path.'/wikimedia-importance.sql.gz';
|
||||
if (file_exists($sWikiArticlesFile)) {
|
||||
info('Importing wikipedia articles and redirects');
|
||||
$this->dropTable('wikipedia_article');
|
||||
$this->dropTable('wikipedia_redirect');
|
||||
$this->pgsqlRunScriptFile($sWikiArticlesFile);
|
||||
} else {
|
||||
warn('wikipedia importance dump file not found - places will have default importance');
|
||||
}
|
||||
}
|
||||
|
||||
public function loadData($bDisableTokenPrecalc)
|
||||
{
|
||||
info('Drop old Data');
|
||||
|
||||
$oDB = $this->db();
|
||||
|
||||
$oDB->exec('TRUNCATE word');
|
||||
echo '.';
|
||||
$oDB->exec('TRUNCATE placex');
|
||||
echo '.';
|
||||
$oDB->exec('TRUNCATE location_property_osmline');
|
||||
echo '.';
|
||||
$oDB->exec('TRUNCATE place_addressline');
|
||||
echo '.';
|
||||
$oDB->exec('TRUNCATE location_area');
|
||||
echo '.';
|
||||
if (!$this->dbReverseOnly()) {
|
||||
$oDB->exec('TRUNCATE search_name');
|
||||
echo '.';
|
||||
}
|
||||
$oDB->exec('TRUNCATE search_name_blank');
|
||||
echo '.';
|
||||
$oDB->exec('DROP SEQUENCE seq_place');
|
||||
echo '.';
|
||||
$oDB->exec('CREATE SEQUENCE seq_place start 100000');
|
||||
echo '.';
|
||||
|
||||
$sSQL = 'select distinct partition from country_name';
|
||||
$aPartitions = $oDB->getCol($sSQL);
|
||||
|
||||
if (!$this->bNoPartitions) $aPartitions[] = 0;
|
||||
foreach ($aPartitions as $sPartition) {
|
||||
$oDB->exec('TRUNCATE location_road_'.$sPartition);
|
||||
echo '.';
|
||||
}
|
||||
|
||||
// used by getorcreate_word_id to ignore frequent partial words
|
||||
$sSQL = 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS ';
|
||||
$sSQL .= '$$ SELECT '.CONST_Max_Word_Frequency.' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE';
|
||||
$oDB->exec($sSQL);
|
||||
echo ".\n";
|
||||
|
||||
// pre-create the word list
|
||||
if (!$bDisableTokenPrecalc) {
|
||||
info('Loading word list');
|
||||
$this->pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
|
||||
}
|
||||
|
||||
info('Load Data');
|
||||
$sColumns = 'osm_type, osm_id, class, type, name, admin_level, address, extratags, geometry';
|
||||
|
||||
$aDBInstances = array();
|
||||
$iLoadThreads = max(1, $this->iInstances - 1);
|
||||
for ($i = 0; $i < $iLoadThreads; $i++) {
|
||||
// https://secure.php.net/manual/en/function.pg-connect.php
|
||||
$DSN = CONST_Database_DSN;
|
||||
$DSN = preg_replace('/^pgsql:/', '', $DSN);
|
||||
$DSN = preg_replace('/;/', ' ', $DSN);
|
||||
$aDBInstances[$i] = pg_connect($DSN, PGSQL_CONNECT_FORCE_NEW);
|
||||
pg_ping($aDBInstances[$i]);
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $iLoadThreads; $i++) {
|
||||
$sSQL = "INSERT INTO placex ($sColumns) SELECT $sColumns FROM place WHERE osm_id % $iLoadThreads = $i";
|
||||
$sSQL .= " and not (class='place' and type='houses' and osm_type='W'";
|
||||
$sSQL .= " and ST_GeometryType(geometry) = 'ST_LineString')";
|
||||
$sSQL .= ' and ST_IsValid(geometry)';
|
||||
if ($this->bVerbose) echo "$sSQL\n";
|
||||
if (!pg_send_query($aDBInstances[$i], $sSQL)) {
|
||||
fail(pg_last_error($aDBInstances[$i]));
|
||||
}
|
||||
}
|
||||
|
||||
// last thread for interpolation lines
|
||||
// https://secure.php.net/manual/en/function.pg-connect.php
|
||||
$DSN = CONST_Database_DSN;
|
||||
$DSN = preg_replace('/^pgsql:/', '', $DSN);
|
||||
$DSN = preg_replace('/;/', ' ', $DSN);
|
||||
$aDBInstances[$iLoadThreads] = pg_connect($DSN, PGSQL_CONNECT_FORCE_NEW);
|
||||
pg_ping($aDBInstances[$iLoadThreads]);
|
||||
$sSQL = 'insert into location_property_osmline';
|
||||
$sSQL .= ' (osm_id, address, linegeo)';
|
||||
$sSQL .= ' SELECT osm_id, address, geometry from place where ';
|
||||
$sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
|
||||
if ($this->bVerbose) echo "$sSQL\n";
|
||||
if (!pg_send_query($aDBInstances[$iLoadThreads], $sSQL)) {
|
||||
fail(pg_last_error($aDBInstances[$iLoadThreads]));
|
||||
}
|
||||
|
||||
$bFailed = false;
|
||||
for ($i = 0; $i <= $iLoadThreads; $i++) {
|
||||
while (($hPGresult = pg_get_result($aDBInstances[$i])) !== false) {
|
||||
$resultStatus = pg_result_status($hPGresult);
|
||||
// PGSQL_EMPTY_QUERY, PGSQL_COMMAND_OK, PGSQL_TUPLES_OK,
|
||||
// PGSQL_COPY_OUT, PGSQL_COPY_IN, PGSQL_BAD_RESPONSE,
|
||||
// PGSQL_NONFATAL_ERROR and PGSQL_FATAL_ERROR
|
||||
// echo 'Query result ' . $i . ' is: ' . $resultStatus . "\n";
|
||||
if ($resultStatus != PGSQL_COMMAND_OK && $resultStatus != PGSQL_TUPLES_OK) {
|
||||
$resultError = pg_result_error($hPGresult);
|
||||
echo '-- error text ' . $i . ': ' . $resultError . "\n";
|
||||
$bFailed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($bFailed) {
|
||||
fail('SQL errors loading placex and/or location_property_osmline tables');
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $this->iInstances; $i++) {
|
||||
pg_close($aDBInstances[$i]);
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
info('Reanalysing database');
|
||||
$this->pgsqlRunScript('ANALYSE');
|
||||
|
||||
$sDatabaseDate = getDatabaseDate($oDB);
|
||||
$oDB->exec('TRUNCATE import_status');
|
||||
if (!$sDatabaseDate) {
|
||||
warn('could not determine database date.');
|
||||
} else {
|
||||
$sSQL = "INSERT INTO import_status (lastimportdate) VALUES('".$sDatabaseDate."')";
|
||||
$oDB->exec($sSQL);
|
||||
echo "Latest data imported from $sDatabaseDate.\n";
|
||||
}
|
||||
}
|
||||
|
||||
public function importTigerData()
|
||||
{
|
||||
info('Import Tiger data');
|
||||
|
||||
$aFilenames = glob(CONST_Tiger_Data_Path.'/*.sql');
|
||||
info('Found '.count($aFilenames).' SQL files in path '.CONST_Tiger_Data_Path);
|
||||
if (empty($aFilenames)) {
|
||||
warn('Tiger data import selected but no files found in path '.CONST_Tiger_Data_Path);
|
||||
return;
|
||||
}
|
||||
$sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
|
||||
$sTemplate = $this->replaceSqlPatterns($sTemplate);
|
||||
|
||||
$this->pgsqlRunScript($sTemplate, false);
|
||||
|
||||
$aDBInstances = array();
|
||||
for ($i = 0; $i < $this->iInstances; $i++) {
|
||||
// https://secure.php.net/manual/en/function.pg-connect.php
|
||||
$DSN = CONST_Database_DSN;
|
||||
$DSN = preg_replace('/^pgsql:/', '', $DSN);
|
||||
$DSN = preg_replace('/;/', ' ', $DSN);
|
||||
$aDBInstances[$i] = pg_connect($DSN, PGSQL_CONNECT_FORCE_NEW | PGSQL_CONNECT_ASYNC);
|
||||
pg_ping($aDBInstances[$i]);
|
||||
}
|
||||
|
||||
foreach ($aFilenames as $sFile) {
|
||||
echo $sFile.': ';
|
||||
$hFile = fopen($sFile, 'r');
|
||||
$sSQL = fgets($hFile, 100000);
|
||||
$iLines = 0;
|
||||
while (true) {
|
||||
for ($i = 0; $i < $this->iInstances; $i++) {
|
||||
if (!pg_connection_busy($aDBInstances[$i])) {
|
||||
while (pg_get_result($aDBInstances[$i]));
|
||||
$sSQL = fgets($hFile, 100000);
|
||||
if (!$sSQL) break 2;
|
||||
if (!pg_send_query($aDBInstances[$i], $sSQL)) fail(pg_last_error($aDBInstances[$i]));
|
||||
$iLines++;
|
||||
if ($iLines == 1000) {
|
||||
echo '.';
|
||||
$iLines = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
usleep(10);
|
||||
}
|
||||
fclose($hFile);
|
||||
|
||||
$bAnyBusy = true;
|
||||
while ($bAnyBusy) {
|
||||
$bAnyBusy = false;
|
||||
for ($i = 0; $i < $this->iInstances; $i++) {
|
||||
if (pg_connection_busy($aDBInstances[$i])) $bAnyBusy = true;
|
||||
}
|
||||
usleep(10);
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $this->iInstances; $i++) {
|
||||
pg_close($aDBInstances[$i]);
|
||||
}
|
||||
|
||||
info('Creating indexes on Tiger data');
|
||||
$sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
|
||||
$sTemplate = $this->replaceSqlPatterns($sTemplate);
|
||||
|
||||
$this->pgsqlRunScript($sTemplate, false);
|
||||
}
|
||||
|
||||
public function calculatePostcodes($bCMDResultAll)
|
||||
{
|
||||
info('Calculate Postcodes');
|
||||
$this->db()->exec('TRUNCATE location_postcode');
|
||||
|
||||
$sSQL = 'INSERT INTO location_postcode';
|
||||
$sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
|
||||
$sSQL .= "SELECT nextval('seq_place'), 1, country_code,";
|
||||
$sSQL .= " upper(trim (both ' ' from address->'postcode')) as pc,";
|
||||
$sSQL .= ' ST_Centroid(ST_Collect(ST_Centroid(geometry)))';
|
||||
$sSQL .= ' FROM placex';
|
||||
$sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";
|
||||
$sSQL .= ' AND geometry IS NOT null';
|
||||
$sSQL .= ' GROUP BY country_code, pc';
|
||||
$this->db()->exec($sSQL);
|
||||
|
||||
// only add postcodes that are not yet available in OSM
|
||||
$sSQL = 'INSERT INTO location_postcode';
|
||||
$sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
|
||||
$sSQL .= "SELECT nextval('seq_place'), 1, 'us', postcode,";
|
||||
$sSQL .= ' ST_SetSRID(ST_Point(x,y),4326)';
|
||||
$sSQL .= ' FROM us_postcode WHERE postcode NOT IN';
|
||||
$sSQL .= ' (SELECT postcode FROM location_postcode';
|
||||
$sSQL .= " WHERE country_code = 'us')";
|
||||
$this->db()->exec($sSQL);
|
||||
|
||||
// add missing postcodes for GB (if available)
|
||||
$sSQL = 'INSERT INTO location_postcode';
|
||||
$sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
|
||||
$sSQL .= "SELECT nextval('seq_place'), 1, 'gb', postcode, geometry";
|
||||
$sSQL .= ' FROM gb_postcode WHERE postcode NOT IN';
|
||||
$sSQL .= ' (SELECT postcode FROM location_postcode';
|
||||
$sSQL .= " WHERE country_code = 'gb')";
|
||||
$this->db()->exec($sSQL);
|
||||
|
||||
if (!$bCMDResultAll) {
|
||||
$sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
|
||||
$sSQL .= 'and word NOT IN (SELECT postcode FROM location_postcode)';
|
||||
$this->db()->exec($sSQL);
|
||||
}
|
||||
|
||||
$sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
|
||||
$sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
|
||||
$this->db()->exec($sSQL);
|
||||
}
|
||||
|
||||
public function index($bIndexNoanalyse)
|
||||
{
|
||||
checkModulePresence(); // raises exception on failure
|
||||
|
||||
$oBaseCmd = (new \Nominatim\Shell(CONST_BasePath.'/nominatim/nominatim.py'))
|
||||
->addParams('--database', $this->aDSNInfo['database'])
|
||||
->addParams('--port', $this->aDSNInfo['port'])
|
||||
->addParams('--threads', $this->iInstances);
|
||||
|
||||
if (!$this->bQuiet) {
|
||||
$oBaseCmd->addParams('-v');
|
||||
}
|
||||
if ($this->bVerbose) {
|
||||
$oBaseCmd->addParams('-v');
|
||||
}
|
||||
if (isset($this->aDSNInfo['hostspec'])) {
|
||||
$oBaseCmd->addParams('--host', $this->aDSNInfo['hostspec']);
|
||||
}
|
||||
if (isset($this->aDSNInfo['username'])) {
|
||||
$oBaseCmd->addParams('--user', $this->aDSNInfo['username']);
|
||||
}
|
||||
if (isset($this->aDSNInfo['password'])) {
|
||||
$oBaseCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
|
||||
}
|
||||
|
||||
info('Index ranks 0 - 4');
|
||||
$oCmd = (clone $oBaseCmd)->addParams('--maxrank', 4);
|
||||
echo $oCmd->escapedCmd();
|
||||
|
||||
$iStatus = $oCmd->run();
|
||||
if ($iStatus != 0) {
|
||||
fail('error status ' . $iStatus . ' running nominatim!');
|
||||
}
|
||||
if (!$bIndexNoanalyse) $this->pgsqlRunScript('ANALYSE');
|
||||
|
||||
info('Index administrative boundaries');
|
||||
$oCmd = (clone $oBaseCmd)->addParams('-b');
|
||||
$iStatus = $oCmd->run();
|
||||
if ($iStatus != 0) {
|
||||
fail('error status ' . $iStatus . ' running nominatim!');
|
||||
}
|
||||
|
||||
info('Index ranks 5 - 25');
|
||||
$oCmd = (clone $oBaseCmd)->addParams('--minrank', 5, '--maxrank', 25);
|
||||
$iStatus = $oCmd->run();
|
||||
if ($iStatus != 0) {
|
||||
fail('error status ' . $iStatus . ' running nominatim!');
|
||||
}
|
||||
|
||||
if (!$bIndexNoanalyse) $this->pgsqlRunScript('ANALYSE');
|
||||
|
||||
info('Index ranks 26 - 30');
|
||||
$oCmd = (clone $oBaseCmd)->addParams('--minrank', 26);
|
||||
$iStatus = $oCmd->run();
|
||||
if ($iStatus != 0) {
|
||||
fail('error status ' . $iStatus . ' running nominatim!');
|
||||
}
|
||||
|
||||
info('Index postcodes');
|
||||
$sSQL = 'UPDATE location_postcode SET indexed_status = 0';
|
||||
$this->db()->exec($sSQL);
|
||||
}
|
||||
|
||||
public function createSearchIndices()
|
||||
{
|
||||
info('Create Search indices');
|
||||
|
||||
$sSQL = 'SELECT relname FROM pg_class, pg_index ';
|
||||
$sSQL .= 'WHERE pg_index.indisvalid = false AND pg_index.indexrelid = pg_class.oid';
|
||||
$aInvalidIndices = $this->db()->getCol($sSQL);
|
||||
|
||||
foreach ($aInvalidIndices as $sIndexName) {
|
||||
info("Cleaning up invalid index $sIndexName");
|
||||
$this->db()->exec("DROP INDEX $sIndexName;");
|
||||
}
|
||||
|
||||
$sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
|
||||
if (!$this->bDrop) {
|
||||
$sTemplate .= file_get_contents(CONST_BasePath.'/sql/indices_updates.src.sql');
|
||||
}
|
||||
if (!$this->dbReverseOnly()) {
|
||||
$sTemplate .= file_get_contents(CONST_BasePath.'/sql/indices_search.src.sql');
|
||||
}
|
||||
$sTemplate = $this->replaceSqlPatterns($sTemplate);
|
||||
|
||||
$this->pgsqlRunScript($sTemplate);
|
||||
}
|
||||
|
||||
public function createCountryNames()
|
||||
{
|
||||
info('Create search index for default country names');
|
||||
|
||||
$this->pgsqlRunScript("select getorcreate_country(make_standard_name('uk'), 'gb')");
|
||||
$this->pgsqlRunScript("select getorcreate_country(make_standard_name('united states'), 'us')");
|
||||
$this->pgsqlRunScript('select count(*) from (select getorcreate_country(make_standard_name(country_code), country_code) from country_name where country_code is not null) as x');
|
||||
$this->pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(name->'name'), country_code) from country_name where name ? 'name') as x");
|
||||
$sSQL = 'select count(*) from (select getorcreate_country(make_standard_name(v),'
|
||||
.'country_code) from (select country_code, skeys(name) as k, svals(name) as v from country_name) x where k ';
|
||||
if (CONST_Languages) {
|
||||
$sSQL .= 'in ';
|
||||
$sDelim = '(';
|
||||
foreach (explode(',', CONST_Languages) as $sLang) {
|
||||
$sSQL .= $sDelim."'name:$sLang'";
|
||||
$sDelim = ',';
|
||||
}
|
||||
$sSQL .= ')';
|
||||
} else {
|
||||
// all include all simple name tags
|
||||
$sSQL .= "like 'name:%'";
|
||||
}
|
||||
$sSQL .= ') v';
|
||||
$this->pgsqlRunScript($sSQL);
|
||||
}
|
||||
|
||||
public function drop()
|
||||
{
|
||||
info('Drop tables only required for updates');
|
||||
|
||||
// The implementation is potentially a bit dangerous because it uses
|
||||
// a positive selection of tables to keep, and deletes everything else.
|
||||
// Including any tables that the unsuspecting user might have manually
|
||||
// created. USE AT YOUR OWN PERIL.
|
||||
// tables we want to keep. everything else goes.
|
||||
$aKeepTables = array(
|
||||
'*columns',
|
||||
'import_polygon_*',
|
||||
'import_status',
|
||||
'place_addressline',
|
||||
'location_postcode',
|
||||
'location_property*',
|
||||
'placex',
|
||||
'search_name',
|
||||
'seq_*',
|
||||
'word',
|
||||
'query_log',
|
||||
'new_query_log',
|
||||
'spatial_ref_sys',
|
||||
'country_name',
|
||||
'place_classtype_*',
|
||||
'country_osm_grid'
|
||||
);
|
||||
|
||||
$aDropTables = array();
|
||||
$aHaveTables = $this->db()->getListOfTables();
|
||||
|
||||
foreach ($aHaveTables as $sTable) {
|
||||
$bFound = false;
|
||||
foreach ($aKeepTables as $sKeep) {
|
||||
if (fnmatch($sKeep, $sTable)) {
|
||||
$bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$bFound) array_push($aDropTables, $sTable);
|
||||
}
|
||||
foreach ($aDropTables as $sDrop) {
|
||||
$this->dropTable($sDrop);
|
||||
}
|
||||
|
||||
$this->removeFlatnodeFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup settings-frontend.php in the build/website directory
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function setupWebsite()
|
||||
{
|
||||
$rOutputFile = fopen(CONST_InstallPath.'/settings/settings-frontend.php', 'w');
|
||||
|
||||
fwrite($rOutputFile, "<?php
|
||||
@define('CONST_BasePath', '".CONST_BasePath."');
|
||||
if (file_exists(getenv('NOMINATIM_SETTINGS'))) require_once(getenv('NOMINATIM_SETTINGS'));
|
||||
|
||||
@define('CONST_Database_DSN', '".CONST_Database_DSN."');
|
||||
@define('CONST_Default_Language', ".(CONST_Default_Language ? ("'".CONST_Default_Language."'") : 'false').");
|
||||
@define('CONST_Log_DB', ".(CONST_Log_DB ? 'true' : 'false').");
|
||||
@define('CONST_Log_File', ".(CONST_Log_File ? ("'".CONST_Log_File."'") : 'false').");
|
||||
@define('CONST_Max_Word_Frequency', '".CONST_Max_Word_Frequency."');
|
||||
@define('CONST_NoAccessControl', ".CONST_NoAccessControl.");
|
||||
@define('CONST_Places_Max_ID_count', ".CONST_Places_Max_ID_count.");
|
||||
@define('CONST_PolygonOutput_MaximumTypes', ".CONST_PolygonOutput_MaximumTypes.");
|
||||
@define('CONST_Search_AreaPolygons', ".CONST_Search_AreaPolygons.");
|
||||
@define('CONST_Search_BatchMode', ".(CONST_Search_BatchMode ? 'true' : 'false').");
|
||||
@define('CONST_Search_NameOnlySearchFrequencyThreshold', ".CONST_Search_NameOnlySearchFrequencyThreshold.");
|
||||
@define('CONST_Search_ReversePlanForAll', ".CONST_Search_ReversePlanForAll.");
|
||||
@define('CONST_Term_Normalization_Rules', \"".CONST_Term_Normalization_Rules."\");
|
||||
@define('CONST_Use_Aux_Location_data', ".(CONST_Use_Aux_Location_data ? 'true' : 'false').");
|
||||
@define('CONST_Use_US_Tiger_Data', ".(CONST_Use_US_Tiger_Data ? 'true' : 'false').");
|
||||
@define('CONST_MapIcon_URL', ".(CONST_MapIcon_URL ? ("'".CONST_MapIcon_URL."'") : 'false').');
|
||||
');
|
||||
info(CONST_InstallPath.'/settings/settings-frontend.php has been set up successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the connection to the database.
|
||||
*
|
||||
* @return Database object.
|
||||
*
|
||||
* Creates a new connection if none exists yet. Otherwise reuses the
|
||||
* already established connection.
|
||||
*/
|
||||
private function db()
|
||||
{
|
||||
if (is_null($this->oDB)) {
|
||||
$this->oDB = new \Nominatim\DB();
|
||||
$this->oDB->connect();
|
||||
}
|
||||
|
||||
return $this->oDB;
|
||||
}
|
||||
|
||||
private function removeFlatnodeFile()
|
||||
{
|
||||
if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
|
||||
if (file_exists(CONST_Osm2pgsql_Flatnode_File)) {
|
||||
if ($this->bVerbose) echo 'Deleting '.CONST_Osm2pgsql_Flatnode_File."\n";
|
||||
unlink(CONST_Osm2pgsql_Flatnode_File);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function pgsqlRunScript($sScript, $bfatal = true)
|
||||
{
|
||||
runSQLScript(
|
||||
$sScript,
|
||||
$bfatal,
|
||||
$this->bVerbose,
|
||||
$this->sIgnoreErrors
|
||||
);
|
||||
}
|
||||
|
||||
private function createSqlFunctions()
|
||||
{
|
||||
$sBasePath = CONST_BasePath.'/sql/functions/';
|
||||
$sTemplate = file_get_contents($sBasePath.'utils.sql');
|
||||
$sTemplate .= file_get_contents($sBasePath.'normalization.sql');
|
||||
$sTemplate .= file_get_contents($sBasePath.'ranking.sql');
|
||||
$sTemplate .= file_get_contents($sBasePath.'importance.sql');
|
||||
$sTemplate .= file_get_contents($sBasePath.'address_lookup.sql');
|
||||
$sTemplate .= file_get_contents($sBasePath.'interpolation.sql');
|
||||
if ($this->db()->tableExists('place')) {
|
||||
$sTemplate .= file_get_contents($sBasePath.'place_triggers.sql');
|
||||
}
|
||||
if ($this->db()->tableExists('placex')) {
|
||||
$sTemplate .= file_get_contents($sBasePath.'placex_triggers.sql');
|
||||
}
|
||||
if ($this->db()->tableExists('location_postcode')) {
|
||||
$sTemplate .= file_get_contents($sBasePath.'postcode_triggers.sql');
|
||||
}
|
||||
$sTemplate = str_replace('{modulepath}', $this->sModulePath, $sTemplate);
|
||||
if ($this->bEnableDiffUpdates) {
|
||||
$sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
|
||||
}
|
||||
if ($this->bEnableDebugStatements) {
|
||||
$sTemplate = str_replace('--DEBUG:', '', $sTemplate);
|
||||
}
|
||||
if (CONST_Limit_Reindexing) {
|
||||
$sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
|
||||
}
|
||||
if (!CONST_Use_US_Tiger_Data) {
|
||||
$sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
|
||||
}
|
||||
if (!CONST_Use_Aux_Location_data) {
|
||||
$sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
|
||||
}
|
||||
|
||||
$sReverseOnly = $this->dbReverseOnly() ? 'true' : 'false';
|
||||
$sTemplate = str_replace('%REVERSE-ONLY%', $sReverseOnly, $sTemplate);
|
||||
|
||||
$this->pgsqlRunScript($sTemplate);
|
||||
}
|
||||
|
||||
private function pgsqlRunPartitionScript($sTemplate)
|
||||
{
|
||||
$sSQL = 'select distinct partition from country_name';
|
||||
$aPartitions = $this->db()->getCol($sSQL);
|
||||
if (!$this->bNoPartitions) $aPartitions[] = 0;
|
||||
|
||||
preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
|
||||
foreach ($aMatches as $aMatch) {
|
||||
$sResult = '';
|
||||
foreach ($aPartitions as $sPartitionName) {
|
||||
$sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
|
||||
}
|
||||
$sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
|
||||
}
|
||||
|
||||
$this->pgsqlRunScript($sTemplate);
|
||||
}
|
||||
|
||||
private function pgsqlRunScriptFile($sFilename)
|
||||
{
|
||||
if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
|
||||
|
||||
$oCmd = (new \Nominatim\Shell('psql'))
|
||||
->addParams('--port', $this->aDSNInfo['port'])
|
||||
->addParams('--dbname', $this->aDSNInfo['database']);
|
||||
|
||||
if (!$this->bVerbose) {
|
||||
$oCmd->addParams('--quiet');
|
||||
}
|
||||
if (isset($this->aDSNInfo['hostspec'])) {
|
||||
$oCmd->addParams('--host', $this->aDSNInfo['hostspec']);
|
||||
}
|
||||
if (isset($this->aDSNInfo['username'])) {
|
||||
$oCmd->addParams('--username', $this->aDSNInfo['username']);
|
||||
}
|
||||
if (isset($this->aDSNInfo['password'])) {
|
||||
$oCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
|
||||
}
|
||||
$ahGzipPipes = null;
|
||||
if (preg_match('/\\.gz$/', $sFilename)) {
|
||||
$aDescriptors = array(
|
||||
0 => array('pipe', 'r'),
|
||||
1 => array('pipe', 'w'),
|
||||
2 => array('file', '/dev/null', 'a')
|
||||
);
|
||||
$oZcatCmd = new \Nominatim\Shell('zcat', $sFilename);
|
||||
|
||||
$hGzipProcess = proc_open($oZcatCmd->escapedCmd(), $aDescriptors, $ahGzipPipes);
|
||||
if (!is_resource($hGzipProcess)) fail('unable to start zcat');
|
||||
$aReadPipe = $ahGzipPipes[1];
|
||||
fclose($ahGzipPipes[0]);
|
||||
} else {
|
||||
$oCmd->addParams('--file', $sFilename);
|
||||
$aReadPipe = array('pipe', 'r');
|
||||
}
|
||||
$aDescriptors = array(
|
||||
0 => $aReadPipe,
|
||||
1 => array('pipe', 'w'),
|
||||
2 => array('file', '/dev/null', 'a')
|
||||
);
|
||||
$ahPipes = null;
|
||||
|
||||
$hProcess = proc_open($oCmd->escapedCmd(), $aDescriptors, $ahPipes, null, $oCmd->aEnv);
|
||||
if (!is_resource($hProcess)) fail('unable to start pgsql');
|
||||
// TODO: error checking
|
||||
while (!feof($ahPipes[1])) {
|
||||
echo fread($ahPipes[1], 4096);
|
||||
}
|
||||
fclose($ahPipes[1]);
|
||||
$iReturn = proc_close($hProcess);
|
||||
if ($iReturn > 0) {
|
||||
fail("pgsql returned with error code ($iReturn)");
|
||||
}
|
||||
if ($ahGzipPipes) {
|
||||
fclose($ahGzipPipes[1]);
|
||||
proc_close($hGzipProcess);
|
||||
}
|
||||
}
|
||||
|
||||
private function replaceSqlPatterns($sSql)
|
||||
{
|
||||
$sSql = str_replace('{www-user}', CONST_Database_Web_User, $sSql);
|
||||
|
||||
$aPatterns = array(
|
||||
'{ts:address-data}' => CONST_Tablespace_Address_Data,
|
||||
'{ts:address-index}' => CONST_Tablespace_Address_Index,
|
||||
'{ts:search-data}' => CONST_Tablespace_Search_Data,
|
||||
'{ts:search-index}' => CONST_Tablespace_Search_Index,
|
||||
'{ts:aux-data}' => CONST_Tablespace_Aux_Data,
|
||||
'{ts:aux-index}' => CONST_Tablespace_Aux_Index,
|
||||
);
|
||||
|
||||
foreach ($aPatterns as $sPattern => $sTablespace) {
|
||||
if ($sTablespace) {
|
||||
$sSql = str_replace($sPattern, 'TABLESPACE "'.$sTablespace.'"', $sSql);
|
||||
} else {
|
||||
$sSql = str_replace($sPattern, '', $sSql);
|
||||
}
|
||||
}
|
||||
|
||||
return $sSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop table with the given name if it exists.
|
||||
*
|
||||
* @param string $sName Name of table to remove.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
private function dropTable($sName)
|
||||
{
|
||||
if ($this->bVerbose) echo "Dropping table $sName\n";
|
||||
$this->db()->deleteTable($sName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the database is in reverse-only mode.
|
||||
*
|
||||
* @return True if there is no search_name table and infrastructure.
|
||||
*/
|
||||
private function dbReverseOnly()
|
||||
{
|
||||
return !($this->db()->tableExists('search_name'));
|
||||
}
|
||||
}
|
||||
31
lib/setup_functions.php
Executable file
31
lib/setup_functions.php
Executable file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
function checkInFile($sOSMFile)
|
||||
{
|
||||
if (!isset($sOSMFile)) {
|
||||
fail('missing --osm-file for data import');
|
||||
}
|
||||
|
||||
if (!file_exists($sOSMFile)) {
|
||||
fail('the path supplied to --osm-file does not exist');
|
||||
}
|
||||
|
||||
if (!is_readable($sOSMFile)) {
|
||||
fail('osm-file "' . $aCMDResult['osm-file'] . '" not readable');
|
||||
}
|
||||
}
|
||||
|
||||
function checkModulePresence()
|
||||
{
|
||||
// Try accessing the C module, so we know early if something is wrong.
|
||||
// Raises Nominatim\DatabaseError on failure
|
||||
|
||||
$sModulePath = CONST_Database_Module_Path;
|
||||
$sSQL = "CREATE FUNCTION nominatim_test_import_func(text) RETURNS text AS '";
|
||||
$sSQL .= $sModulePath . "/nominatim.so', 'transliteration' LANGUAGE c IMMUTABLE STRICT";
|
||||
$sSQL .= ';DROP FUNCTION nominatim_test_import_func(text);';
|
||||
|
||||
$oDB = new \Nominatim\DB();
|
||||
$oDB->connect();
|
||||
$oDB->exec($sSQL, null, 'Database server failed to load '.$sModulePath.'/nominatim.so module');
|
||||
}
|
||||
68
lib/template/address-geocodejson.php
Normal file
68
lib/template/address-geocodejson.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
// 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']['type'] = $aPlace['type'];
|
||||
|
||||
$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']);
|
||||
} 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)
|
||||
));
|
||||
}
|
||||
69
lib/template/address-geojson.php
Normal file
69
lib/template/address-geojson.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
$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']);
|
||||
} 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,133 +0,0 @@
|
||||
<?php
|
||||
header("content-type: text/html; charset=UTF-8");
|
||||
?>
|
||||
<?php include(CONST_BasePath.'/lib/template/includes/html-header.php'); ?>
|
||||
<link href="css/common.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/search.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
|
||||
<body id="reverse-page">
|
||||
|
||||
<?php include(CONST_BasePath.'/lib/template/includes/html-top-navigation.php'); ?>
|
||||
|
||||
<form class="form-inline" role="search" accept-charset="UTF-8" action="<?php echo CONST_Website_BaseURL; ?>reverse.php">
|
||||
<div class="form-group">
|
||||
<input name="format" type="hidden" value="html">
|
||||
<input name="lat" type="text" class="form-control input-sm" placeholder="latitude" value="<?php echo $fLat; ?>" >
|
||||
<span id="switch-coords"><></span>
|
||||
<input name="lon" type="text" class="form-control input-sm" placeholder="longitude" value="<?php echo $fLon; ?>" >
|
||||
max zoom
|
||||
|
||||
<select name="zoom" class="form-control input-sm">
|
||||
<option value="" <?php if ($iZoom === false) echo 'selected="selected"' ?> >--</option>
|
||||
<?php
|
||||
|
||||
$aZoomLevels = array(
|
||||
0 => "Continent / Sea",
|
||||
1 => "",
|
||||
2 => "",
|
||||
3 => "Country",
|
||||
4 => "",
|
||||
5 => "State",
|
||||
6 => "Region",
|
||||
7 => "",
|
||||
8 => "County",
|
||||
9 => "",
|
||||
10 => "City",
|
||||
11 => "",
|
||||
12 => "Town / Village",
|
||||
13 => "",
|
||||
14 => "Suburb",
|
||||
15 => "",
|
||||
16 => "Street",
|
||||
17 => "",
|
||||
18 => "Building",
|
||||
19 => "",
|
||||
20 => "",
|
||||
21 => "",
|
||||
);
|
||||
|
||||
foreach($aZoomLevels as $iZoomLevel => $sLabel)
|
||||
{
|
||||
$bSel = $iZoom === $iZoomLevel;
|
||||
echo '<option value="'.$iZoomLevel.'"'.($bSel?' selected="selected"':'').'>'.$iZoomLevel.' '.$sLabel.'</option>'."\n";
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group search-button-group">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Search</button>
|
||||
</div>
|
||||
<div class="search-type-link">
|
||||
<a href="<?php echo CONST_Website_BaseURL; ?>search.php">forward search</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<div id="content">
|
||||
|
||||
<?php if (count($aPlace)>0) { ?>
|
||||
|
||||
<div id="searchresults" class="sidebar">
|
||||
<?php
|
||||
$aResult = $aPlace;
|
||||
|
||||
echo '<div class="result" data-position="0">';
|
||||
|
||||
echo (isset($aResult['icon'])?'<img alt="icon" src="'.$aResult['icon'].'"/>':'');
|
||||
echo ' <span class="name">'.htmlspecialchars($aResult['langaddress']).'</span>';
|
||||
if (isset($aResult['label']))
|
||||
echo ' <span class="type">('.$aResult['label'].')</span>';
|
||||
else if ($aResult['type'] == 'yes')
|
||||
echo ' <span class="type">('.ucwords(str_replace('_',' ',$aResult['class'])).')</span>';
|
||||
else
|
||||
echo ' <span class="type">('.ucwords(str_replace('_',' ',$aResult['type'])).')</span>';
|
||||
echo '<p>'.$aResult['lat'].','.$aResult['lon'].'</p>';
|
||||
echo ' <a class="btn btn-default btn-xs details" href="details.php?place_id='.$aResult['place_id'].'">details</a>';
|
||||
echo '</div>';
|
||||
?>
|
||||
</div>
|
||||
|
||||
<?php } else { ?>
|
||||
|
||||
<div id="intro" class="sidebar">
|
||||
Search for coordinates or click anywhere on the map.
|
||||
</div>
|
||||
|
||||
<?php } ?>
|
||||
|
||||
<div id="map-wrapper">
|
||||
<div id="map-position">
|
||||
<div id="map-position-inner"></div>
|
||||
<div id="map-position-close"><a href="#">hide</a></div>
|
||||
</div>
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
|
||||
</div> <!-- /content -->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
<?php
|
||||
|
||||
$aNominatimMapInit = array(
|
||||
'zoom' => $iZoom !== false ? $iZoom : CONST_Default_Zoom,
|
||||
'lat' => $fLat !== false ? $fLat : CONST_Default_Lat,
|
||||
'lon' => $fLon !== false ? $fLon : CONST_Default_Lon,
|
||||
'tile_url' => $sTileURL,
|
||||
'tile_attribution' => $sTileAttribution
|
||||
);
|
||||
echo 'var nominatim_map_init = ' . json_encode($aNominatimMapInit, JSON_PRETTY_PRINT) . ';';
|
||||
|
||||
echo 'var nominatim_results = ' . json_encode([$aPlace], JSON_PRETTY_PRINT) . ';';
|
||||
?>
|
||||
</script>
|
||||
<?php include(CONST_BasePath.'/lib/template/includes/html-footer.php'); ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,55 +2,61 @@
|
||||
|
||||
$aFilteredPlaces = array();
|
||||
|
||||
if (!sizeof($aPlace))
|
||||
{
|
||||
if (empty($aPlace)) {
|
||||
if (isset($sError))
|
||||
$aFilteredPlaces['error'] = $sError;
|
||||
else
|
||||
$aFilteredPlaces['error'] = 'Unable to geocode';
|
||||
}
|
||||
else
|
||||
{
|
||||
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. http://www.openstreetmap.org/copyright";
|
||||
$aFilteredPlaces['licence'] = 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright';
|
||||
$sOSMType = formatOSMType($aPlace['osm_type']);
|
||||
if ($sOSMType)
|
||||
{
|
||||
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['aAddress'])) $aFilteredPlaces['address'] = $aPlace['aAddress'];
|
||||
|
||||
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']))
|
||||
{
|
||||
if (isset($aPlace['aBoundingBox'])) {
|
||||
$aFilteredPlaces['boundingbox'] = $aPlace['aBoundingBox'];
|
||||
}
|
||||
|
||||
if (isset($aPlace['asgeojson']))
|
||||
{
|
||||
if (isset($aPlace['asgeojson'])) {
|
||||
$aFilteredPlaces['geojson'] = json_decode($aPlace['asgeojson']);
|
||||
}
|
||||
|
||||
if (isset($aPlace['assvg']))
|
||||
{
|
||||
if (isset($aPlace['assvg'])) {
|
||||
$aFilteredPlaces['svg'] = $aPlace['assvg'];
|
||||
}
|
||||
|
||||
if (isset($aPlace['astext']))
|
||||
{
|
||||
if (isset($aPlace['astext'])) {
|
||||
$aFilteredPlaces['geotext'] = $aPlace['astext'];
|
||||
}
|
||||
|
||||
if (isset($aPlace['askml']))
|
||||
{
|
||||
if (isset($aPlace['askml'])) {
|
||||
$aFilteredPlaces['geokml'] = $aPlace['askml'];
|
||||
}
|
||||
}
|
||||
|
||||
javascript_renderData($aFilteredPlaces);
|
||||
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
<?php
|
||||
|
||||
$aFilteredPlaces = array();
|
||||
|
||||
if (!sizeof($aPlace))
|
||||
{
|
||||
if (isset($sError))
|
||||
$aFilteredPlaces['error'] = $sError;
|
||||
else
|
||||
$aFilteredPlaces['error'] = 'Unable to geocode';
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($aPlace['place_id']) $aFilteredPlaces['place_id'] = $aPlace['place_id'];
|
||||
$aFilteredPlaces['licence'] = "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.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'];
|
||||
|
||||
$aFilteredPlaces['place_rank'] = $aPlace['rank_search'];
|
||||
|
||||
$aFilteredPlaces['category'] = $aPlace['class'];
|
||||
$aFilteredPlaces['type'] = $aPlace['type'];
|
||||
|
||||
$aFilteredPlaces['importance'] = $aPlace['importance'];
|
||||
|
||||
$aFilteredPlaces['addresstype'] = strtolower($aPlace['addresstype']);
|
||||
|
||||
$aFilteredPlaces['display_name'] = $aPlace['langaddress'];
|
||||
$aFilteredPlaces['name'] = $aPlace['placename'];
|
||||
|
||||
if (isset($aPlace['aAddress'])) $aFilteredPlaces['address'] = $aPlace['aAddress'];
|
||||
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']);
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -1,103 +1,90 @@
|
||||
<?php
|
||||
header("content-type: text/xml; charset=UTF-8");
|
||||
header('content-type: text/xml; charset=UTF-8');
|
||||
|
||||
echo "<";
|
||||
echo "?xml version=\"1.0\" encoding=\"UTF-8\" ?";
|
||||
echo '<';
|
||||
echo '?xml version="1.0" encoding="UTF-8" ?';
|
||||
echo ">\n";
|
||||
|
||||
echo "<reversegeocode";
|
||||
echo '<reversegeocode';
|
||||
echo " timestamp='".date(DATE_RFC822)."'";
|
||||
echo " attribution='Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright'";
|
||||
echo " querystring='".htmlspecialchars($_SERVER['QUERY_STRING'], ENT_QUOTES)."'";
|
||||
echo ">\n";
|
||||
|
||||
if (!sizeof($aPlace))
|
||||
{
|
||||
if (empty($aPlace)) {
|
||||
if (isset($sError))
|
||||
echo "<error>$sError</error>";
|
||||
else
|
||||
echo "<error>Unable to geocode</error>";
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "<result";
|
||||
else echo '<error>Unable to geocode</error>';
|
||||
} else {
|
||||
echo '<result';
|
||||
if ($aPlace['place_id']) echo ' place_id="'.$aPlace['place_id'].'"';
|
||||
$sOSMType = formatOSMType($aPlace['osm_type']);
|
||||
if ($sOSMType) echo ' osm_type="'.$sOSMType.'"'.' osm_id="'.$aPlace['osm_id'].'"';
|
||||
if ($aPlace['ref']) echo ' ref="'.htmlspecialchars($aPlace['ref']).'"';
|
||||
if (isset($aPlace['lat'])) echo ' lat="'.htmlspecialchars($aPlace['lat']).'"';
|
||||
if (isset($aPlace['lon'])) echo ' lon="'.htmlspecialchars($aPlace['lon']).'"';
|
||||
if (isset($aPlace['aBoundingBox']))
|
||||
{
|
||||
if (isset($aPlace['aBoundingBox'])) {
|
||||
echo ' boundingbox="';
|
||||
echo join(',', $aPlace['aBoundingBox']);
|
||||
echo '"';
|
||||
}
|
||||
echo " place_rank='".$aPlace['rank_search']."'";
|
||||
echo " address_rank='".$aPlace['rank_address']."'";
|
||||
|
||||
if (isset($aPlace['asgeojson']))
|
||||
{
|
||||
|
||||
if (isset($aPlace['asgeojson'])) {
|
||||
echo ' geojson=\'';
|
||||
echo $aPlace['asgeojson'];
|
||||
echo '\'';
|
||||
}
|
||||
|
||||
if (isset($aPlace['assvg']))
|
||||
{
|
||||
if (isset($aPlace['assvg'])) {
|
||||
echo ' geosvg=\'';
|
||||
echo $aPlace['assvg'];
|
||||
echo '\'';
|
||||
}
|
||||
|
||||
if (isset($aPlace['astext']))
|
||||
{
|
||||
if (isset($aPlace['astext'])) {
|
||||
echo ' geotext=\'';
|
||||
echo $aPlace['astext'];
|
||||
echo '\'';
|
||||
}
|
||||
echo ">".htmlspecialchars($aPlace['langaddress'])."</result>";
|
||||
echo '>'.htmlspecialchars($aPlace['langaddress']).'</result>';
|
||||
|
||||
if (isset($aPlace['aAddress']))
|
||||
{
|
||||
echo "<addressparts>";
|
||||
foreach($aPlace['aAddress'] as $sKey => $sValue)
|
||||
{
|
||||
$sKey = str_replace(' ','_',$sKey);
|
||||
if (isset($aPlace['address'])) {
|
||||
echo '<addressparts>';
|
||||
foreach ($aPlace['address']->getAddressNames() as $sKey => $sValue) {
|
||||
$sKey = str_replace(' ', '_', $sKey);
|
||||
echo "<$sKey>";
|
||||
echo htmlspecialchars($sValue);
|
||||
echo "</$sKey>";
|
||||
}
|
||||
echo "</addressparts>";
|
||||
echo '</addressparts>';
|
||||
}
|
||||
|
||||
if (isset($aPlace['sExtraTags']))
|
||||
{
|
||||
echo "<extratags>";
|
||||
foreach ($aPlace['sExtraTags'] as $sKey => $sValue)
|
||||
{
|
||||
if (isset($aPlace['sExtraTags'])) {
|
||||
echo '<extratags>';
|
||||
foreach ($aPlace['sExtraTags'] as $sKey => $sValue) {
|
||||
echo '<tag key="'.htmlspecialchars($sKey).'" value="'.htmlspecialchars($sValue).'"/>';
|
||||
}
|
||||
echo "</extratags>";
|
||||
echo '</extratags>';
|
||||
}
|
||||
|
||||
if (isset($aPlace['sNameDetails']))
|
||||
{
|
||||
echo "<namedetails>";
|
||||
foreach ($aPlace['sNameDetails'] as $sKey => $sValue)
|
||||
{
|
||||
if (isset($aPlace['sNameDetails'])) {
|
||||
echo '<namedetails>';
|
||||
foreach ($aPlace['sNameDetails'] as $sKey => $sValue) {
|
||||
echo '<name desc="'.htmlspecialchars($sKey).'">';
|
||||
echo htmlspecialchars($sValue);
|
||||
echo "</name>";
|
||||
echo '</name>';
|
||||
}
|
||||
echo "</namedetails>";
|
||||
echo '</namedetails>';
|
||||
}
|
||||
|
||||
if (isset($aPlace['askml']))
|
||||
{
|
||||
if (isset($aPlace['askml'])) {
|
||||
echo "\n<geokml>";
|
||||
echo $aPlace['askml'];
|
||||
echo "</geokml>";
|
||||
echo '</geokml>';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
echo "</reversegeocode>";
|
||||
echo '</reversegeocode>';
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
<?php
|
||||
header("content-type: text/html; charset=UTF-8");
|
||||
?>
|
||||
<?php include(CONST_BasePath.'/lib/template/includes/html-header.php'); ?>
|
||||
<link href="css/common.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/details.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
|
||||
|
||||
<?php
|
||||
|
||||
function osmMapUrl($aFeature)
|
||||
{
|
||||
if (isset($sFeature['error_x']) && isset($sFeature['error_y']))
|
||||
{
|
||||
$sBaseUrl = '//www.openstreetmap.org/';
|
||||
$sOSMType = formatOSMType($aFeature['osm_type'], false);
|
||||
if ($sOSMType)
|
||||
{
|
||||
$sBaseUrl += $sOSMType.'/'.$aFeature['osm_id'];
|
||||
}
|
||||
|
||||
return '<a href="'.$sBaseUrl.'?mlat='.$aFeature['error_y'].'&mlon='.$aFeature['error_x'].'">view on osm.org</a>';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function josm_edit_url($aFeature)
|
||||
{
|
||||
$fWidth = 0.0002;
|
||||
$sLon = $aFeature['error_x'];
|
||||
$sLat = $aFeature['error_y'];
|
||||
|
||||
if (isset($sLat))
|
||||
{
|
||||
return "http://localhost:8111/load_and_zoom?left=".($sLon-$fWidth)."&right=".($sLon+$fWidth)."&top=".($sLat+$fWidth)."&bottom=".($sLat-$fWidth);
|
||||
}
|
||||
|
||||
$sOSMType = formatOSMType($aFeature['osm_type'], false);
|
||||
if ($sOSMType)
|
||||
{
|
||||
return 'http://localhost:8111/import?url=http://www.openstreetmap.org/api/0.6/'.$sOSMType.'/'.$aFeature['osm_id'].'/full';
|
||||
// Should be better to load by object id - but this doesn't seem to zoom correctly
|
||||
// return " <a href=\"http://localhost:8111/load_object?new_layer=true&objects=".strtolower($aFeature['osm_type']).$sOSMID."\" target=\"josm\">Remote Control (JOSM / Merkaartor)</a>";
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function potlach_edit_url($aFeature)
|
||||
{
|
||||
$fWidth = 0.0002;
|
||||
$sLat = $aFeature['error_y'];
|
||||
$sLon = $aFeature['error_x'];
|
||||
|
||||
if (isset($sLat))
|
||||
{
|
||||
return "//www.openstreetmap.org/edit?editor=potlatch2&bbox=".($sLon-$fWidth).",".($sLat-$fWidth).",".($sLon+$fWidth).",".($sLat+$fWidth);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
|
||||
?>
|
||||
|
||||
<body id="details-page">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
|
||||
|
||||
<h1><?php echo $aPointDetails['localname'] ?></h1>
|
||||
<div class="locationdetails">
|
||||
<h2 class="bg-danger">This object has an invalid geometry.</h2>
|
||||
|
||||
<div>
|
||||
Type: <span class="type"><?php echo $aPointDetails['class'].':'.$aPointDetails['type'];?></span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
OSM: <span class="label"><?php echo osmLink($aPointDetails); ?><span>
|
||||
</div>
|
||||
|
||||
|
||||
<h4>Error</h4>
|
||||
<p>
|
||||
<?php echo $aPointDetails['errormessage']?$aPointDetails['errormessage']:'unknown'; ?>
|
||||
</p>
|
||||
<?php echo osmMapUrl($aPointDetails); ?>
|
||||
|
||||
<h4>Edit</h4>
|
||||
<ul>
|
||||
<?php if (josm_edit_url($aPointDetails)) { ?>
|
||||
<li><a href="<?php echo josm_edit_url($aPointDetails); ?>" target="josm">Remote Control (JOSM / Merkaartor)</a></li>
|
||||
<?php } ?>
|
||||
<?php if (potlach_edit_url($aPointDetails)) { ?>
|
||||
<li><a href="<?php echo potlach_edit_url($aPointDetails); ?>" target="potlatch2">Potlatch 2</a></li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
var nominatim_result = {
|
||||
outlinestring: '<?php echo $aPointDetails['outlinestring'];?>',
|
||||
lon: <?php echo isset($aPointDetails['error_x']) ? $aPointDetails['error_x'] : 0; ?>,
|
||||
lat: <?php echo isset($aPointDetails['error_y']) ? $aPointDetails['error_y'] : 0; ?>
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<?php include(CONST_BasePath.'/lib/template/includes/html-footer.php'); ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,258 +0,0 @@
|
||||
<?php
|
||||
header("content-type: text/html; charset=UTF-8");
|
||||
?>
|
||||
<?php include(CONST_BasePath.'/lib/template/includes/html-header.php'); ?>
|
||||
<link href="css/common.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/details.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
|
||||
|
||||
<?php
|
||||
|
||||
function headline($sTitle)
|
||||
{
|
||||
echo "<tr class='all-columns'><td colspan='6'><h2>".$sTitle."</h2></td></tr>\n";
|
||||
}
|
||||
|
||||
function headline3($sTitle)
|
||||
{
|
||||
echo "<tr class='all-columns'><td colspan='6'><h3>".$sTitle."</h3></td></tr>\n";
|
||||
}
|
||||
|
||||
|
||||
function format_distance($fDistance)
|
||||
{
|
||||
// $fDistance is in meters
|
||||
if ($fDistance < 1)
|
||||
{
|
||||
return '0';
|
||||
}
|
||||
elseif ($fDistance < 1000)
|
||||
{
|
||||
return'<abbr class="distance" title="'.$fDistance.'">~'.(round($fDistance,0)).' m</abbr>';
|
||||
}
|
||||
else
|
||||
{
|
||||
return'<abbr class="distance" title="'.$fDistance.'">~'.(round($fDistance/1000,1)).' km</abbr>';
|
||||
}
|
||||
}
|
||||
|
||||
function kv($sKey,$sValue)
|
||||
{
|
||||
echo ' <tr><td>' . $sKey . '</td><td>'.$sValue.'</td></tr>'. "\n";
|
||||
}
|
||||
|
||||
|
||||
function hash_to_subtable($aAssociatedList)
|
||||
{
|
||||
$sHTML = '';
|
||||
foreach($aAssociatedList as $sKey => $sValue)
|
||||
{
|
||||
$sHTML = $sHTML.' <div class="line"><span class="name">'.$sValue.'</span> ('.$sKey.')</div>'."\n";
|
||||
}
|
||||
return $sHTML;
|
||||
}
|
||||
|
||||
function map_icon($sIcon)
|
||||
{
|
||||
if ($sIcon){
|
||||
echo '<img id="mapicon" src="'.CONST_Website_BaseURL.'images/mapicons/'.$sIcon.'.n.32.png'.'" alt="'.$sIcon.'" />';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _one_row($aAddressLine){
|
||||
$bNotUsed = (isset($aAddressLine['isaddress']) && $aAddressLine['isaddress'] == 'f');
|
||||
|
||||
echo '<tr class="' . ($bNotUsed?'notused':'') . '">'."\n";
|
||||
echo ' <td class="name">'.(trim($aAddressLine['localname'])?$aAddressLine['localname']:'<span class="noname">No Name</span>')."</td>\n";
|
||||
echo ' <td>' . $aAddressLine['class'].':'.$aAddressLine['type'] . "</td>\n";
|
||||
echo ' <td>' . osmLink($aAddressLine) . "</td>\n";
|
||||
echo ' <td>' . (isset($aAddressLine['admin_level']) ? $aAddressLine['admin_level'] : '') . "</td>\n";
|
||||
echo ' <td>' . format_distance($aAddressLine['distance'])."</td>\n";
|
||||
echo ' <td>' . detailsLink($aAddressLine,'details >') . "</td>\n";
|
||||
echo "</tr>\n";
|
||||
}
|
||||
|
||||
function _one_keyword_row($keyword_token,$word_id){
|
||||
echo "<tr>\n";
|
||||
echo '<td>';
|
||||
// mark partial tokens (those starting with a space) with a star for readability
|
||||
echo ($keyword_token[0]==' '?'*':'');
|
||||
echo $keyword_token;
|
||||
if (isset($word_id))
|
||||
{
|
||||
echo '</td><td>word id: '.$word_id;
|
||||
}
|
||||
echo "</td></tr>\n";
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
|
||||
|
||||
<body id="details-page">
|
||||
<?php include(CONST_BasePath.'/lib/template/includes/html-top-navigation.php'); ?>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-10">
|
||||
<h1><?php echo $aPointDetails['localname'] ?></h1>
|
||||
</div>
|
||||
<div class="col-sm-2 text-right">
|
||||
<?php map_icon($aPointDetails['icon']) ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<table id="locationdetails" class="table table-striped">
|
||||
|
||||
<?php
|
||||
|
||||
kv('Name' , hash_to_subtable($aPointDetails['aNames']) );
|
||||
kv('Type' , $aPointDetails['class'].':'.$aPointDetails['type'] );
|
||||
kv('Last Updated' , $aPointDetails['indexed_date'] );
|
||||
kv('Admin Level' , $aPointDetails['admin_level'] );
|
||||
kv('Rank' , $aPointDetails['rank_search_label'] );
|
||||
if ($aPointDetails['calculated_importance']) {
|
||||
kv('Importance' , $aPointDetails['calculated_importance'].($aPointDetails['importance']?'':' (estimated)') );
|
||||
}
|
||||
kv('Coverage' , ($aPointDetails['isarea']=='t'?'Polygon':'Point') );
|
||||
kv('Centre Point' , $aPointDetails['lat'].','.$aPointDetails['lon'] );
|
||||
kv('OSM' , osmLink($aPointDetails) );
|
||||
if ($aPointDetails['wikipedia'])
|
||||
{
|
||||
kv('Wikipedia Calculated' , wikipediaLink($aPointDetails) );
|
||||
}
|
||||
|
||||
kv('Extra Tags' , hash_to_subtable($aPointDetails['aExtraTags']) );
|
||||
|
||||
?>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<h2>Address</h2>
|
||||
|
||||
<table id="address" class="table table-striped table-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Local name</td>
|
||||
<td>Type</td>
|
||||
<td>OSM</td>
|
||||
<td>Admin level</td>
|
||||
<td>Distance</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<?php
|
||||
|
||||
foreach($aAddressLines as $aAddressLine)
|
||||
{
|
||||
_one_row($aAddressLine);
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
|
||||
<?php
|
||||
|
||||
if ($aLinkedLines)
|
||||
{
|
||||
headline('Linked Places');
|
||||
foreach($aLinkedLines as $aAddressLine)
|
||||
{
|
||||
_one_row($aAddressLine);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ($aPlaceSearchNameKeywords)
|
||||
{
|
||||
headline('Name Keywords');
|
||||
foreach($aPlaceSearchNameKeywords as $aRow)
|
||||
{
|
||||
_one_keyword_row($aRow['word_token'], $aRow['word_id']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($aPlaceSearchAddressKeywords)
|
||||
{
|
||||
headline('Address Keywords');
|
||||
foreach($aPlaceSearchAddressKeywords as $aRow)
|
||||
{
|
||||
_one_keyword_row($aRow['word_token'], $aRow['word_id']);
|
||||
}
|
||||
}
|
||||
|
||||
if (sizeof($aParentOfLines))
|
||||
{
|
||||
headline('Parent Of');
|
||||
|
||||
$aGroupedAddressLines = array();
|
||||
foreach($aParentOfLines as $aAddressLine)
|
||||
{
|
||||
if ($aAddressLine['type'] == 'yes') $sType = $aAddressLine['class'];
|
||||
else $sType = $aAddressLine['type'];
|
||||
|
||||
if (!isset($aGroupedAddressLines[$sType]))
|
||||
$aGroupedAddressLines[$sType] = array();
|
||||
$aGroupedAddressLines[$sType][] = $aAddressLine;
|
||||
}
|
||||
foreach($aGroupedAddressLines as $sGroupHeading => $aParentOfLines)
|
||||
{
|
||||
$sGroupHeading = ucwords($sGroupHeading);
|
||||
headline3($sGroupHeading);
|
||||
|
||||
foreach($aParentOfLines as $aAddressLine)
|
||||
{
|
||||
_one_row($aAddressLine);
|
||||
}
|
||||
}
|
||||
if (sizeof($aParentOfLines) >= 500) {
|
||||
echo '<p>There are more child objects which are not shown.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
echo "</table>\n";
|
||||
?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
<?php
|
||||
|
||||
$aNominatimMapInit = array(
|
||||
'tile_url' => $sTileURL,
|
||||
'tile_attribution' => $sTileAttribution
|
||||
);
|
||||
echo 'var nominatim_map_init = ' . json_encode($aNominatimMapInit, JSON_PRETTY_PRINT) . ';';
|
||||
|
||||
$aPlace = array(
|
||||
'asgeojson' => $aPointDetails['asgeojson'],
|
||||
'lon' => $aPointDetails['lon'],
|
||||
'lat' => $aPointDetails['lat'],
|
||||
);
|
||||
echo 'var nominatim_result = ' . json_encode($aPlace, JSON_PRETTY_PRINT) . ';';
|
||||
|
||||
|
||||
?>
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<?php include(CONST_BasePath.'/lib/template/includes/html-footer.php'); ?>
|
||||
</body>
|
||||
</html>
|
||||
107
lib/template/details-json.php
Normal file
107
lib/template/details-json.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
$aPlaceDetails = array();
|
||||
|
||||
$aPlaceDetails['place_id'] = (int) $aPointDetails['place_id'];
|
||||
$aPlaceDetails['parent_place_id'] = (int) $aPointDetails['parent_place_id'];
|
||||
|
||||
$aPlaceDetails['osm_type'] = $aPointDetails['osm_type'];
|
||||
$aPlaceDetails['osm_id'] = (int) $aPointDetails['osm_id'];
|
||||
|
||||
$aPlaceDetails['category'] = $aPointDetails['class'];
|
||||
$aPlaceDetails['type'] = $aPointDetails['type'];
|
||||
$aPlaceDetails['admin_level'] = $aPointDetails['admin_level'];
|
||||
|
||||
$aPlaceDetails['localname'] = $aPointDetails['localname'];
|
||||
$aPlaceDetails['names'] = $aPointDetails['aNames'];
|
||||
|
||||
$aPlaceDetails['addresstags'] = $aPointDetails['aAddressTags'];
|
||||
$aPlaceDetails['housenumber'] = $aPointDetails['housenumber'];
|
||||
$aPlaceDetails['calculated_postcode'] = $aPointDetails['postcode'];
|
||||
$aPlaceDetails['country_code'] = $aPointDetails['country_code'];
|
||||
|
||||
$aPlaceDetails['indexed_date'] = (new DateTime('@'.$aPointDetails['indexed_epoch']))->format(DateTime::RFC3339);
|
||||
$aPlaceDetails['importance'] = (float) $aPointDetails['importance'];
|
||||
$aPlaceDetails['calculated_importance'] = (float) $aPointDetails['calculated_importance'];
|
||||
|
||||
$aPlaceDetails['extratags'] = $aPointDetails['aExtraTags'];
|
||||
$aPlaceDetails['calculated_wikipedia'] = $aPointDetails['wikipedia'];
|
||||
$sIcon = Nominatim\ClassTypes\getIconFile($aPointDetails);
|
||||
if (isset($sIcon)) {
|
||||
$aPlaceDetails['icon'] = $sIcon;
|
||||
}
|
||||
|
||||
$aPlaceDetails['rank_address'] = (int) $aPointDetails['rank_address'];
|
||||
$aPlaceDetails['rank_search'] = (int) $aPointDetails['rank_search'];
|
||||
|
||||
$aPlaceDetails['isarea'] = $aPointDetails['isarea'];
|
||||
$aPlaceDetails['centroid'] = array(
|
||||
'type' => 'Point',
|
||||
'coordinates' => array( (float) $aPointDetails['lon'], (float) $aPointDetails['lat'] )
|
||||
);
|
||||
|
||||
$aPlaceDetails['geometry'] = json_decode($aPointDetails['asgeojson']);
|
||||
|
||||
$funcMapAddressLine = function ($aFull) {
|
||||
$aMapped = array(
|
||||
'localname' => $aFull['localname'],
|
||||
'place_id' => isset($aFull['place_id']) ? (int) $aFull['place_id'] : null,
|
||||
'osm_id' => isset($aFull['osm_id']) ? (int) $aFull['osm_id'] : null,
|
||||
'osm_type' => isset($aFull['osm_type']) ? $aFull['osm_type'] : null,
|
||||
'place_type' => isset($aFull['place_type']) ? $aFull['place_type'] : null,
|
||||
'class' => $aFull['class'],
|
||||
'type' => $aFull['type'],
|
||||
'admin_level' => isset($aFull['admin_level']) ? (int) $aFull['admin_level'] : null,
|
||||
'rank_address' => $aFull['rank_address'] ? (int) $aFull['rank_address'] : null,
|
||||
'distance' => (float) $aFull['distance'],
|
||||
'isaddress' => isset($aFull['isaddress']) ? (bool) $aFull['isaddress'] : null
|
||||
);
|
||||
|
||||
return $aMapped;
|
||||
};
|
||||
|
||||
$funcMapKeyword = function ($aFull) {
|
||||
$aMapped = array(
|
||||
'id' => (int) $aFull['word_id'],
|
||||
'token' => $aFull['word_token']
|
||||
);
|
||||
return $aMapped;
|
||||
};
|
||||
|
||||
if ($aAddressLines) {
|
||||
$aPlaceDetails['address'] = array_map($funcMapAddressLine, $aAddressLines);
|
||||
}
|
||||
|
||||
if ($aLinkedLines) {
|
||||
$aPlaceDetails['linked_places'] = array_map($funcMapAddressLine, $aLinkedLines);
|
||||
}
|
||||
|
||||
if ($bIncludeKeywords) {
|
||||
$aPlaceDetails['keywords'] = array();
|
||||
|
||||
if ($aPlaceSearchNameKeywords) {
|
||||
$aPlaceDetails['keywords']['name'] = array_map($funcMapKeyword, $aPlaceSearchNameKeywords);
|
||||
}
|
||||
|
||||
if ($aPlaceSearchAddressKeywords) {
|
||||
$aPlaceDetails['keywords']['address'] = array_map($funcMapKeyword, $aPlaceSearchAddressKeywords);
|
||||
}
|
||||
}
|
||||
|
||||
if ($bIncludeHierarchy) {
|
||||
if ($bGroupHierarchy) {
|
||||
$aPlaceDetails['hierarchy'] = array();
|
||||
foreach ($aHierarchyLines as $aAddressLine) {
|
||||
if ($aAddressLine['type'] == 'yes') $sType = $aAddressLine['class'];
|
||||
else $sType = $aAddressLine['type'];
|
||||
|
||||
if (!isset($aPlaceDetails['hierarchy'][$sType]))
|
||||
$aPlaceDetails['hierarchy'][$sType] = array();
|
||||
$aPlaceDetails['hierarchy'][$sType][] = $funcMapAddressLine($aAddressLine);
|
||||
}
|
||||
} else {
|
||||
$aPlaceDetails['hierarchy'] = array_map($funcMapAddressLine, $aHierarchyLines);
|
||||
}
|
||||
}
|
||||
|
||||
javascript_renderData($aPlaceDetails);
|
||||
11
lib/template/error-json.php
Normal file
11
lib/template/error-json.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
$error = array(
|
||||
'code' => $exception->getCode(),
|
||||
'message' => $exception->getMessage()
|
||||
);
|
||||
|
||||
if (CONST_Debug) {
|
||||
$error['details'] = $exception->getFile() . '('. $exception->getLine() . ')';
|
||||
}
|
||||
|
||||
echo javascript_renderData(array('error' => $error));
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user