Compare commits

...

335 Commits

Author SHA1 Message Date
Sarah Hoffmann
9610664fc5 prepare 3.6.0 release 2020-12-13 11:59:16 +01:00
Sarah Hoffmann
eed2e3f2a8 Merge pull request #2105 from lonvia/fix-use-of-records
Use a typed record for place info in get_addressdata
2020-12-13 09:48:21 +01:00
Sarah Hoffmann
1b62162e08 actions: run tests also on postgresql 9.5
This is the oldest version for which a postgis is available.
2020-12-12 20:55:54 +01:00
Sarah Hoffmann
5cf573c340 use a typed record for place info in get_addressdata
Older versions of Postgresql cannot handle an untyped record
for INTO.

Fixes #2100.
2020-12-12 14:46:34 +01:00
Sarah Hoffmann
76cd8bf258 Merge pull request #2102 from mtmail/typo-in-reverse-md
Reverse.md: fix two typos
2020-12-12 10:16:23 +01:00
marc tobias
31ba7e7cf0 Reverse.md: fix two typos 2020-12-12 00:30:26 +01:00
Sarah Hoffmann
a338ba695b Merge pull request #2101 from lonvia/update-documentation
Update documentation
2020-12-11 18:02:44 +01:00
Sarah Hoffmann
9f6e2de4ed fix typos 2020-12-11 17:08:13 +01:00
Sarah Hoffmann
1b3acc4f8f update import times 2020-12-11 16:53:38 +01:00
Sarah Hoffmann
56cf3b362e Merge pull request #2099 from lonvia/update-country-names
Dynamically update country names from OSM data
2020-12-10 17:56:32 +01:00
Sarah Hoffmann
b59d01fe85 update country names
Copies all name:xx country names that are in OSM as of today
into the country name fallback table.
2020-12-09 17:52:25 +01:00
Sarah Hoffmann
65d8770b28 update country_names from OSM data
Update names in the coutry_names table on the fly from incomming
OSM country data. Adding a small sanity check that the country
must be an OSM relation and within the area where we expect the
country to be.
2020-12-09 11:38:19 +01:00
Sarah Hoffmann
38582c3e52 Merge pull request #2096 from otbutz/patch-1
Add link to discussions when creating new issue
2020-12-09 09:55:59 +01:00
otbutz
5f6fe6cab7 Add link to discussions when creating new issue 2020-12-09 09:33:42 +01:00
Sarah Hoffmann
34f8e6ddf7 remove bug reporting hints in favour of issue templates 2020-12-08 17:47:38 +01:00
Sarah Hoffmann
603367dced updates to admin and develop documentation
Mostly minor updates in wording and resource consumption.
2020-12-08 17:47:38 +01:00
Sarah Hoffmann
eaee87e73d update API documentation
* remove traces of HTML output
* add details on artificial objects (see also #1671)
* add geometry output documentation for lookup
* deprecate query by ID via reverse endpoint
* remove /search/<query> query format, no longer supported
* explain better what reverse geocoding does
* lots of smaller fixes to wording
2020-12-08 17:47:38 +01:00
Sarah Hoffmann
ea844db847 do not classify housenumbers as rare
House numbers are highly redundant, so don't even attempt to
do it as a rare name search. Greatly improves speed of such
queries.
2020-12-08 17:25:15 +01:00
Sarah Hoffmann
3ca8cc0344 Merge pull request #2092 from lonvia/update-osm2pgsql
Update osm2pgsql to 1.4.0 release
2020-12-08 10:56:25 +01:00
Sarah Hoffmann
f67c06f128 update osm2pgsql to 1.4.0 release 2020-12-08 10:18:18 +01:00
Sarah Hoffmann
7889509856 Create issue templates 2020-12-06 22:24:45 +01:00
Sarah Hoffmann
67c995aef6 Merge pull request #2090 from lonvia/avoid-contains-operator
avoid contains operator for geometries
2020-12-03 09:39:50 +01:00
Sarah Hoffmann
e20defeebd avoid contains operator for geometries
Postgis keeps messing up use of index in some circumstances.
2020-12-02 22:18:27 +01:00
Sarah Hoffmann
cca646a19e Merge pull request #2087 from lonvia/only-one-link-per-node
Place nodes can only be linked once against boundaries
2020-12-02 22:16:56 +01:00
Sarah Hoffmann
ddc2b4b806 Merge pull request #2088 from lonvia/update-osm2pgsql
update osm2pgsql
2020-12-02 22:16:38 +01:00
Sarah Hoffmann
0334915067 update osm2pgsql
Needs now an explicit switch to avoid propagating changes from
nodes to ways to relations.
2020-12-02 18:27:18 +01:00
Sarah Hoffmann
987d60ccda place nodes can only be linked once against boundaries
If a place node is already linked against a boundary, it should not
be used for linking again. It is usually a sign of a mapping error,
when there are multiple boundary candidates. This change just avoids
inconsistent data in the database, it does not guarantee that the
linking is against the more correct boundary.
2020-12-02 15:31:02 +01:00
Sarah Hoffmann
7d520bf448 also add explicit cast for varchar 2020-12-01 22:15:51 +01:00
Sarah Hoffmann
dc53288e6b Merge pull request #2085 from lonvia/add-address-rank-to-xml-output
add address rank to XML output
2020-12-01 19:59:29 +01:00
Sarah Hoffmann
db02673b60 Merge pull request #2084 from lonvia/fix-term-count
fix use of term count in partial terms
2020-12-01 19:59:07 +01:00
Sarah Hoffmann
cf9b248f29 add address rank to XML output
The address rank is much more interesting than the search rank
these days because it tells something about the kind of object.

Reverse did have neither rank, so add both for consistency.
2020-12-01 17:54:53 +01:00
Sarah Hoffmann
df12954312 fix use of term count in partial terms
Term count for partial words is one less than the actual number
of words. Take that into account when adding to the search rank.

Fixes #2081.
2020-12-01 17:21:01 +01:00
Sarah Hoffmann
a9357b4dce Merge pull request #2082 from lonvia/compute-address-on-the-fly-II
Compute address for POIs on the fly
2020-12-01 16:41:31 +01:00
Sarah Hoffmann
63544db8f9 null entries need to be typed 2020-12-01 14:54:42 +01:00
Sarah Hoffmann
7295cad715 compute address parts for rank 30 objects on the fly
Rank 30 objects usually use the address parts of their parent.
When the parent has address parts that are areas but not marked
as isaddress, then the parent might go through multiple administrative
areas. In that case recheck if the right area has been choosen
for the object in question instead of relying on isaddress.
Note that we really only have to do the recomputation in the
case of 'isarea = True and isaddress = False' which hopefully
keeps the number of additional geometric operations we have to do
to a minimum.

There is one more special case to be taken into account here: a
street may go through two administrative areas and a house along
that street is placed in one of the area while the addr:* tags
says it belongs to the other. In that case we must not switch
the isaddress to the one it is situated. To avoid that recheck
the address names against the name of the ara. That is not perfect
but should cover most cases.

Fixes #328.
2020-12-01 11:58:25 +01:00
Sarah Hoffmann
ff85da0a31 cleanup get_addressdata
Save location data in a ROW instead of using separate varaibles
for each value.
2020-11-30 22:54:36 +01:00
Sarah Hoffmann
75b2d7ca99 Merge pull request #2080 from donalhunt/fix-Migration.md-typos
Migration.md: fix typos, improve style consistency and readability.
2020-11-30 16:21:35 +01:00
Donal Hunt
3c9eeb11fa Migration.md: fix typos, improve style consistency and readability. 2020-11-30 11:59:10 +00:00
Sarah Hoffmann
63bacaee2e Merge pull request #2079 from lonvia/improve-progress-logging
Improve progress logging during indexing
2020-11-30 11:42:08 +01:00
Sarah Hoffmann
5016eace34 improve progress logging during indexing
Wait for 2 seconds before logging the first progress, so that we
have numbers that are a bit more reliable statistically speaking.

Also provides an actual implementation for the log_interval
parameter and fixes some small style issues.
2020-11-30 10:59:29 +01:00
Sarah Hoffmann
2e5c8b5cd3 Merge pull request #2077 from lonvia/optimize-large-rank-0-areas
Restrict size of features that get a full address search
2020-11-26 14:40:54 +01:00
Sarah Hoffmann
bf0f81adcb Merge pull request #2076 from lonvia/search-name-index-migration
Docs: add migration for search_name_* tables
2020-11-26 12:01:38 +01:00
Sarah Hoffmann
2db751700e restrict size of features that get a full address search
It would be nice to always compute addresses for rank 0 objects
over the complete geometry, so that they can be found via all
the admin boundaries that they intersect. However, there are a
couple of extramely large boundaries in OSM (like timezones)
where this results in thousands of possible address candidates
that need to be checked. Fall back to getting the address of the
centroid for them.
2020-11-26 11:53:58 +01:00
Sarah Hoffmann
62bee4ed37 docs: add migration for search_name_* tables 2020-11-26 09:18:33 +01:00
Sarah Hoffmann
1f07d63dc5 Merge pull request #2075 from lonvia/filter-postcodes-from-location-area-large
Filter postcodes by search rank when adding to address list
2020-11-25 21:42:27 +01:00
Sarah Hoffmann
cc1af99dbd filter postcodes by search rank when adding to address list
The post codes are the last part that does not fit the new
address ranking scheme. In particular, the search rank is still
relevant for choosing if a postcode should be included into
the address terms. Filter out irrelevant postcodes in
getNearFeatures() already, to avoid having to check for
geometry relation.
2020-11-25 21:01:33 +01:00
Sarah Hoffmann
c5d98effc0 Merge pull request #2074 from lonvia/add-housenumber-to-unknown-places
Improve finding addresses that have their own search_name entry because of unknown addr:* parts
2020-11-25 16:57:09 +01:00
Sarah Hoffmann
b68b2ff6b8 Merge pull request #2073 from lonvia/multi-word-partial-terms-in-search-description
Improve handling of multi-word partials in SearchDescription
2020-11-25 16:57:00 +01:00
Sarah Hoffmann
57f0d55c2e make phpcs happy 2020-11-25 16:14:31 +01:00
Sarah Hoffmann
3cf763475f do not use artificial housenumbers as names
If they are artificial they cannot have a search_name entry.
2020-11-25 16:11:32 +01:00
Sarah Hoffmann
0f87da017f improve handling of multi-word partials in SearchDescription
Multi-word partial terms had an undue advantage over separate partial
terms because they only need to pay the penalty once. This changes
the behaviour by setting the penalty according to the number of
words in the token. This should get rid of search interpretations
with low chance of matching.

This also fixes handling of exact term matching. We now match against
all exact terms of the query, not just a couple of them collected
while building the interpretations.

Also adds a penalty to very short postcodes.
2020-11-25 12:07:04 +01:00
Sarah Hoffmann
22800d7d59 Search housenumbers with unknown address parts by housenumber term
House numbers need special handling because they may appear after
the street term. That means we canot just use them as the main name
for searches where the address has its own search term entries.
Doing this right now, we are able to find '40, Main St, Town' but not
'Main St 40, Town'.

This switches to using the housenumber token as the name term instead.
House number tokens can get special handling when building the search
query that covers the case where they come after the street.

The main disadvantage is that this once more increases the numbers
of possible search interpretation of which we have already too many.

no penalty for housenumber searches
2020-11-25 11:36:10 +01:00
Sarah Hoffmann
f21853ea9d Merge pull request #2071 from lonvia/fix-more-ranks
Search rank 30 must always go with address rank 30
2020-11-24 21:45:30 +01:00
Sarah Hoffmann
1e76d668bd Merge pull request #2070 from lonvia/unlisted-places-to-rank-25
Move unlisted places to address rank 25
2020-11-24 21:45:16 +01:00
Sarah Hoffmann
b4b50eef15 search rank 30 must always go with address rank 30 2020-11-24 17:57:28 +01:00
Sarah Hoffmann
a9ad390b9e move unlisted places to address rank 25
Unlisted places are derived from addr:place and as such are
still places not streets.
2020-11-24 17:54:00 +01:00
Sarah Hoffmann
2e9e961fff Merge pull request #2068 from lonvia/fix-reverse-only
Do not create POI search terms in reverse-only mode
2020-11-24 08:22:48 +01:00
Sarah Hoffmann
13180989d9 Test --reverse-only with CI 2020-11-23 22:36:28 +01:00
Sarah Hoffmann
a4f1e40b72 do not create POI search terms on reverse-only
Fixes #2067.
2020-11-23 19:55:36 +01:00
Sarah Hoffmann
04d485c550 Merge pull request #2065 from rustycamper/patch-1
viewbox arguments are no longer accepter "in any order"
2020-11-23 09:55:29 +01:00
Pietro
a92bd1e2db viewbox arguments are no longer accepter "in any order"
Order should be longitude, then latitude
2020-11-23 10:40:43 +02:00
Sarah Hoffmann
f89e71a861 make sure that admin levels in NL are kept in order 2020-11-19 09:44:02 +01:00
Hendrik Morée
dcc075b34b Admin levels 8 and 10 of the Netherlands are municipal / city 2020-11-18 11:30:24 +01:00
Sarah Hoffmann
49083c2597 Merge pull request #2058 from lonvia/split-address-words
Split addr:* tags into words before adding to the search index
2020-11-18 08:58:17 +01:00
Sarah Hoffmann
29785ba166 Merge pull request #2059 from lonvia/include-parent-name-for-unknown-places
POIs with unknown addr:place must add parent name to address
2020-11-18 08:58:03 +01:00
Sarah Hoffmann
ffb2c93ba3 POIs with unknown addr:place must add parent name to address
The previous behaviour was a left-over from a former version
where such POIs parented to the street. Now that they parent to
places, it should be included.
2020-11-17 19:44:43 +01:00
Sarah Hoffmann
30a6b6bdac split addr: tags into words before adding to the search index
Address parts are only matched by single partial words. If
the addr: names are not split, then multi-word names cannot
be found.
2020-11-17 18:03:33 +01:00
Sarah Hoffmann
cc345f531a Merge pull request #2056 from lonvia/avoid-linking-postal-areas
Disallow linking for postcode areas
2020-11-17 11:15:56 +01:00
Sarah Hoffmann
9ede048769 disallow linking for postcode areas 2020-11-17 10:53:26 +01:00
Sarah Hoffmann
d23bf6e659 Merge pull request #2054 from lonvia/display-addr-terms
Merge places into address lists referred to by addr:* tags but not computed by Nominatim
2020-11-16 16:08:06 +01:00
Sarah Hoffmann
6b60f0ab03 use bool_or(ST_Intersects) instead of ST_Intersects(ST_Collect)
ST_Intersects segfaults on geometry collections for certain versions
of Postgis 3.
2020-11-16 15:28:01 +01:00
Sarah Hoffmann
aa9923bf07 fix typo 2020-11-16 15:28:01 +01:00
Sarah Hoffmann
9160cce6d8 remove unused columns in search_name_* and use right index
We only need the address rank these days, so get rid of
search rank. Also switch indexes to work on address rank.
2020-11-16 15:28:01 +01:00
Sarah Hoffmann
885dc0a8e1 more tests for absense of additional addressline entries 2020-11-16 15:28:01 +01:00
Sarah Hoffmann
7324431b12 get additional addresses for rank 30 objects
get_addressdata() now also checks if the place itself has entries
in the place_addressline table and merges them into the results.

Also restrict checking for address tag places to cases where the
name cannot be found in the parent's address search terms. Looking
up all address tags is just too slow.
2020-11-16 15:28:01 +01:00
Sarah Hoffmann
021f2bef4c get address terms from address tags for rank 30
For rank 30 objects add extra elements into the place_addressline
table.
2020-11-16 15:28:01 +01:00
Sarah Hoffmann
6260fef2e8 add test for placex from addr tags 2020-11-16 15:28:01 +01:00
Sarah Hoffmann
c7472662a6 lookup places for address tags for rank < 30
While previously the content of addr:* tags was only added
to the list of address search keywords, we now really look up
the matching place. This has the advantage that we pull in all
potential translations from the place, just like all the other
address terms that are looked up by neighbourhood search.

If no place can be found for a given name, the content of the
addr:* tag is still added to the search keywords as before.
2020-11-16 15:28:01 +01:00
Sarah Hoffmann
fecfe62fc6 Merge pull request #2055 from lonvia/fix-actions
Actions: update apt repo before installing software
2020-11-16 11:26:10 +01:00
Sarah Hoffmann
21b0430e46 actions: update apt repo before installing software 2020-11-16 10:14:38 +01:00
Sarah Hoffmann
66595c2d2b Merge pull request #2046 from lonvia/less-parallel-ranking
Only index larger batches for rank 30
2020-11-06 09:39:07 +01:00
Sarah Hoffmann
4ac29fb525 only index larger batches for rank 30
Fixes #2045.
2020-11-05 22:14:49 +01:00
Sarah Hoffmann
04d50a271d Merge pull request #2041 from lonvia/address-ranks-belgium
Adapt admin_levels for Belgium
2020-11-03 16:23:35 +01:00
Sarah Hoffmann
cbbda1ddf0 adapt admin_levels for Belgium
Fixes #272.
2020-11-03 10:46:52 +01:00
Sarah Hoffmann
928c6245c9 Merge pull request #2038 from lonvia/addresses-for-large-areas
Improve addresses for large areas
2020-11-03 08:49:01 +01:00
Sarah Hoffmann
ac9be161f6 Merge pull request #2039 from lonvia/migration-for-ui
Add  migration hints for UI removal and remove tests for icon attribute
2020-11-03 08:48:45 +01:00
Sarah Hoffmann
33378dcf6e remove tests for icon attribute
The icon attribute requires the CONST_MapIcon_URL to be present
which we cannot guarantee for the tests.
2020-11-02 16:46:29 +01:00
Sarah Hoffmann
e31a1f7ef1 docs: add migration hints for removed UI 2020-11-02 16:34:17 +01:00
Sarah Hoffmann
fa574ae9fd use different area estimates for large countries 2020-11-02 14:21:30 +01:00
Sarah Hoffmann
b2ebf4b4b7 adapt tests to rank changes of natural 2020-11-02 11:42:10 +01:00
Sarah Hoffmann
0f5615b618 guess a base address level for address rank 0 objects
The guess is based on the area and mainly avoids odd
addresses for very large or small objects.
2020-11-02 11:42:10 +01:00
Sarah Hoffmann
f050f898bc elevate most natural feature to address rank 22
Makes them be in par with landuse features.
2020-11-02 11:42:10 +01:00
Sarah Hoffmann
ce1c3bab6d Merge pull request #2032 from lonvia/remove-ui
Remove HTML output
2020-11-01 15:12:12 +01:00
Sarah Hoffmann
5cdabc5173 Merge pull request #2035 from lonvia/add-index-to-multicountry-script
docs: need to index after updating with a file
2020-11-01 15:11:02 +01:00
Sarah Hoffmann
42775f959b docs: need to index after updating with a file
Fixes #2031.
2020-10-31 22:53:08 +01:00
Sarah Hoffmann
7f55dcef3a use simpler recurse operator for overpass download
Also fixes a typo in the OSM link.
2020-10-31 21:44:28 +01:00
Sarah Hoffmann
ba8accf4bb make phpcs happy 2020-10-29 11:36:16 +01:00
Sarah Hoffmann
e62db51b06 vagrant: setting website URL is not longer necessary 2020-10-29 11:13:32 +01:00
Sarah Hoffmann
b81894d3d5 remove now unused settings related to website
There are two places where the website URL is still used:
for icons, replace the URL with a link to the icon repository
of the UI repo. The more URL now builds the link from the
server info.
2020-10-29 11:13:32 +01:00
Sarah Hoffmann
d86cf6801f remove tests for HTML output 2020-10-29 11:13:32 +01:00
Sarah Hoffmann
c0d21d0bd3 remove HTML output and UI elements 2020-10-29 11:13:04 +01:00
Sarah Hoffmann
b838f66dd7 remove hierarchy endpoint
This endpoint was never maintained and most of the information
can be obtained via the details endpoint.
2020-10-29 11:13:03 +01:00
Sarah Hoffmann
db9cc270b3 Merge pull request #2030 from lonvia/improve-ci
Small improvements for github actions run
2020-10-28 15:20:40 +01:00
Sarah Hoffmann
4147e04319 action: cache downloaded dependencies 2020-10-28 14:51:05 +01:00
Sarah Hoffmann
a888f6ff93 Merge pull request #2027 from lonvia/remove-duplicate-admin-boundaries
Handle duplicated admin boundaries
2020-10-28 11:11:42 +01:00
Sarah Hoffmann
bd04c49bc1 actions: tweak database settings
Disabling fsync and friends should speed up the CI run
significantly.
2020-10-28 11:10:14 +01:00
Sarah Hoffmann
5872b81232 use highest admin boundary for duplicated ones 2020-10-28 10:49:26 +01:00
Sarah Hoffmann
abd20d3ca6 disable admin level 5 in Russia
They either interfere with cities or refer to historical boundaries.
2020-10-28 10:49:26 +01:00
Sarah Hoffmann
95f83b90d2 minor fixes for geometry compuation during boundary ranking
Go back to using centroid when determining if one admin level
is within another. There are cases where boundaries are slightly
misaligned due to mapping errors (not using the same ways in the
relations).

Only declare boundaries the same when they have the same wikidata
tag _and_ have exactly the same geometry. This works around tagging
errors with the wikidata tag, which happen because of automated
edits to the wikidata tag.
2020-10-28 10:49:26 +01:00
Sarah Hoffmann
7a16909219 detect and remove admin boundary duplicates
The Polish community maps admin boundaries that span multiple
levels by duplicating the boundary relations. Detect this situation
by looking out for matching wikidata tags. The higher ranked
duplicates are then thrown out from the address pool by setting
their address rank to 0.
2020-10-28 10:49:26 +01:00
Sarah Hoffmann
ee4684e6a9 Merge pull request #2029 from lonvia/master
Switch CI to github actions
2020-10-28 10:12:52 +01:00
Sarah Hoffmann
a2b1a1eb33 switch CI badge to github actions 2020-10-28 10:11:22 +01:00
Sarah Hoffmann
7bc0fc9611 switch CI to github acitons 2020-10-28 09:29:22 +01:00
Sarah Hoffmann
e0e18e2b6f Merge pull request #2025 from lonvia/fix-secondary-importance-for-countries
Improve secondary result ordering for administrative boundaries
2020-10-22 13:43:22 +02:00
Sarah Hoffmann
788ba6d985 adjust secondary order when no addressimportance available
In cases of countries and remote places without an address
it is possible that 'addressimportance' comes back empty.
Adjust the 'foundorder' to the places importance instead
in such cases.

Fixes #2023.
2020-10-22 10:20:16 +02:00
Sarah Hoffmann
b012e15245 readd boundary:administrative to class importance 2020-10-22 10:16:04 +02:00
Sarah Hoffmann
ba31456278 Merge pull request #2022 from lonvia/populate-rank-25
reorganize ranks of high-level place types
2020-10-20 23:26:18 +02:00
Sarah Hoffmann
b661c66c00 reorganize ranks of high-level place types
Rank 25 is now available for places that should appear in addresses
but not when a street is present. Use this for som block-like
place types. Also document the particularity of rank 25.

subdevisions and allotments are now at the same level as landuse
which they are frequently used together with.
2020-10-20 20:20:49 +02:00
Sarah Hoffmann
f0372736aa Merge pull request #2019 from lonvia/find-pgconfig-inpostgresql-repo
Add support for finding pg_config in Postgresql repos
2020-10-20 15:02:00 +02:00
Sarah Hoffmann
e286d3f23d add support for finding pg_config in Postgresql repos
It uses the same PostgreSQL_ADDITIONAL_VERSIONS variable as
osm2pgsql so that setting that should be sufficient to make
it work.
2020-10-20 11:39:00 +02:00
Sarah Hoffmann
6c8a7b0a1a Merge pull request #2018 from lonvia/update-osm2pgsql
Update to latest osm2pgsql
2020-10-20 11:33:36 +02:00
Sarah Hoffmann
3107dc208a update to latest osm2pgsql
Important changes:

* fix disabling of JIT in Postgresql
* support for finding latests Postgresql from their repos
* no longer create nodes table with flatnodes
2020-10-20 11:08:07 +02:00
Sarah Hoffmann
bf4d75458c add explicit bbox contains check
Now that the containment check uses ST_Relate, we need to add
a separate bbox contains check to ensure that Postgis does the
efficient check first. Note that we still cannot get rid of the
overlap(&&) check because then Postgis will use the wrong indexes.
2020-10-19 10:39:01 +02:00
Sarah Hoffmann
3604d0d913 Merge pull request #2016 from lonvia/locale-address-russia
add country-specific address ranks for Russia
2020-10-18 09:48:23 +02:00
Sarah Hoffmann
dd06638dec Merge pull request #2015 from lonvia/cleanup-address-computation
Rework collection of address parts
2020-10-18 09:48:05 +02:00
Sarah Hoffmann
73a0ec22a3 add country-specific address ranks for Russia
Removes admin level 7, which should not exist and promotes
admin level 8 to municipality level.

place=municipality is only used for boroughs of St. Petersburg,
so demote to level 18.

Fixes #926.
2020-10-17 17:54:06 +02:00
Sarah Hoffmann
b0ef84caae add tests for rank computation 2020-10-17 17:51:22 +02:00
Sarah Hoffmann
64899ef54b add tests for address computation 2020-10-16 11:07:17 +02:00
Sarah Hoffmann
1064a9264e revert to && comparison for geometries
Postgis 3 picks the wrong index when using ~ or @.
2020-10-16 09:49:48 +02:00
Sarah Hoffmann
acfa7bec9c use computed centroid for location_area_large
The new address computation assumes that the centroid is inside
the area. Therefore we cannot use the centroid function. Use the
pre-computed centroid instead which has already been corrected to
be inside the area.
2020-10-15 17:30:52 +02:00
Sarah Hoffmann
62b94e838b correctly set from area column in place_addressline
This was always set to true which brings us to the question
if it is even still needed.
2020-10-15 12:06:53 +02:00
Sarah Hoffmann
5236e7a03e fix use of geometry operators
@ is contained by while ~ is contains.
2020-10-15 12:06:18 +02:00
Sarah Hoffmann
7e9412a044 demote admin boundaries for place areas
Also demote the address rank of an admin boundary when there
is a place area of higher rank that completely contains the
area. This catches the case where city boundaries do not exactly
align with administrative units (see for example Moscow).
2020-10-14 11:33:47 +02:00
Sarah Hoffmann
e47c19beb9 exclude rank 25 when computing addresses of streets
Address rank 25 is used for squares which are address-wise on the
same level as streets.
2020-10-13 22:36:17 +02:00
Sarah Hoffmann
2fe3c654fc overhaul address computation
This is a complete rewrite of the selection of address parts to
be inserted into the place_addressline table.

The new algorithm selects for each rank:
* the boundary overlapping with the addressee and contained
  in the already selected boundaries of lower rank, or failing that
* the place node closest to the addressee that is contained in
  the already selected boundaries and in the influence radius
  of already selected place nodes of lower rank

Place nodes that are not contained in already selected boundaries
of lower rank are completely thrown away. All other candidates are
added as non-address parts.
2020-10-13 22:10:07 +02:00
Sarah Hoffmann
5ec48c66cb move ordering out of getNearFeatures
The two places where the function is called have different ordering
requirement.
2020-10-13 15:24:54 +02:00
Sarah Hoffmann
4e7ec92d6f Merge pull request #2012 from lonvia/format-reverse-debug-output
use Debug class for formatting reverse debug output
2020-10-13 09:23:43 +02:00
Sarah Hoffmann
e115de47fc use Debug class for formatting reverse debug output 2020-10-12 17:12:03 +02:00
Sarah Hoffmann
bb5ffd3904 Merge pull request #2011 from lonvia/increase-city-radius
Increase radius of influence around city nodes
2020-10-12 16:17:39 +02:00
Sarah Hoffmann
887ae7fcab increase radius of influence around city nodes
The current radius does not cover cities with more than a
million inhabitants well.
2020-10-12 14:17:37 +02:00
Sarah Hoffmann
abaaf942cb Merge pull request #2004 from lonvia/demote-place-nodes-in-admin-areas
demote place nodes in admin areas
2020-10-12 11:54:52 +02:00
Sarah Hoffmann
ff47f6f65d when linking always check against original address rank 2020-10-11 12:29:49 +02:00
Sarah Hoffmann
ca680fc9fc make housenumber interpolation tests more lenient 2020-10-11 12:04:53 +02:00
Sarah Hoffmann
b04463bb2d demote place nodes in admin areas
If a place node of city rank and above finds itself in an
administrative boundary of the same address rank, then
increase the address rank by 2. This catches the rather
frequent case where city suburbs are tagged for historical
reasons as towns or villages.
2020-10-11 12:04:53 +02:00
Sarah Hoffmann
c66f701232 Merge pull request #2008 from lonvia/docs-missing-index-migration
docs: migration to new wikipedia needs new index
2020-10-11 11:05:59 +02:00
Sarah Hoffmann
3b23144ae6 docs: migration to new wikipedia needs new index
Fixes #1998.
2020-10-11 10:40:23 +02:00
Sarah Hoffmann
f7a9462337 Merge pull request #2006 from mtmail/ubuntu-20-postgresql-contrib
Ubuntu 20: use postgresql-contrib-12 so no version higher gets installed
2020-10-11 09:45:37 +02:00
Sarah Hoffmann
17f24d0061 Merge pull request #2003 from lonvia/admin-levels-indonesia
Adapt address levels for admin boundaries in Indonesia
2020-10-11 09:43:33 +02:00
marc tobias
eb8bce22b8 Ubuntu 20: use postgresql-contrib-12 so no version higher gets installed 2020-10-10 02:42:46 +02:00
Sarah Hoffmann
6a31691121 adapt address levels for admin boundaries in Indonesia 2020-10-09 22:28:06 +02:00
Sarah Hoffmann
25c4bf6ed4 Merge pull request #2002 from lonvia/analyse-indexing-script
Add script for detailed explaing of indexing trigger
2020-10-09 20:23:51 +02:00
Sarah Hoffmann
ec5743bcc0 add script for detailed explaing of indexing trigger 2020-10-09 17:38:33 +02:00
Sarah Hoffmann
7cd330ee55 Merge pull request #1996 from lonvia/remove-postcode-search-structured
Restrict postcode searches to postcode in first token
2020-10-06 17:04:22 +02:00
Sarah Hoffmann
7d2b6879c8 restrict postcode searches to postcode in first token
In structured queries we should only assume that it is
a postcode search when only the postcode and optionally
the country is given. If any other term is present, it
is better to avoid the search for postcode as it yields
too many bad searches. Given that the terms in a structured
query are ordered, this means that the postcode must be
the first token just like in the unstructured query.

Fixes #1988.
2020-10-06 14:08:31 +02:00
Sarah Hoffmann
a40684162a Revert "adapt tests to rank_search removal"
This reverts commit 2a717da850.
2020-10-06 13:59:50 +02:00
Sarah Hoffmann
cd997ff058 Merge pull request #1995 from lonvia/update-osm2pgsql
update to latest osm2pgsql version
2020-10-05 18:14:29 +02:00
Sarah Hoffmann
5391bb6cf7 update to latest osm2pgsql version
The latest version of osm2pgsql no longer creates indexes on
the members of planet_osm_rels. So we do that ourselves.
Given that we only need to access associated street relations,
the index can become quite a bit smaller.
2020-10-05 17:11:13 +02:00
Sarah Hoffmann
6b8ce1ee74 Merge pull request #1986 from mtmail/document-drop-idx_placex_geometry_reverse_lookupPoint
migration guide: idx_placex_geometry_reverse_lookupPoint can be dropped
2020-10-05 16:18:46 +02:00
Sarah Hoffmann
a37d46ec25 Merge pull request #1993 from mtmail/country-name-eSwatini
country names: Swaziland => eSwatini
2020-10-05 16:17:54 +02:00
marc tobias
b5c8237118 country names: Swaziland => eSwatini 2020-10-04 13:38:15 +02:00
marc tobias
eeffe2caf4 migration guide: idx_placex_geometry_reverse_lookupPoint can be dropped 2020-09-30 14:28:20 +02:00
Sarah Hoffmann
c5c7a6a453 Merge pull request #1984 from lonvia/remove-reverse-index
Remove unused idx_placex_geometry_reverse_lookupPoint
2020-09-30 12:02:34 +02:00
Sarah Hoffmann
b969392f40 remove removed index from database check 2020-09-30 11:33:15 +02:00
Sarah Hoffmann
40bc1752c2 remove unused idx_placex_geometry_reverse_lookupPoint
The index has been unused ever since the query using it was
changed two years ago. Given that it has not been missed much,
drop it completely here.
2020-09-30 09:21:35 +02:00
Sarah Hoffmann
851c3779b5 Merge pull request #1982 from lonvia/more-rank-search-removal
More rank search removal
2020-09-26 11:08:20 +02:00
Sarah Hoffmann
2a717da850 adapt tests to rank_search removal 2020-09-26 09:10:37 +02:00
Sarah Hoffmann
f8694da3c9 Remove more rank_search usage from address computation
Fixes #1904.
2020-09-25 17:50:36 +02:00
Sarah Hoffmann
eeb2b1f998 Merge pull request #1980 from lonvia/add-descriptive-term-for-address-rank-24
add descriptive term for address rank 24
2020-09-25 16:56:35 +02:00
Sarah Hoffmann
1db8f7e353 add descriptive term for address rank 24
With that term we have terms for all ranks, so that no generic
'administrative' term will show up in the address details anymore.
2020-09-25 16:02:17 +02:00
Sarah Hoffmann
6625e93be6 Merge pull request #1975 from lonvia/simplify-parent-assignment-for-unlisted-places
Use closest containing place area for parent of unlisted addr:place
2020-09-23 19:10:42 +02:00
Sarah Hoffmann
56053988bf Merge pull request #1970 from lonvia/remove-duplicate-geometry-check
Switch recursive updates to using rank_search
2020-09-23 18:51:35 +02:00
Sarah Hoffmann
d9325dc11a use rank_address when invalidating containing objects
Only rank_address is now relevant for determining if a place
could be part of an address.
2020-09-23 17:44:31 +02:00
Sarah Hoffmann
d3ca9dd3f7 remove ST_Covers check when also testing for ST_Intersects
Using both is slightly problematic because they have different
ways to use the index. Newer versions of Postgis exhibit a
query planner issue when both functions appear together.
As ST_Intersects includes ST_Covers, simply remove the latter.
2020-09-23 17:44:31 +02:00
Sarah Hoffmann
e552f6bce5 use closest containing place for unlisted addr:place
We can't use getNearFeatures() to determine the parent of a
place with an unlisted addr:place because this function
returns place nodes that are potentially outside the area
of interest. Doing the complete address computation is too
expensive, so simply use the area with the largest rank that
contains the feature instead.
2020-09-23 17:33:42 +02:00
Sarah Hoffmann
cf23e10382 Merge pull request #1974 from lonvia/show-unknown-addr-place
add unknown addr:place to address output
2020-09-23 15:26:12 +02:00
Sarah Hoffmann
c84e7e72f1 add unknown addr:place to address output
When a POI has no addr:street but an addr:place that is not
contained in the name list of the parent place, then remember
this situation and merge the content of addr:place into the
address output.

We don't need to care about translations in this case because
it is obvious that no object with translations exists if the
parent isn't the object named in addr:place.
2020-09-23 11:55:18 +02:00
Sarah Hoffmann
f2ff351da4 Merge pull request #1971 from lonvia/drop-support-for-isin
Drop support for is_in tag
2020-09-23 09:20:35 +02:00
Sarah Hoffmann
c5c242d193 Merge pull request #1972 from lonvia/exclude-unnamed-highway-areas
Exclude unnamed highway areas
2020-09-23 09:20:16 +02:00
Sarah Hoffmann
72193a1c23 exclude unnamed highway areas
These are used to mark large paved areas. Sometimes they exists
together with named regular streets. In such cases the unnamed
area may overshadow the actual street when computing the address
parent. As unnamed highways are not very useful anyway, we
simply remove them from the database.
2020-09-22 21:42:13 +02:00
Sarah Hoffmann
248d6b413a remove test for is_in 2020-09-22 21:36:49 +02:00
Sarah Hoffmann
d04e87fb80 drop suport for is_in tag 2020-09-22 20:26:36 +02:00
Sarah Hoffmann
cc61b74cde Merge pull request #1965 from lonvia/make-addr-tags-searchable
Make addresses searchable by their addr: tags
2020-09-22 20:21:06 +02:00
Sarah Hoffmann
915d362b11 Merge pull request #1966 from lonvia/remove-dead-code
remove dead code
2020-09-21 10:59:58 +02:00
Sarah Hoffmann
f8a5f2964f remove dead code
The SQL query has moved into the addTokensFromDB() funtion.
2020-09-21 10:39:14 +02:00
Sarah Hoffmann
a8dfbcef44 always bind addr:place to place instead of street
If an addr:place is given but no addr:street tag, then bind
the rank 30 object always to a <=25 object, even when there
is none found with the same name.
2020-09-21 10:15:14 +02:00
Sarah Hoffmann
caea14d035 merge addr tags into search_name table
When a place of rank 30 has addr tags that are not covered by the
search terms of the parent, add a separate entry for the POI in
the search_name table that includes the addr tags. We can only
do that with named places. For POIs without a name the housenumber
is used as name. If that is not available either, searching still
won't work.
2020-09-21 10:15:14 +02:00
Sarah Hoffmann
c5fc12e04b Merge pull request #1964 from lonvia/remove-postcodes-with-colon
ignore postcodes with colons
2020-09-19 17:47:43 +02:00
Sarah Hoffmann
731c620e31 ignore postcodes with colons
Colons are used as a delimiter in tiger:left and tiger:right tags
when multiple postcodes are given. Ignore those. This was already
done in the postcode update script. This changes just makes the
two places consistent where postcodes are added.
2020-09-19 17:23:40 +02:00
Ben Antony
0dad470eb9 Update broken links in documentation 2020-09-18 22:52:59 +02:00
Sarah Hoffmann
6947ab3a65 Merge pull request #1963 from lonvia/remove-postcodes-from-search-index
Remove postcodes from search index
2020-09-18 22:41:24 +02:00
Sarah Hoffmann
b219374d36 remove special casing for rank 25 postcodes
They can be computed like any other place.
2020-09-18 16:18:02 +02:00
Sarah Hoffmann
576ee5aaab use same label for all types of postcode in address 2020-09-18 16:17:30 +02:00
Sarah Hoffmann
4c9cfe2532 remove postcodes entirely from indexing
place=postcode places are artificial places that collect addr:postcode
points for aggration. They should neither show up in the address nor
be searchable. That means that there is no need to index them at all.
Only let boundary=postal_code through which define correct areas for
postcodes.
2020-09-18 15:09:35 +02:00
Sarah Hoffmann
7fb62ea904 postal boundary may be imported without name
Postal boundaries usually just have the postcode tag set and are
therefore officially 'nameless'. We want to have them as
boundary=postal_code anyways in order to distiguish them from postcode
points inherited from addr: tags.
2020-09-18 11:33:45 +02:00
Sarah Hoffmann
fe250d3ee8 Merge pull request #1961 from lonvia/set-place-type-for-result-in-address
Use place type of for result object in address parts
2020-09-17 21:23:40 +02:00
Sarah Hoffmann
6f55c67d16 Merge pull request #1960 from lonvia/fix-postcodes-duplicated-by-normalization
Make sure that all postcodes have an entry in the word table
2020-09-17 21:23:23 +02:00
Sarah Hoffmann
3aa6e6a365 Merge pull request #1957 from lonvia/docs-separate-out-deployment
Restructure vagrant scripts and installation documentation
2020-09-17 21:22:48 +02:00
Sarah Hoffmann
b8ced5d96b Merge pull request #1962 from mtmail/travis-ci-without-webserver
travis-ci: we dont need Apache installed
2020-09-17 20:38:17 +02:00
marc tobias
9e21c6a862 travis-ci: we dont need Apache installed 2020-09-17 18:25:23 +02:00
Sarah Hoffmann
fe8566928e use place type of for result object in address parts
Boundaries shound derive the address part type from the
linked place if possible. This was already implemented
for the address objects but not for the address information
from the address itself.

Fixes #1949.
2020-09-17 18:17:01 +02:00
Sarah Hoffmann
3600709116 make sure that all postcodes have an entry in word
It may happen that two different postcodes normalize to exactly
the same token. In that case we still need two different entries
in the word table. Token lookup will then make sure that the correct
one is choosen.

Fixes #1953.
2020-09-17 17:11:22 +02:00
Sarah Hoffmann
df115c73b2 Merge pull request #1945 from mtmail/travis-ubuntu-20
Upgrade Travis-CI from Ubuntu 18 to 20
2020-09-17 16:20:19 +02:00
Sarah Hoffmann
dbe025fe40 docs: fix formatting 2020-09-17 10:16:25 +02:00
Sarah Hoffmann
2b11a47a2f restructure developer's manual
Add a section on setting up the development environment which now
also includes the former chapter on recreating the documentation.
Move the README from test/ into the manual as the new Testing
chapter.
2020-09-17 09:54:46 +02:00
Sarah Hoffmann
fb63a7418e make CentOS 8 the default vagrant script
This puts it in line with the Ubunutu scripts.
2020-09-16 17:34:36 +02:00
Sarah Hoffmann
2029931b95 adapt Ubunut-20 vagrant file to triple webserver config 2020-09-16 16:27:34 +02:00
Sarah Hoffmann
91219bb3dd restructure webserver setup in ubuntu 18 script
Unify the two vagrant scripts for Ubuntu 18. The script can now
be run in three modes: no webserver, with apache, with nginx.
The default mode is to not install any webserver at all. This is
normally sufficient when just developping.

The commit also switches from bento to generic boxes and adds config
for running with a libvirt provider. You need an NFS deamon for
synchronized folders.
2020-09-16 11:19:38 +02:00
Sarah Hoffmann
ab4fe4d58a add 'make serve-global'
This runs the PHP development server in a mode where it listens
globally. This is needed when running inside vagrant and port-forwarding
to the host machine.
2020-09-16 11:15:55 +02:00
Sarah Hoffmann
8ff1f16b7f remove host from default website URL
Just assume that Nominatim runs under the root URL. This is a
more versatile base that also makes 'make serve' work out of the
box.
2020-09-16 11:13:51 +02:00
Sarah Hoffmann
cf4f62c82c docs: move webserver installation into separate chapter 2020-09-15 23:51:25 +02:00
marc tobias
1d2f4264a2 Upgrade Travis-CI from Ubuntu 18 to 20 2020-09-14 12:44:50 +02:00
Sarah Hoffmann
9d506a4afa Merge pull request #1943 from mtmail/no-get-magic-quotes
starting PHP 5.4 get_magic_quotes_gpc() returns false, no need to check
2020-09-14 08:47:49 +02:00
marc tobias
7ac22e9227 starting PHP 5.4 get_magic_quotes_gpc() returns false, no need to check 2020-09-14 00:45:22 +02:00
Sarah Hoffmann
47f2fe724f Merge pull request #1940 from mtmail/north-macedonia
country_name Macedonia => North Macedonia
2020-09-12 16:15:22 +02:00
marc tobias
130571fb29 country_name Macedonia => North Macedonia 2020-09-07 17:24:06 +02:00
Sarah Hoffmann
da7218350b Merge pull request #1936 from lonvia/tweeking-of-ranks
More fine tuning of default rank assignments
2020-09-01 21:23:28 +02:00
Sarah Hoffmann
b6078de6f8 adapt tests to ranking changes 2020-09-01 18:03:17 +02:00
Sarah Hoffmann
07430b0194 tweak size of large POIs
Further reduce the size from which on POIs are no longer bound
to streets but only to larger objects. The point of reference,
of what a largest POI could be that is still bound is JFK airport.
2020-09-01 18:00:40 +02:00
Sarah Hoffmann
fae02fab00 address rank adjustment for addressable boundaries only
Only administrative boundaries with an address rank need
to be adjusted. Otherwise just handle them like any other
object.
2020-09-01 17:59:26 +02:00
Sarah Hoffmann
a68cdc40be improve fallback ranking
Boundaries and places now always get a rank < 26 to make sure that
they do not parent to a street. Skip boundary=place completely
because they will be covered throught the secondary place tag.
2020-09-01 17:55:40 +02:00
Sarah Hoffmann
76b307f42a Merge pull request #1934 from lonvia/fix-deletion-of-large-highway-areas
Do not block deletion of large highway areas
2020-08-28 10:08:42 +02:00
Sarah Hoffmann
6e4b7eb966 do not block deletion of large highway areas
Deletion of areas should only e blocked for addressable features.
Streets and POIs do not have a large impact on updates.
2020-08-28 09:49:21 +02:00
Sarah Hoffmann
770754ae2c place lookup: filter places that have no details
In rare cases search_name might have entries for places for
which we do not return details, in particular for linkees.
Need to remove those entries in the result list before returning
the details.

Fixes #1932.
2020-08-27 09:33:21 +02:00
Sarah Hoffmann
a932855f6f Merge pull request #1931 from lonvia/stable-sort-for-results
Reranking of results must be stable
2020-08-26 20:52:17 +02:00
Sarah Hoffmann
72ee1abc90 ensure that ordering by importance is stable
The initial search results retrieved from the database already come
preordered, either by importnace or by distance. We want to keep
that order if all other things are equal.
2020-08-26 17:42:43 +02:00
Sarah Hoffmann
9e1909643c PlaceLookup should return results in input order 2020-08-26 17:15:11 +02:00
Sarah Hoffmann
77a1329285 Merge pull request #1930 from lonvia/add-support-for-squares
Add support for place=square
2020-08-26 15:11:45 +02:00
Sarah Hoffmann
be6ecc388c add support for place=square
Squares are now addressable (on address level 25) and thus can
be attached to a house number via addr:place. Needed to increase
the rank range for matching up addr:place to 25.
2020-08-26 12:12:52 +02:00
Sarah Hoffmann
13dba94307 do not run rank 0 objects in parallel
Waterways are at address rank 0 and do linking. This might lead to
deadlocks.
2020-08-22 19:51:19 +02:00
Sarah Hoffmann
d51440bb5d Merge pull request #1926 from lonvia/speed-up-location-lookup
Increase splitting for large geometries
2020-08-22 17:04:34 +02:00
Sarah Hoffmann
d730e179bf tests: use larger grid to avoid rouding errors 2020-08-22 16:04:24 +02:00
Sarah Hoffmann
559fe513fa increase splitting for large geometries
When computing the address parts for a geometry, we need to do
a ST_Relates lookup in the location_area_large_* tables. This is
potentially very expensive for geometries with many vertices.
There is already a funtion for splitting large areas to reduce the
impact. This commit reduces the minimum area of a split, effectively
increasing the number of splits.

The effect on database size is minimal (around 3% increase), while
the indexing speed for streets increases by a good 60%.
2020-08-20 16:37:33 +02:00
Sarah Hoffmann
5b20fa7e38 Merge pull request #1923 from lonvia/split-indexing-for-boundries
Rework indexing order of places
2020-08-20 15:03:29 +02:00
Sarah Hoffmann
d16e75de91 Merge pull request #1924 from lonvia/installation-instructions-external-server
docs: installation hints for external databases
2020-08-19 15:17:32 +02:00
Sarah Hoffmann
6fd9994590 docs: installation hints for external databases
Fixes #1882.
2020-08-19 15:03:42 +02:00
Sarah Hoffmann
b9729f3b66 Merge pull request #1921 from lonvia/skip-over-traffic-signs
Remove traffic signs from full styles
2020-08-19 11:50:28 +02:00
Sarah Hoffmann
d6ff7475f1 make sure that addr:* tags can always be searched for
Always add contents of addr:* tags into address part of the search
table, even when there is no corresponding other name. This keeps
search tolerant to the kind of tagging where parts show up in the
address that have no corresponding object in the database or where
it is only an unaddressable object.
2020-08-19 11:44:10 +02:00
Sarah Hoffmann
984979d9bf add migration for new indxing schema 2020-08-18 21:40:53 +02:00
Sarah Hoffmann
73c449b97b switch indexind to address rank
A place needs all lower address rank object indexed to make up
the address. The search rank no longer ensures that as it can have
a different ordering than the address rank.

This switches indexing rank order to address ranks. Non-address
objects (with address rank 0) are indexed together with POIs.
2020-08-18 16:58:58 +02:00
Sarah Hoffmann
1529666232 use only centroid to get parent admin boundaries
Using the full geometry is far too expensive.
2020-08-18 15:17:09 +02:00
Sarah Hoffmann
3816b86a9e nominatim: also index boundaries by rank
We need to make sure that the entry in serach_name from a lower rank
is indeed available.
2020-08-18 15:17:09 +02:00
Sarah Hoffmann
a4b30fc649 index admin boundaries before everything else
Avoids irregularities that might happen because the address
rank of a boundary is changed through linking.
2020-08-18 15:17:09 +02:00
Sarah Hoffmann
fc50eb8688 nominatim: move DBConnection class into its own file 2020-08-18 15:17:09 +02:00
Sarah Hoffmann
071db1fae7 remove traffic signs from full styles
Traffic signs rarely have name and are therefore mostly not
searchable. Remove them completely. Allow street lamps only when
they have a name. Removes about 2M object from a planet instance.
2020-08-15 22:37:45 +02:00
Sarah Hoffmann
a163ea63c5 Merge pull request #1920 from lonvia/remove-linked-place-when-updating
Remove linked_place from extratags when updating
2020-08-14 09:44:56 +02:00
Sarah Hoffmann
e21a707166 remove linked_place from extratags when updating
Before updating an admin boundary we need to make sure that any
artificially generated 'linked_place' entry is removed from the
extratags column. This ensures that the place designation does
not linger when a linked place disappears and that it is updated
when the linking changes.
2020-08-13 16:59:11 +02:00
Sarah Hoffmann
1b484fa90d Merge pull request #1919 from lonvia/tests-for-ranking
More tests and fixes for address rank computation
2020-08-13 09:13:10 +02:00
Sarah Hoffmann
06aa0f0b76 use address rank for address forming when available 2020-08-12 22:22:24 +02:00
Sarah Hoffmann
fb8bb30144 boundary address ranks must not go above 25
Fixes #1914.
2020-08-12 22:22:24 +02:00
Sarah Hoffmann
7429a33818 add simple tests for address rank computation 2020-08-12 22:22:24 +02:00
Sarah Hoffmann
5b9f61cff8 also take place tags into account for address rank
An admin boundary might have a place tag but no matching place node.
We still should use the place value as indicator for the address
rank in this case.
2020-08-12 22:22:24 +02:00
Sarah Hoffmann
4ff0e20e1f Merge pull request #1917 from lonvia/docs-rank-levels
docs: add tables for the meaning of address and search ranks
2020-08-11 15:12:16 +02:00
Sarah Hoffmann
a692bfa8f9 docs: add tables for the meaning of address and search ranks
Also makes tables a bit more readable by adding margins and better
headers.
2020-08-11 11:48:55 +02:00
Sarah Hoffmann
49dd927406 Merge pull request #1918 from lonvia/remove-more-osmosis-init
remove more traces of --osmosis-init switch
2020-08-11 11:48:17 +02:00
Sarah Hoffmann
4b21cc1737 remove more traces of osmosis-init 2020-08-11 10:43:04 +02:00
Sarah Hoffmann
7ae16f7302 Merge pull request #1912 from lonvia/remove-unused-import-update-functions
remove unused functions from setup and update
2020-08-09 09:31:48 +02:00
Sarah Hoffmann
73566a9f15 remove unused functions from setup and update
Removes the defunct --osmosis-init and --no-api switches and the
unsupported (and unnecessary) deduplicate. Also removes
'experimental' from --setup-website as this is a required
function now.
2020-08-06 16:16:35 +02:00
Sarah Hoffmann
fbdf205ab4 Merge pull request #1909 from lonvia/minor-fixes
Make SQL debug statements execute again
2020-08-06 11:07:38 +02:00
Sarah Hoffmann
83b2b4970d Make SQL debug statements execute again
There were some old variable names used that are no longer valid.
Either fix them or remove the statement completely.

Fixes #1907.
2020-08-06 09:29:19 +02:00
Sarah Hoffmann
f29dc7d7ac Merge pull request #1865 from mtmail/how-to-import-test-db
test/README.md - more instructions how to import test db
2020-08-04 14:31:19 +02:00
Sarah Hoffmann
4d5db74c18 Merge pull request #1902 from lonvia/avoid-touching-boundaries-in-addresses
Be more strict what areas make up an address
2020-08-04 14:30:08 +02:00
Sarah Hoffmann
8c7d285e03 Merge pull request #1901 from lonvia/speed-up-indexing
Batch-index places at rank 30
2020-08-04 12:32:16 +02:00
Sarah Hoffmann
1347abb1e7 be more strict what areas make up an address
Exclude boundaries that touch a line in only one point and
that touch areas only along the boundary.

Fixes #1900.
2020-08-04 12:08:50 +02:00
Sarah Hoffmann
2cb85e48b4 adapt test results to new ranking 2020-08-03 16:57:22 +02:00
Sarah Hoffmann
5be084e0f5 indexer: allow batch processing of places
Request and process multiple place_ids at once so that
Postgres can make better use of caching and there are less
transactions running.
2020-08-03 10:32:39 +02:00
Sarah Hoffmann
2323923bec indexer: move progress tracker into separate class 2020-08-03 10:32:39 +02:00
Sarah Hoffmann
0f54d42863 indexer: get rid of special handling of few places
Given that we do not distiribute geometry sectors to threads anymore,
there is no point in this kind of special handling.
2020-08-03 10:32:39 +02:00
Sarah Hoffmann
8201c7f46c Merge pull request #1899 from mtmail/use-new-dsn-format-in-vagrant-md
VAGRANT.md: we use different database DSN syntax these days
2020-08-03 10:26:35 +02:00
marc tobias
ed22d640f4 VAGRANT.md: we use different database DSN syntax these days 2020-07-31 16:52:29 +02:00
marc tobias
01b009ff24 test/README.md - more instructions how to import test db 2020-07-31 16:50:27 +02:00
Sarah Hoffmann
665b90bf5a Merge pull request #1898 from lonvia/show-housenumber-with-housename
make house number reappear in display name on named POIs
2020-07-31 10:11:09 +02:00
Sarah Hoffmann
4e1f245331 make house number reappear in display name on named POIs
After 6cc6cf950c names and house numbers
of POIS got mingled into a single item when creating the display name.
Add the house number as extra information without place_id to avoid
later mangling.
2020-07-30 23:39:55 +02:00
Sarah Hoffmann
f8e1d39208 Merge pull request #1894 from lonvia/fix-hierarchy-by-admin-level
Preserve admin level hierarchy between admin boundaries
2020-07-29 09:20:34 +02:00
Sarah Hoffmann
955dae5d4b Merge pull request #1895 from lonvia/update-less-quiet
Make indexing during updates less quiet
2020-07-29 09:20:14 +02:00
Sarah Hoffmann
b78cd3f4c9 make indexing during updates less quiet
Adjust verbosity behaviour to that of indexing during setup.
2020-07-28 22:35:51 +02:00
Sarah Hoffmann
6a3eb7edf2 preserve admin level hierarchy between admin boundaries
When the address rank of an admin boundary is changed because
of an attached place type, it may happen that the admin_level
hierarchy gets inversed. Avoid that by adjusting the address
rank if an inversion is detected.
2020-07-28 22:15:25 +02:00
Sarah Hoffmann
0a710c0762 Merge pull request #1891 from lonvia/automatic-db-setup
Implicitly connect to database during setup
2020-07-26 16:08:45 +02:00
Sarah Hoffmann
9a204f6284 test: make road really cross the boundary 2020-07-26 15:57:07 +02:00
Sarah Hoffmann
7837970303 remove connect() in update script
This is now implicit.
2020-07-26 12:27:52 +02:00
Sarah Hoffmann
8cd9550295 implicitly connect to database during setup
Make access to the DB object a function, so that the connection
can be opened implicitly when the object is accessed for the first
time. This way we no longer need to check beforehand if a specific
function of the setup needs DB access or not.

Also move the check for the module to the relevant sub step.
2020-07-26 11:56:00 +02:00
Sarah Hoffmann
840c692d5b Merge pull request #1890 from lonvia/add-wiki-tags-to-all-styles
Add wiki tags to all styles
2020-07-25 11:34:51 +02:00
Sarah Hoffmann
05e0d3e2d4 add wiki tags to all styles
wikipedia and wikidata tags are needed to compute the importance
so we need to put them into extra tags for all styles.

Fixes #1885.
2020-07-25 10:00:18 +02:00
Sarah Hoffmann
7429ff9dce forgit to adapt info message 2020-07-18 12:14:51 +02:00
Sarah Hoffmann
29602fe0cf Merge pull request #1874 from joy-yyd/rank_modification
House number search fix for #164
2020-07-18 12:12:37 +02:00
Sarah Hoffmann
1b95ec5591 Merge pull request #1884 from lonvia/fixes-for-webserver-settigns
Small fixes for new webserver settings file
2020-07-18 11:44:01 +02:00
Sarah Hoffmann
3efe0dc8dc move website settings back to settings/
We don't want the settings to become visible when a server is
accidentally configured wrongly.
2020-07-18 11:02:07 +02:00
Sarah Hoffmann
241e4af1b0 log file is a string when not set to false 2020-07-18 11:00:17 +02:00
(Joy) Yuanyue Ding
cac8a8df18 Modifiy the range of address_rank, fix for issue #164 2020-07-08 17:47:38 +02:00
Sarah Hoffmann
1181ceb735 Merge pull request #1873 from lonvia/resurrect-debug-option
Reenable debug parameter
2020-07-08 10:06:49 +02:00
Sarah Hoffmann
f376f45277 default language is a string when not set to false 2020-07-08 08:38:11 +02:00
Sarah Hoffmann
d364afdf3b reenable debug parameter
The parameter got lost when switching to website settings.
Given that the use of a fixed parameter is limited,
debugging output can now only be set via the URL parameter.
2020-07-08 08:32:46 +02:00
Sarah Hoffmann
7ecfcf7eaa Merge pull request #1869 from lonvia/migration-for-setup-website
docs: add migration for new --setup-website step
2020-07-05 21:06:27 +02:00
Sarah Hoffmann
709c9bbe88 Merge pull request #1868 from lonvia/reverse-for-addressable-places-only
reverse: ignore place nodes without an address rank
2020-07-05 21:06:00 +02:00
Sarah Hoffmann
db175f606e docs: add migration for new --setup-website step 2020-07-05 15:46:06 +02:00
Sarah Hoffmann
3a664dc676 reverse: ignore place nodes without an address rank
We already exclude all polygon places without an address
rank. place nodes should also be ignored. This removes
places like locality from the reverse results.

Fixes #1839.
2020-07-05 15:38:49 +02:00
Sarah Hoffmann
5f8d5f10a6 docs: rename documentation chapter
Avoids confusion about this being the documentation itself.
2020-07-05 11:14:48 +02:00
Sarah Hoffmann
f02d4d9677 docs: move external data sources into simple page 2020-07-05 11:13:28 +02:00
Sarah Hoffmann
1889643eca Merge branch 'move-datasources-into-separate-repos' of https://github.com/mtmail/Nominatim into mtmail-move-datasources-into-separate-repos 2020-07-05 10:57:12 +02:00
Sarah Hoffmann
4fc5c2024b Merge pull request #1864 from lonvia/langauge-specific-presuffixes
exclude language-specific name:prefix and name:suffix
2020-07-05 10:54:42 +02:00
Sarah Hoffmann
354487d7f4 Merge pull request #1829 from krahulreddy/websiteSetup
Added setup-website option
2020-07-01 18:11:50 +02:00
Sarah Hoffmann
6478058946 Merge pull request #1857 from mtmail/db-migration-update-functions
Migration.md - admin also need to run recreate db functions
2020-07-01 18:11:14 +02:00
Sarah Hoffmann
8b8dcea3de exclude language-specific name:prefix and name:suffix
There are about 1k suffixes and 20k prefixes with a
language-speicfic variant in use. These should not
show up as names.
2020-07-01 18:00:53 +02:00
marc tobias
64ace51e02 move data-sources/ directory in new git repos 2020-07-01 17:38:44 +02:00
marc tobias
4cb5c67a44 Migration.md - admin also need to run recreate db functions 2020-07-01 16:46:54 +02:00
Sarah Hoffmann
2edefd9e80 sql: fix rank variable type
The rank type needs to match the parameter type of
update_place_diameter().

Fixes #1851.
2020-07-01 15:48:00 +02:00
Sarah Hoffmann
fa8b16e7e7 Merge pull request #1858 from lonvia/update-osm2pgsql
Update osm2pgsql
2020-07-01 11:38:40 +02:00
Sarah Hoffmann
2a953700e2 update osm2pgsql (hang on multipolygons) 2020-06-30 22:39:38 +02:00
Sarah Hoffmann
c1dc835b5c Merge pull request #1852 from osm-search/disable-jit-for-updates
Disable Postgresql jit and parallel processing for osm2pgsql updates
2020-06-28 22:39:44 +02:00
Sarah Hoffmann
214f92c428 make phpcs happy 2020-06-28 18:24:29 +02:00
Sarah Hoffmann
95fc680af9 travis: reduce the size of diff download 2020-06-28 18:21:42 +02:00
Sarah Hoffmann
22d0c6b5e1 travis: run a single round of updates on the Monaco import 2020-06-28 18:09:08 +02:00
Sarah Hoffmann
ff1be13d0e disable JIT and parallel processing for osm2pgsql in updates
This is known to cause issues because of bad indexing
statistics.
2020-06-28 18:06:06 +02:00
K Rahul Reddy
a3201be7e7 Moved settings-frontend to website/ 2020-06-27 10:45:53 +05:30
K Rahul Reddy
37f0b51dff Updated setup.php 2020-06-27 10:45:53 +05:30
K Rahul Reddy
95d2dd74ad Documentation updated 2020-06-27 10:45:53 +05:30
K Rahul Reddy
a175a25e6c Added setup-website to travis.yml 2020-06-27 10:45:51 +05:30
K Rahul Reddy
6c406124dd Added setup-website option 2020-06-27 10:45:51 +05:30
Sarah Hoffmann
0f17529486 Merge pull request #1836 from lonvia/rework-large-location-II
Change processing of place nodes in addresses
2020-06-26 21:36:32 +02:00
Sarah Hoffmann
dd10c867db docs: minor typo and grammar fixes 2020-06-23 23:31:18 +02:00
Sarah Hoffmann
8335dd3aa5 Merge branch 'split-off-test-tool-installation-instructions' of https://github.com/mtmail/Nominatim into mtmail-split-off-test-tool-installation-instructions 2020-06-23 23:25:46 +02:00
Sarah Hoffmann
cd73ac7038 Merge pull request #1841 from mtmail/faq-entry-about-rebuilding-nominatim-so
FAQ addition when to rebuild nominatim.so
2020-06-23 23:20:43 +02:00
Sarah Hoffmann
7cc33a839c Merge pull request #1834 from mtmail/faq-invalid-page-in-block
FAQ entry for PostgreSQL -invalid page in block-
2020-06-23 23:10:26 +02:00
marc tobias
828da6a425 FAQ addition when to rebuild nominatim.so 2020-06-20 04:01:50 +02:00
marc tobias
2e5bdb8794 Put install instructions of test tools into separate docs/ markdown file 2020-06-20 03:48:07 +02:00
marc tobias
f56bac350b FAQ entry for PostgreSQL -invalid page in block- 2020-06-19 21:16:57 +02:00
Sarah Hoffmann
d373f16c81 Merge pull request #1838 from lonvia/make-serve
add 'make serve' command
2020-06-19 20:12:56 +02:00
Sarah Hoffmann
ebffa15c7c add 'make serve' command
Starts up PHP's built-in webserver in the website/ directory.
Useful for testing and development.

See #1831.
2020-06-19 17:35:24 +02:00
Sarah Hoffmann
6e4ee160ee adapt tests to new search ranks 2020-06-17 10:53:11 +02:00
Sarah Hoffmann
a5697c5279 change place node expansion for large area table
So far we've used a buffer around a place node to define its
potential address reach. This had two problems: the buffer was
so large that addresses often contain false positives and the
buffer is really distorted when getting closer to the poles.

Change the buffer here to draw a bounndig box at a certain
distance in meter. This means that we always use the same
box everywhere on the planet and can make the extent much
smaller. Using a box has the advantage that it is much faster
to figure out if a point is within the box.
2020-06-17 10:53:11 +02:00
Sarah Hoffmann
5abec720d8 Merge pull request #1830 from lonvia/docs-for-nominatim-ui
Add usage docs for nominatim-ui
2020-06-17 09:30:29 +02:00
Sarah Hoffmann
84403b47cb add usage docs for nominatim-ui
Includes migration guides for Apache and nginx.
2020-06-13 20:09:20 +02:00
Sarah Hoffmann
4342a539af Merge pull request #1827 from mtmail/centos8-postgresql12-without-proj
Vagrant centos8: proj52 not needed, use postgresql 12/postgis 3.0
2020-06-11 23:12:38 +02:00
Sarah Hoffmann
f4e744ade5 remove version warning from ubuntu 20 installation 2020-06-11 22:46:04 +02:00
Sarah Hoffmann
155d4c5591 cmake: only require php for import and api 2020-06-11 22:44:00 +02:00
marc tobias
f5cbe0e6ba Vagrant centos8: proj52 not needed, use postgresql 12/postgis 3.0 2020-06-11 19:10:38 +02:00
3383 changed files with 4865 additions and 11178 deletions

4
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View 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.

View 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.

View 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.

View 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.

View 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

View 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
View 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

View File

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

View File

@@ -19,7 +19,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
project(nominatim)
set(NOMINATIM_VERSION_MAJOR 3)
set(NOMINATIM_VERSION_MINOR 5)
set(NOMINATIM_VERSION_MINOR 6)
set(NOMINATIM_VERSION_PATCH 0)
set(NOMINATIM_VERSION "${NOMINATIM_VERSION_MAJOR}.${NOMINATIM_VERSION_MINOR}.${NOMINATIM_VERSION_PATCH}")
@@ -79,14 +79,16 @@ endif()
# Setting PHP binary variable as to command line (prevailing) or auto detect
if (NOT PHP_BIN)
find_program (PHP_BIN php)
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()
# sanity check if PHP binary exists
if (NOT EXISTS ${PHP_BIN})
message(FATAL_ERROR "PHP binary not found. Install php or provide location with -DPHP_BIN=/path/php ")
endif()
message (STATUS "Using PHP binary " ${PHP_BIN})
#-----------------------------------------------------------------------------
# import scripts and utilities (importer only)
@@ -119,7 +121,6 @@ if (BUILD_API)
set(WEBSITESCRIPTS
website/deletable.php
website/details.php
website/hierarchy.php
website/lookup.php
website/polygons.php
website/reverse.php
@@ -139,6 +140,16 @@ if (BUILD_API)
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()
#-----------------------------------------------------------------------------

View File

@@ -7,41 +7,6 @@ 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 Bugs...
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
Bug reports that do not include extensive information about your system,
about the problem and about what you have been trying to debug the problem
will be closed.
## Workflow for Pull Requests
We love to get pull requests from you. We operate the "Fork & Pull" model

View File

@@ -1,3 +1,50 @@
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
@@ -11,7 +58,7 @@
* cleanup of partition function
* improve parenting for large POIs
* add support for Postgresql 12 and Postgis 3
* add earlier cleanup when --drop is given, to reduce meory usage
* 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

View File

@@ -1,4 +1,4 @@
[![Build Status](https://travis-ci.org/osm-search/Nominatim.svg?branch=master)](https://travis-ci.org/osm-search/Nominatim)
[![Build Status](https://github.com/osm-search/Nominatim/workflows/CI%20Tests/badge.svg)](https://github.com/osm-search/Nominatim/actions?query=workflow%3A%22CI+Tests%22)
Nominatim
=========

View File

@@ -160,9 +160,9 @@ You can configure/download other Vagrant boxes from [https://app.vagrantup.com/b
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 or restarting necessary.
No data import or restarting necessary.
If the Postgres installation is behind a firewall, you can try

91
Vagrantfile vendored
View File

@@ -4,18 +4,38 @@
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-20.04"
sub.vm.box = "generic/ubuntu2004"
sub.vm.provision :shell do |s|
s.path = "vagrant/Install-on-Ubuntu-20.sh"
s.privileged = false
@@ -23,8 +43,26 @@ Vagrant.configure("2") do |config|
end
end
config.vm.define "ubuntu18", primary: true do |sub|
sub.vm.box = "bento/ubuntu-18.04"
config.vm.define "ubuntu-apache" 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-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
@@ -32,60 +70,41 @@ Vagrant.configure("2") do |config|
end
end
config.vm.define "ubuntu18nginx" do |sub|
sub.vm.box = "bento/ubuntu-18.04"
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-nginx.sh"
s.path = "vagrant/Install-on-Ubuntu-18.sh"
s.privileged = false
s.args = [checkout]
s.args = [checkout, "install-apache"]
end
end
config.vm.define "ubuntu16" do |sub|
sub.vm.box = "bento/ubuntu-16.04"
config.vm.define "ubuntu18-nginx" do |sub|
sub.vm.box = "generic/ubuntu1804"
sub.vm.provision :shell do |s|
s.path = "vagrant/Install-on-Ubuntu-16.sh"
s.path = "vagrant/Install-on-Ubuntu-18.sh"
s.privileged = false
s.args = [checkout]
s.args = [checkout, "install-nginx"]
end
end
config.vm.define "travis" do |sub|
sub.vm.box = "bento/ubuntu-14.04"
config.vm.define "centos7" do |sub|
sub.vm.box = "centos/7"
sub.vm.provision :shell do |s|
s.path = "vagrant/install-on-travis-ci.sh"
s.path = "vagrant/Install-on-Centos-7.sh"
s.privileged = false
s.args = [checkout]
end
end
config.vm.define "centos" do |sub|
sub.vm.box = "centos/7"
sub.vm.provision :shell do |s|
s.path = "vagrant/Install-on-Centos-7.sh"
s.privileged = false
s.args = "yes"
end
sub.vm.synced_folder ".", "/home/vagrant/Nominatim", disabled: true
sub.vm.synced_folder ".", "/vagrant", disabled: true
end
config.vm.define "centos8" do |sub|
sub.vm.box = "generic/centos8"
sub.vm.provision :shell do |s|
s.path = "vagrant/Install-on-Centos-8.sh"
s.privileged = false
s.args = "yes"
s.args = [checkout]
end
sub.vm.synced_folder ".", "/home/vagrant/Nominatim", disabled: true
sub.vm.synced_folder ".", "/vagrant", disabled: true
end
config.vm.provider "virtualbox" do |vb|
vb.gui = false
vb.memory = 2048
vb.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate//vagrant","0"]
end
end

View File

@@ -1,3 +1,5 @@
<?php
require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
@define('CONST_Debug', (isset($_GET['debug']) && $_GET['debug']));
require_once(dirname(dirname(__FILE__)).'/settings/settings-frontend.php');
require_once(CONST_BasePath.'/@script_source@');

View File

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

View File

@@ -1,33 +0,0 @@
-- Script to build a calculated country grid from existing tables
DROP TABLE IF EXISTS tmp_country_osm_grid;
CREATE TABLE tmp_country_osm_grid as select country_name.country_code,st_union(placex.geometry) as geometry from country_name,
placex
where (lower(placex.country_code) = country_name.country_code)
and placex.rank_search < 16 and st_area(placex.geometry) > 0
group by country_name.country_code;
ALTER TABLE tmp_country_osm_grid add column area double precision;
UPDATE tmp_country_osm_grid set area = st_area(geometry::geography);
-- compare old and new
select country_code, round, round(log(area)) from (select distinct country_code,round(log(area)) from country_osm_grid order by country_code) as x
left outer join tmp_country_osm_grid using (country_code) where area is null or round(log(area)) != round;
DROP TABLE IF EXISTS new_country_osm_grid;
CREATE TABLE new_country_osm_grid as select country_code,area,quad_split_geometry(geometry,0.5,20) as geometry from tmp_country_osm_grid;
CREATE INDEX new_idx_country_osm_grid_geometry ON new_country_osm_grid USING GIST (geometry);
-- Sometimes there are problems calculating area due to invalid data - optionally recalc
UPDATE new_country_osm_grid set area = sum from (select country_code,sum(case when st_area(geometry::geography) = 'NaN' THEN 0 ELSE st_area(geometry::geography) END)
from new_country_osm_grid group by country_code) as x where x.country_code = new_country_osm_grid.country_code;
-- compare old and new
select country_code, x.round, y.round from (select distinct country_code,round(log(area)) from country_osm_grid order by country_code) as x
left outer join (select distinct country_code,round(log(area)) from new_country_osm_grid order by country_code) as y
using (country_code) where x.round != y.round;
-- Flip the new table in
BEGIN;
DROP TABLE IF EXISTS country_osm_grid;
ALTER TABLE new_country_osm_grid rename to country_osm_grid;
ALTER INDEX new_idx_country_osm_grid_geometry RENAME TO idx_country_osm_grid_geometry;
COMMIT;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,620 +0,0 @@
#!/usr/bin/python3
# Tiger road data to OSM conversion script
# Creates Karlsruhe-style address ways beside the main way
# based on the Massachusetts GIS script by christopher schmidt
#BUGS:
# On very tight curves, a loop may be generated in the address way.
# It would be nice if the ends of the address ways were not pulled back from dead ends
# Ways that include these mtfccs should not be uploaded
# H1100 Connector
# H3010 Stream/River
# H3013 Braided Stream
# H3020 Canal, Ditch or Aqueduct
# L4130 Point-to-Point Line
# L4140 Property/Parcel Line (Including PLSS)
# P0001 Nonvisible Linear Legal/Statistical Boundary
# P0002 Perennial Shoreline
# P0003 Intermittent Shoreline
# P0004 Other non-visible bounding Edge (e.g., Census water boundary, boundary of an areal feature)
ignoremtfcc = [ "H1100", "H3010", "H3013", "H3020", "L4130", "L4140", "P0001", "P0002", "P0003", "P0004" ]
# Sets the distance that the address ways should be from the main way, in feet.
address_distance = 30
# Sets the distance that the ends of the address ways should be pulled back from the ends of the main way, in feet
address_pullback = 45
import sys, os.path, json
try:
from osgeo import ogr
from osgeo import osr
except:
import ogr
import osr
# https://www.census.gov/geo/reference/codes/cou.html
# tiger_county_fips.json was generated from the following:
# wget https://www2.census.gov/geo/docs/reference/codes/files/national_county.txt
# cat national_county.txt | perl -F, -naE'($F[0] ne 'AS') && $F[3] =~ s/ ((city|City|County|District|Borough|City and Borough|Municipio|Municipality|Parish|Island|Census Area)(?:, |\Z))+//; say qq( "$F[1]$F[2]": "$F[3], $F[0]",)'
json_fh = open(os.path.dirname(sys.argv[0]) + "/tiger_county_fips.json")
county_fips_data = json.load(json_fh)
def parse_shp_for_geom_and_tags( filename ):
#ogr.RegisterAll()
dr = ogr.GetDriverByName("ESRI Shapefile")
poDS = dr.Open( filename )
if poDS == None:
raise "Open failed."
poLayer = poDS.GetLayer( 0 )
fieldNameList = []
layerDefinition = poLayer.GetLayerDefn()
for i in range(layerDefinition.GetFieldCount()):
fieldNameList.append(layerDefinition.GetFieldDefn(i).GetName())
# sys.stderr.write(",".join(fieldNameList))
poLayer.ResetReading()
ret = []
poFeature = poLayer.GetNextFeature()
while poFeature:
tags = {}
# WAY ID
tags["tiger:way_id"] = int( poFeature.GetField("TLID") )
# FEATURE IDENTIFICATION
mtfcc = poFeature.GetField("MTFCC");
if mtfcc != None:
if mtfcc == "L4010": #Pipeline
tags["man_made"] = "pipeline"
if mtfcc == "L4020": #Powerline
tags["power"] = "line"
if mtfcc == "L4031": #Aerial Tramway/Ski Lift
tags["aerialway"] = "cable_car"
if mtfcc == "L4110": #Fence Line
tags["barrier"] = "fence"
if mtfcc == "L4125": #Cliff/Escarpment
tags["natural"] = "cliff"
if mtfcc == "L4165": #Ferry Crossing
tags["route"] = "ferry"
if mtfcc == "R1011": #Railroad Feature (Main, Spur, or Yard)
tags["railway"] = "rail"
ttyp = poFeature.GetField("TTYP")
if ttyp != None:
if ttyp == "S":
tags["service"] = "spur"
if ttyp == "Y":
tags["service"] = "yard"
tags["tiger:ttyp"] = ttyp
if mtfcc == "R1051": #Carline, Streetcar Track, Monorail, Other Mass Transit Rail)
tags["railway"] = "light_rail"
if mtfcc == "R1052": #Cog Rail Line, Incline Rail Line, Tram
tags["railway"] = "incline"
if mtfcc == "S1100":
tags["highway"] = "primary"
if mtfcc == "S1200":
tags["highway"] = "secondary"
if mtfcc == "S1400":
tags["highway"] = "residential"
if mtfcc == "S1500":
tags["highway"] = "track"
if mtfcc == "S1630": #Ramp
tags["highway"] = "motorway_link"
if mtfcc == "S1640": #Service Drive usually along a limited access highway
tags["highway"] = "service"
if mtfcc == "S1710": #Walkway/Pedestrian Trail
tags["highway"] = "path"
if mtfcc == "S1720":
tags["highway"] = "steps"
if mtfcc == "S1730": #Alley
tags["highway"] = "service"
tags["service"] = "alley"
if mtfcc == "S1740": #Private Road for service vehicles (logging, oil, fields, ranches, etc.)
tags["highway"] = "service"
tags["access"] = "private"
if mtfcc == "S1750": #Private Driveway
tags["highway"] = "service"
tags["access"] = "private"
tags["service"] = "driveway"
if mtfcc == "S1780": #Parking Lot Road
tags["highway"] = "service"
tags["service"] = "parking_aisle"
if mtfcc == "S1820": #Bike Path or Trail
tags["highway"] = "cycleway"
if mtfcc == "S1830": #Bridle Path
tags["highway"] = "bridleway"
tags["tiger:mtfcc"] = mtfcc
# FEATURE NAME
if poFeature.GetField("FULLNAME"):
#capitalizes the first letter of each word
name = poFeature.GetField( "FULLNAME" )
tags["name"] = name
#Attempt to guess highway grade
if name[0:2] == "I-":
tags["highway"] = "motorway"
if name[0:3] == "US ":
tags["highway"] = "primary"
if name[0:3] == "US-":
tags["highway"] = "primary"
if name[0:3] == "Hwy":
if tags["highway"] != "primary":
tags["highway"] = "secondary"
# TIGER 2017 no longer contains this field
if 'DIVROAD' in fieldNameList:
divroad = poFeature.GetField("DIVROAD")
if divroad != None:
if divroad == "Y" and "highway" in tags and tags["highway"] == "residential":
tags["highway"] = "tertiary"
tags["tiger:separated"] = divroad
statefp = poFeature.GetField("STATEFP")
countyfp = poFeature.GetField("COUNTYFP")
if (statefp != None) and (countyfp != None):
county_name = county_fips_data.get(statefp + '' + countyfp)
if county_name:
tags["tiger:county"] = county_name
# tlid = poFeature.GetField("TLID")
# if tlid != None:
# tags["tiger:tlid"] = tlid
lfromadd = poFeature.GetField("LFROMADD")
if lfromadd != None:
tags["tiger:lfromadd"] = lfromadd
rfromadd = poFeature.GetField("RFROMADD")
if rfromadd != None:
tags["tiger:rfromadd"] = rfromadd
ltoadd = poFeature.GetField("LTOADD")
if ltoadd != None:
tags["tiger:ltoadd"] = ltoadd
rtoadd = poFeature.GetField("RTOADD")
if rtoadd != None:
tags["tiger:rtoadd"] = rtoadd
zipl = poFeature.GetField("ZIPL")
if zipl != None:
tags["tiger:zip_left"] = zipl
zipr = poFeature.GetField("ZIPR")
if zipr != None:
tags["tiger:zip_right"] = zipr
if mtfcc not in ignoremtfcc:
# COPY DOWN THE GEOMETRY
geom = []
rawgeom = poFeature.GetGeometryRef()
for i in range( rawgeom.GetPointCount() ):
geom.append( (rawgeom.GetX(i), rawgeom.GetY(i)) )
ret.append( (geom, tags) )
poFeature = poLayer.GetNextFeature()
return ret
# ====================================
# to do read .prj file for this data
# Change the Projcs_wkt to match your datas prj file.
# ====================================
projcs_wkt = \
"""GEOGCS["GCS_North_American_1983",
DATUM["D_North_American_1983",
SPHEROID["GRS_1980",6378137,298.257222101]],
PRIMEM["Greenwich",0],
UNIT["Degree",0.017453292519943295]]"""
from_proj = osr.SpatialReference()
from_proj.ImportFromWkt( projcs_wkt )
# output to WGS84
to_proj = osr.SpatialReference()
to_proj.SetWellKnownGeogCS( "EPSG:4326" )
tr = osr.CoordinateTransformation( from_proj, to_proj )
import math
def length(segment, nodelist):
'''Returns the length (in feet) of a segment'''
first = True
distance = 0
lat_feet = 364613 #The approximate number of feet in one degree of latitude
for point in segment:
pointid, (lat, lon) = nodelist[ round_point( point ) ]
if first:
first = False
else:
#The approximate number of feet in one degree of longitute
lrad = math.radians(lat)
lon_feet = 365527.822 * math.cos(lrad) - 306.75853 * math.cos(3 * lrad) + 0.3937 * math.cos(5 * lrad)
distance += math.sqrt(((lat - previous[0])*lat_feet)**2 + ((lon - previous[1])*lon_feet)**2)
previous = (lat, lon)
return distance
def addressways(waylist, nodelist, first_id):
id = first_id
lat_feet = 364613 #The approximate number of feet in one degree of latitude
distance = float(address_distance)
ret = []
for waykey, segments in waylist.items():
waykey = dict(waykey)
rsegments = []
lsegments = []
for segment in segments:
lsegment = []
rsegment = []
lastpoint = None
# Don't pull back the ends of very short ways too much
seglength = length(segment, nodelist)
if seglength < float(address_pullback) * 3.0:
pullback = seglength / 3.0
else:
pullback = float(address_pullback)
if "tiger:lfromadd" in waykey:
lfromadd = waykey["tiger:lfromadd"]
else:
lfromadd = None
if "tiger:ltoadd" in waykey:
ltoadd = waykey["tiger:ltoadd"]
else:
ltoadd = None
if "tiger:rfromadd" in waykey:
rfromadd = waykey["tiger:rfromadd"]
else:
rfromadd = None
if "tiger:rtoadd" in waykey:
rtoadd = waykey["tiger:rtoadd"]
else:
rtoadd = None
if rfromadd != None and rtoadd != None:
right = True
else:
right = False
if lfromadd != None and ltoadd != None:
left = True
else:
left = False
if left or right:
first = True
firstpointid, firstpoint = nodelist[ round_point( segment[0] ) ]
finalpointid, finalpoint = nodelist[ round_point( segment[len(segment) - 1] ) ]
for point in segment:
pointid, (lat, lon) = nodelist[ round_point( point ) ]
#The approximate number of feet in one degree of longitute
lrad = math.radians(lat)
lon_feet = 365527.822 * math.cos(lrad) - 306.75853 * math.cos(3 * lrad) + 0.3937 * math.cos(5 * lrad)
#Calculate the points of the offset ways
if lastpoint != None:
#Skip points too close to start
if math.sqrt((lat * lat_feet - firstpoint[0] * lat_feet)**2 + (lon * lon_feet - firstpoint[1] * lon_feet)**2) < pullback:
#Preserve very short ways (but will be rendered backwards)
if pointid != finalpointid:
continue
#Skip points too close to end
if math.sqrt((lat * lat_feet - finalpoint[0] * lat_feet)**2 + (lon * lon_feet - finalpoint[1] * lon_feet)**2) < pullback:
#Preserve very short ways (but will be rendered backwards)
if (pointid != firstpointid) and (pointid != finalpointid):
continue
X = (lon - lastpoint[1]) * lon_feet
Y = (lat - lastpoint[0]) * lat_feet
if Y != 0:
theta = math.pi/2 - math.atan( X / Y)
Xp = math.sin(theta) * distance
Yp = math.cos(theta) * distance
else:
Xp = 0
if X > 0:
Yp = -distance
else:
Yp = distance
if Y > 0:
Xp = -Xp
else:
Yp = -Yp
if first:
first = False
dX = - (Yp * (pullback / distance)) / lon_feet #Pull back the first point
dY = (Xp * (pullback / distance)) / lat_feet
if left:
lpoint = (lastpoint[0] + (Yp / lat_feet) - dY, lastpoint[1] + (Xp / lon_feet) - dX)
lsegment.append( (id, lpoint) )
id += 1
if right:
rpoint = (lastpoint[0] - (Yp / lat_feet) - dY, lastpoint[1] - (Xp / lon_feet) - dX)
rsegment.append( (id, rpoint) )
id += 1
else:
#round the curves
if delta[1] != 0:
theta = abs(math.atan(delta[0] / delta[1]))
else:
theta = math.pi / 2
if Xp != 0:
theta = theta - abs(math.atan(Yp / Xp))
else: theta = theta - math.pi / 2
r = 1 + abs(math.tan(theta/2))
if left:
lpoint = (lastpoint[0] + (Yp + delta[0]) * r / (lat_feet * 2), lastpoint[1] + (Xp + delta[1]) * r / (lon_feet * 2))
lsegment.append( (id, lpoint) )
id += 1
if right:
rpoint = (lastpoint[0] - (Yp + delta[0]) * r / (lat_feet * 2), lastpoint[1] - (Xp + delta[1]) * r / (lon_feet * 2))
rsegment.append( (id, rpoint) )
id += 1
delta = (Yp, Xp)
lastpoint = (lat, lon)
#Add in the last node
dX = - (Yp * (pullback / distance)) / lon_feet
dY = (Xp * (pullback / distance)) / lat_feet
if left:
lpoint = (lastpoint[0] + (Yp + delta[0]) / (lat_feet * 2) + dY, lastpoint[1] + (Xp + delta[1]) / (lon_feet * 2) + dX )
lsegment.append( (id, lpoint) )
id += 1
if right:
rpoint = (lastpoint[0] - Yp / lat_feet + dY, lastpoint[1] - Xp / lon_feet + dX)
rsegment.append( (id, rpoint) )
id += 1
#Generate the tags for ways and nodes
zipr = ''
zipl = ''
name = ''
county = ''
if "tiger:zip_right" in waykey:
zipr = waykey["tiger:zip_right"]
if "tiger:zip_left" in waykey:
zipl = waykey["tiger:zip_left"]
if "name" in waykey:
name = waykey["name"]
if "tiger:county" in waykey:
county = waykey["tiger:county"]
if "tiger:separated" in waykey: # No longer set in Tiger-2017
separated = waykey["tiger:separated"]
else:
separated = "N"
#Write the nodes of the offset ways
if right:
rlinestring = [];
for i, point in rsegment:
rlinestring.append( "%f %f" % (point[1], point[0]) )
if left:
llinestring = [];
for i, point in lsegment:
llinestring.append( "%f %f" % (point[1], point[0]) )
if right:
rsegments.append( rsegment )
if left:
lsegments.append( lsegment )
rtofromint = right #Do the addresses convert to integers?
ltofromint = left #Do the addresses convert to integers?
if right:
try: rfromint = int(rfromadd)
except:
print("Non integer address: %s" % rfromadd)
rtofromint = False
try: rtoint = int(rtoadd)
except:
print("Non integer address: %s" % rtoadd)
rtofromint = False
if left:
try: lfromint = int(lfromadd)
except:
print("Non integer address: %s" % lfromadd)
ltofromint = False
try: ltoint = int(ltoadd)
except:
print("Non integer address: %s" % ltoadd)
ltofromint = False
if right:
id += 1
interpolationtype = "all";
if rtofromint:
if (rfromint % 2) == 0 and (rtoint % 2) == 0:
if separated == "Y": #Doesn't matter if there is another side
interpolationtype = "even";
elif ltofromint and (lfromint % 2) == 1 and (ltoint % 2) == 1:
interpolationtype = "even";
elif (rfromint % 2) == 1 and (rtoint % 2) == 1:
if separated == "Y": #Doesn't matter if there is another side
interpolationtype = "odd";
elif ltofromint and (lfromint % 2) == 0 and (ltoint % 2) == 0:
interpolationtype = "odd";
ret.append( "SELECT tiger_line_import(ST_GeomFromText('LINESTRING(%s)',4326), %s, %s, %s, %s, %s, %s);" %
( ",".join(rlinestring), sql_quote(rfromadd), sql_quote(rtoadd), sql_quote(interpolationtype), sql_quote(name), sql_quote(county), sql_quote(zipr) ) )
if left:
id += 1
interpolationtype = "all";
if ltofromint:
if (lfromint % 2) == 0 and (ltoint % 2) == 0:
if separated == "Y":
interpolationtype = "even";
elif rtofromint and (rfromint % 2) == 1 and (rtoint % 2) == 1:
interpolationtype = "even";
elif (lfromint % 2) == 1 and (ltoint % 2) == 1:
if separated == "Y":
interpolationtype = "odd";
elif rtofromint and (rfromint %2 ) == 0 and (rtoint % 2) == 0:
interpolationtype = "odd";
ret.append( "SELECT tiger_line_import(ST_GeomFromText('LINESTRING(%s)',4326), %s, %s, %s, %s, %s, %s);" %
( ",".join(llinestring), sql_quote(lfromadd), sql_quote(ltoadd), sql_quote(interpolationtype), sql_quote(name), sql_quote(county), sql_quote(zipl) ) )
return ret
def sql_quote( string ):
return "'" + string.replace("'", "''") + "'"
def unproject( point ):
pt = tr.TransformPoint( point[0], point[1] )
return (pt[1], pt[0])
def round_point( point, accuracy=8 ):
return tuple( [ round(x,accuracy) for x in point ] )
def compile_nodelist( parsed_gisdata, first_id=1 ):
nodelist = {}
i = first_id
for geom, tags in parsed_gisdata:
if len( geom )==0:
continue
for point in geom:
r_point = round_point( point )
if r_point not in nodelist:
nodelist[ r_point ] = (i, unproject( point ))
i += 1
return (i, nodelist)
def adjacent( left, right ):
left_left = round_point(left[0])
left_right = round_point(left[-1])
right_left = round_point(right[0])
right_right = round_point(right[-1])
return ( left_left == right_left or
left_left == right_right or
left_right == right_left or
left_right == right_right )
def glom( left, right ):
left = list( left )
right = list( right )
left_left = round_point(left[0])
left_right = round_point(left[-1])
right_left = round_point(right[0])
right_right = round_point(right[-1])
if left_left == right_left:
left.reverse()
return left[0:-1] + right
if left_left == right_right:
return right[0:-1] + left
if left_right == right_left:
return left[0:-1] + right
if left_right == right_right:
right.reverse()
return left[0:-1] + right
raise 'segments are not adjacent'
def glom_once( segments ):
if len(segments)==0:
return segments
unsorted = list( segments )
x = unsorted.pop(0)
while len( unsorted ) > 0:
n = len( unsorted )
for i in range(0, n):
y = unsorted[i]
if adjacent( x, y ):
y = unsorted.pop(i)
x = glom( x, y )
break
# Sorted and unsorted lists have no adjacent segments
if len( unsorted ) == n:
break
return x, unsorted
def glom_all( segments ):
unsorted = segments
chunks = []
while unsorted != []:
chunk, unsorted = glom_once( unsorted )
chunks.append( chunk )
return chunks
def compile_waylist( parsed_gisdata ):
waylist = {}
#Group by tiger:way_id
for geom, tags in parsed_gisdata:
way_key = tags.copy()
way_key = ( way_key['tiger:way_id'], tuple( [(k,v) for k,v in way_key.items()] ) )
if way_key not in waylist:
waylist[way_key] = []
waylist[way_key].append( geom )
ret = {}
for (way_id, way_key), segments in waylist.items():
ret[way_key] = glom_all( segments )
return ret
def shape_to_sql( shp_filename, sql_filename ):
print("parsing shpfile %s" % shp_filename)
parsed_features = parse_shp_for_geom_and_tags( shp_filename )
print("compiling nodelist")
i, nodelist = compile_nodelist( parsed_features )
print("compiling waylist")
waylist = compile_waylist( parsed_features )
print("preparing address ways")
sql_lines = addressways(waylist, nodelist, i)
print("writing %s" % sql_filename)
fp = open( sql_filename, "w" )
fp.write( "\n".join( sql_lines ) )
fp.close()
if __name__ == '__main__':
import sys, os.path
if len(sys.argv) < 3:
print("%s input.shp output.sql" % sys.argv[0])
sys.exit()
shp_filename = sys.argv[1]
sql_filename = sys.argv[2]
shape_to_sql(shp_filename, sql_filename)

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,951 +0,0 @@
#!/usr/bin/perl -w
# mysql2pgsql
# MySQL to PostgreSQL dump file converter
#
# For usage: perl mysql2pgsql.perl --help
#
# ddl statments are changed but none or only minimal real data
# formatting are done.
# data consistency is up to the DBA.
#
# (c) 2004-2007 Jose M Duarte and Joseph Speigle ... gborg
#
# (c) 2000-2004 Maxim Rudensky <fonin@omnistaronline.com>
# (c) 2000 Valentine Danilchuk <valdan@ziet.zhitomir.ua>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
# must display the following acknowledgement:
# This product includes software developed by the Max Rudensky
# and its contributors.
# 4. Neither the name of the author nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
use Getopt::Long;
use POSIX;
use strict;
use warnings;
# main sections
# -------------
# 1 variable declarations
# 2 subroutines
# 3 get commandline options and specify help statement
# 4 loop through file and process
# 5. print_plpgsql function prototype
#################################################################
# 1. variable declarations
#################################################################
# command line options
my( $ENC_IN, $ENC_OUT, $PRESERVE_CASE, $HELP, $DEBUG, $SCHEMA, $LOWERCASE, $CHAR2VARCHAR, $NODROP, $SEP_FILE, $opt_debug, $opt_help, $opt_schema, $opt_preserve_case, $opt_char2varchar, $opt_nodrop, $opt_sepfile, $opt_enc_in, $opt_enc_out );
# variables for constructing pre-create-table entities
my $pre_create_sql=''; # comments, 'enum' constraints preceding create table statement
my $auto_increment_seq= ''; # so we can easily substitute it if we need a default value
my $create_sql=''; # all the datatypes in the create table section
my $post_create_sql=''; # create indexes, foreign keys, table comments
my $function_create_sql = ''; # for the set (function,trigger) and CURRENT_TIMESTAMP ( function,trigger )
# constraints
my ($type, $column_valuesStr, @column_values, $value );
my %constraints=(); # holds values constraints used to emulate mysql datatypes (e.g. year, set)
# datatype conversion variables
my ( $index,$seq);
my ( $column_name, $col, $quoted_column);
my ( @year_holder, $year, $constraint_table_name);
my $table=""; # table_name for create sql statements
my $table_no_quotes=""; # table_name for create sql statements
my $sl = '^\s+\w+\s+'; # matches the column name
my $tables_first_timestamp_column= 1; # decision to print warnings about default_timestamp not being in postgres
my $mysql_numeric_datatypes = "TINYINT|SMALLINT|MEDIUMINT|INT|INTEGER|BIGINT|REAL|DOUBLE|FLOAT|DECIMAL|NUMERIC";
my $mysql_datetime_datatypes = "|DATE|TIME|TIMESTAMP|DATETIME|YEAR";
my $mysql_text_datatypes = "CHAR|VARCHAR|BINARY|VARBINARY|TINYBLOB|BLOB|MEDIUMBLOB|LONGBLOB|TINYTEXT|TEXT|MEDIUMTEXT|LONGTEXT|ENUM|SET";
my $mysql_datatypesStr = $mysql_numeric_datatypes . "|". $mysql_datetime_datatypes . "|". $mysql_text_datatypes ;
# handling INSERT INTO statements
my $rowRe = qr{
\( # opening parens
( # (start capture)
(?: # (start group)
' # string start
[^'\\]* # up to string-end or backslash (escape)
(?: # (start group)
\\. # gobble escaped character
[^'\\]* # up to string-end of backslash
)* # (end group, repeat zero or more)
' # string end
| # (OR)
.*? # everything else (not strings)
)* # (end group, repeat zero or more)
) # (end capture)
\) # closing parent
}x;
my ($insert_table, $valueString);
#
########################################################
# 2. subroutines
#
# get_identifier
# print_post_create_sql()
# quote_and_lc()
# make_plpgsql($table,$column_name) -- at end of file
########################################################
# returns an identifier with the given suffix doing controlled
# truncation if necessary
sub get_identifier($$$) {
my ($table, $col, $suffix) = @_;
my $name = '';
$table=~s/\"//g; # make sure that $table doesn't have quotes so we don't end up with redundant quoting
# in the case of multiple columns
my @cols = split(/,/,$col);
$col =~ s/,//g;
# in case all columns together too long we have to truncate them
if (length($col) > 55) {
my $totaltocut = length($col)-55;
my $tocut = ceil($totaltocut / @cols);
@cols = map {substr($_,0,abs(length($_)-$tocut))} @cols;
$col="";
foreach (@cols){
$col.=$_;
}
}
my $max_table_length = 63 - length("_${col}_$suffix");
if (length($table) > $max_table_length) {
$table = substr($table, length($table) - $max_table_length, $max_table_length);
}
return quote_and_lc("${table}_${col}_${suffix}");
}
#
#
# called when we encounter next CREATE TABLE statement
# also called at EOF to print out for last table
# prints comments, indexes, foreign key constraints (the latter 2 possibly to a separate file)
sub print_post_create_sql() {
my ( @create_idx_comments_constraints_commandsArr, $stmts, $table_field_combination);
my %stmts;
# loop to check for duplicates in $post_create_sql
# Needed because of duplicate key declarations ( PRIMARY KEY and KEY), auto_increment columns
@create_idx_comments_constraints_commandsArr = split(';\n?', $post_create_sql);
if ($SEP_FILE) {
open(SEP_FILE, ">>:encoding($ENC_OUT)", $SEP_FILE) or die "Unable to open $SEP_FILE for output: $!\n";
}
foreach (@create_idx_comments_constraints_commandsArr) {
if (m/CREATE INDEX "*(\S+)"*\s/i) { # CREATE INDEX korean_english_wordsize_idx ON korean_english USING btree (wordsize);
$table_field_combination = $1;
# if this particular table_field_combination was already used do not print the statement:
if ($SEP_FILE) {
print SEP_FILE "$_;\n" if !defined($stmts{$table_field_combination});
} else {
print OUT "$_;\n" if !defined($stmts{$table_field_combination});
}
$stmts{$table_field_combination} = 1;
}
elsif (m/COMMENT/i) { # COMMENT ON object IS 'text'; but comment may be part of table name so use 'elsif'
print OUT "$_;\n"
} else { # foreign key constraint or comments (those preceded by -- )
if ($SEP_FILE) {
print SEP_FILE "$_;\n";
} else {
print OUT "$_;\n"
}
}
}
if ($SEP_FILE) {
close SEP_FILE;
}
$post_create_sql='';
# empty %constraints for next " create table" statement
}
# quotes a string or a multicolumn string (comma separated)
# and optionally lowercase (if LOWERCASE is set)
# lowercase .... if user wants default postgres behavior
# quotes .... to preserve keywords and to preserve case when case-sensitive tables are to be used
sub quote_and_lc($)
{
my $col = shift;
if ($LOWERCASE) {
$col = lc($col);
}
if ($col =~ m/,/) {
my @cols = split(/,\s?/, $col);
@cols = map {"\"$_\""} @cols;
return join(', ', @cols);
} else {
return "\"$col\"";
}
}
########################################################
# 3. get commandline options and maybe print help
########################################################
GetOptions("help", "debug"=> \$opt_debug, "schema=s" => \$SCHEMA, "preserve_case" => \$opt_preserve_case, "char2varchar" => \$opt_char2varchar, "nodrop" => \$opt_nodrop, "sepfile=s" => \$opt_sepfile, "enc_in=s" => \$opt_enc_in, "enc_out=s" => \$opt_enc_out );
$HELP = $opt_help || 0;
$DEBUG = $opt_debug || 0;
$PRESERVE_CASE = $opt_preserve_case || 0;
if ($PRESERVE_CASE == 1) { $LOWERCASE = 0; }
else { $LOWERCASE = 1; }
$CHAR2VARCHAR = $opt_char2varchar || 0;
$NODROP = $opt_nodrop || 0;
$SEP_FILE = $opt_sepfile || 0;
$ENC_IN = $opt_enc_in || 'utf8';
$ENC_OUT = $opt_enc_out || 'utf8';
if (($HELP) || ! defined($ARGV[0]) || ! defined($ARGV[1])) {
print "\n\nUsage: perl $0 {--help --debug --preserve_case --char2varchar --nodrop --schema --sepfile --enc_in --enc_out } mysql.sql pg.sql\n";
print "\t* OPTIONS WITHOUT ARGS\n";
print "\t--help: prints this message \n";
print "\t--debug: output the commented-out mysql line above the postgres line in pg.sql \n";
print "\t--preserve_case: prevents automatic case-lowering of column and table names\n";
print "\t\tIf you want to preserve case, you must set this flag. For example,\n";
print "\t\tIf your client application quotes table and column-names and they have cases in them, set this flag\n";
print "\t--char2varchar: converts all char fields to varchar\n";
print "\t--nodrop: strips out DROP TABLE statements\n";
print "\t\totherise harmless warnings are printed by psql when the dropped table does not exist\n";
print "\n\t* OPTIONS WITH ARGS\n";
print "\t--schema: outputs a line into the postgres sql file setting search_path \n";
print "\t--sepfile: output foreign key constraints and indexes to a separate file so that it can be\n";
print "\t\timported after large data set is inserted from another dump file\n";
print "\t--enc_in: encoding of mysql in file (default utf8) \n";
print "\t--enc_out: encoding of postgres out file (default utf8) \n";
print "\n\t* REQUIRED ARGUMENTS\n";
if (defined ($ARGV[0])) {
print "\tmysql.sql ($ARGV[0])\n";
} else {
print "\tmysql.sql (undefined)\n";
}
if (defined ($ARGV[1])) {
print "\tpg.sql ($ARGV[1])\n";
} else {
print "\tpg.sql (undefined)\n";
}
print "\n";
exit 1;
}
########################################################
# 4. process through mysql_dump.sql file
# in a big loop
########################################################
# open in and out files
open(IN,"<:encoding($ENC_IN)", $ARGV[0]) || die "can't open mysql dump file $ARGV[0]";
open(OUT,">:encoding($ENC_OUT)", $ARGV[1]) || die "can't open pg dump file $ARGV[1]";
# output header
print OUT "--\n";
print OUT "-- Generated from mysql2pgsql.perl\n";
print OUT "-- http://gborg.postgresql.org/project/mysql2psql/\n";
print OUT "-- (c) 2001 - 2007 Jose M. Duarte, Joseph Speigle\n";
print OUT "--\n";
print OUT "\n";
print OUT "-- warnings are printed for drop tables if they do not exist\n";
print OUT "-- please see http://archives.postgresql.org/pgsql-novice/2004-10/msg00158.php\n\n";
print OUT "-- ##############################################################\n";
if ($SCHEMA ) {
print OUT "set search_path='" . $SCHEMA . "'\\g\n" ;
}
# loop through mysql file on a per-line basis
while(<IN>) {
############## flow #########################
# (the lines are directed to different string variables at different times)
#
# handle drop table , unlock, connect statements
# if ( start of create table) {
# print out post_create table (indexes, foreign key constraints, comments from previous table)
# add drop table statement if !$NODROP to pre_create_sql
# next;
# }
# else if ( inside create table) {
# add comments in this portion to create_sql
# if ( end of create table) {
# delete mysql-unique CREATE TABLE commands
# print pre_create_sql
# print the constraint tables for set and year datatypes
# print create_sql
# print function_create_sql (this is for the enum columns only)
# next;
# }
# do substitutions
# -- NUMERIC DATATYPES
# -- CHARACTER DATATYPES
# -- DATE AND TIME DATATYPES
# -- KEY AND UNIQUE CREATIONS
# and append them to create_sql
# } else {
# print inserts on-the-spot (this script only changes default timestamp of 0000-00-00)
# }
# LOOP until EOF
#
########################################################
if (!/^\s*insert into/i) { # not inside create table so don't worry about data corruption
s/`//g; # '`pgsql uses no backticks to denote table name (CREATE TABLE `sd`) or around field
# and table names like mysql
# doh! we hope all dashes and special chars are caught by the regular expressions :)
}
if (/^\s*USE\s*([^;]*);/) {
print OUT "\\c ". $1;
next;
}
if (/^(UN)?LOCK TABLES/i || /drop\s+table/i ) {
# skip
# DROP TABLE is added when we see the CREATE TABLE
next;
}
if (/(create\s+table\s+)([-_\w]+)\s/i) { # example: CREATE TABLE `english_english`
print_post_create_sql(); # for last table
$tables_first_timestamp_column= 1; # decision to print warnings about default_timestamp not being in postgres
$create_sql = '';
$table_no_quotes = $2 ;
$table=quote_and_lc($2);
if ( !$NODROP ) { # always print drop table if user doesn't explicitly say not to
# to drop a table that is referenced by a view or a foreign-key constraint of another table,
# CASCADE must be specified. (CASCADE will remove a dependent view entirely, but in the
# in the foreign-key case it will only remove the foreign-key constraint, not the other table entirely.)
# (source: 8.1.3 docs, section "drop table")
warn "table $table will be dropped CASCADE\n";
$pre_create_sql .= "DROP TABLE $table CASCADE;\n"; # custom dumps may be missing the 'dump' commands
}
s/(create\s+table\s+)([-_\w]+)\s/$1 $table /i;
if ($DEBUG) {
$create_sql .= '-- ' . $_;
}
$create_sql .= $_;
next;
}
if ($create_sql ne "") { # we are inside create table statement so lets process datatypes
# print out comments or empty lines in context
if ($DEBUG) {
$create_sql .= '-- ' . $_;
}
if (/^#/ || /^$/ || /^\s*--/) {
s/^#/--/; # Two hyphens (--) is the SQL-92 standard indicator for comments
$create_sql.=$_;
next;
}
if (/\).*;/i) { # end of create table squence
s/INSERT METHOD[=\s+][^;\s]+//i;
s/PASSWORD=[^;\s]+//i;
s/ROW_FORMAT=(?:DEFAULT|DYNAMIC|FIXED|COMPRESSED|REDUNDANT|COMPACT)+//i;
s/KEY_BLOCK_SIZE=8//i;
s/DELAY KEY WRITE=[^;\s]+//i;
s/INDEX DIRECTORY[=\s+][^;\s]+//i;
s/DATA DIRECTORY=[^;\s]+//i;
s/CONNECTION=[^;\s]+//i;
s/CHECKSUM=[^;\s]+//i;
s/Type=[^;\s]+//i; # ISAM , # older versions
s/COLLATE=[^;\s]+//i; # table's collate
s/COLLATE\s+[^;\s]+//i; # table's collate
# possible AUTO_INCREMENT starting index, it is used in mysql 5.0.26, not sure since which version
if (/AUTO_INCREMENT=(\d+)/i) {
# should take < ---- ) ENGINE=MyISAM AUTO_INCREMENT=16 DEFAULT CHARSET=latin1;
# and should ouput ---> CREATE SEQUENCE "rhm_host_info_id_seq" START WITH 16;
my $start_value = $1;
print $auto_increment_seq . "--\n";
# print $pre_create_sql . "--\n";
$pre_create_sql =~ s/(CREATE SEQUENCE $auto_increment_seq )/$1 START WITH $start_value /;
}
s/AUTO_INCREMENT=\d+//i;
s/PACK_KEYS=\d//i; # mysql 5.0.22
s/DEFAULT CHARSET=[^;\s]+//i; # my mysql version is 4.1.11
s/ENGINE\s*=\s*[^;\s]+//i; # my mysql version is 4.1.11
s/ROW_FORMAT=[^;\s]+//i; # my mysql version is 5.0.22
s/KEY_BLOCK_SIZE=8//i;
s/MIN_ROWS=[^;\s]+//i;
s/MAX_ROWS=[^;\s]+//i;
s/AVG_ROW_LENGTH=[^;\s]+//i;
if (/COMMENT='([^']*)'/) { # ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='must be country zones';
$post_create_sql.="COMMENT ON TABLE $table IS '$1'\;"; # COMMENT ON table_name IS 'text';
s/COMMENT='[^']*'//i;
}
$create_sql =~ s/,$//g; # strip last , inside create table
# make sure we end in a comma, as KEY statments are turned
# into post_create_sql indices
# they often are the last line so leaving a 'hanging comma'
my @array = split("\n", $create_sql);
for (my $a = $#array; $a >= 0; $a--) { #loop backwards
if ($a == $#array && $array[$a] =~ m/,\s*$/) { # for last line
$array[$a] =~ s/,\s*$//;
next;
}
if ($array[$a] !~ m/create table/i) { # i.e. if there was more than one column in table
if ($a != $#array && $array[$a] !~ m/,\s*$/ ) { # for second to last
$array[$a] =~ s/$/,/;
last;
}
elsif ($a != $#array && $array[$a] =~ m/,\s*$/ ) { # for second to last
last;
}
}
}
$create_sql = join("\n", @array) . "\n";
$create_sql .= $_;
# put comments out first
print OUT $pre_create_sql;
# create separate table to reference and to hold mysql's possible set data-type
# values. do that table's creation before create table
# definition
foreach $column_name (keys %constraints) {
$type=$constraints{$column_name}{'type'};
$column_valuesStr = $constraints{$column_name}{'values'};
$constraint_table_name = get_identifier(${table},${column_name} ,"constraint_table");
if ($type eq 'set') {
print OUT qq~DROP TABLE $constraint_table_name CASCADE\\g\n~ ;
print OUT qq~create table $constraint_table_name ( set_values varchar UNIQUE)\\g\n~ ;
$function_create_sql .= make_plpgsql($table,$column_name);
} elsif ($type eq 'year') {
print OUT qq~DROP TABLE $constraint_table_name CASCADE\\g\n~ ;
print OUT qq~create table $constraint_table_name ( year_values varchar UNIQUE)\\g\n~ ;
}
@column_values = split /,/, $column_valuesStr;
foreach $value (@column_values) {
print OUT qq~insert into $constraint_table_name values ( $value )\\g\n~; # ad ' for ints and varchars
}
}
$create_sql =~ s/double double/double precision/g;
# print create table and reset create table vars
# when moving from each "create table" to "insert" part of dump
print OUT $create_sql;
print OUT $function_create_sql;
$pre_create_sql="";
$auto_increment_seq="";
$create_sql="";
$function_create_sql='';
%constraints=();
# the post_create_sql for this table is output at the beginning of the next table def
# in case we want to make indexes after doing inserting
next;
}
if (/^\s*(\w+)\s+.*COMMENT\s*'([^']*)'/) { #`zone_country_id` int(11) COMMENT 'column comment here',
$quoted_column=quote_and_lc($1);
$post_create_sql.="COMMENT ON COLUMN $table"."."." $quoted_column IS '$2'\;"; # COMMENT ON table_name.column_name IS 'text';
s/COMMENT\s*'[^']*'//i;
}
# NUMERIC DATATYPES
#
# auto_increment -> sequences
# UNSIGNED conversions
# TINYINT
# SMALLINT
# MEDIUMINT
# INT, INTEGER
# BIGINT
#
# DOUBLE [PRECISION], REAL
# DECIMAL(M,D), NUMERIC(M,D)
# FLOAT(p)
# FLOAT
s/(\w*int)\(\d+\)/$1/g; # hack of the (n) stuff for e.g. mediumint(2) int(3)
if (/^(\s*)(\w+)\s*.*numeric.*auto_increment/i) { # int,auto_increment -> serial
$seq = get_identifier($table, $2, 'seq');
$quoted_column=quote_and_lc($2);
# Smash datatype to int8 and autogenerate the sequence.
s/^(\s*)(\w+)\s*.*NUMERIC(.*)auto_increment([^,]*)/$1 $quoted_column serial8 $4/ig;
$create_sql.=$_;
next;
}
if (/^\s*(\w+)\s+.*int.*auto_increment/i) { # example: data_id mediumint(8) unsigned NOT NULL auto_increment,
$seq = get_identifier($table, $1, 'seq');
$quoted_column=quote_and_lc($1);
s/(\s*)(\w+)\s+.*int.*auto_increment([^,]*)/$1 $quoted_column serial8 $3/ig;
$create_sql.=$_;
next;
}
# convert UNSIGNED to CHECK constraints
if (m/^(\s*)(\w+)\s+((float|double precision|double|real|decimal|numeric))(.*)unsigned/i) {
$quoted_column = quote_and_lc($2);
s/^(\s*)(\w+)\s+((float|double precision|double|real|decimal|numeric))(.*)unsigned/$1 $quoted_column $3 $4 CHECK ($quoted_column >= 0)/i;
}
# example: `wordsize` tinyint(3) unsigned default NULL,
if (m/^(\s+)(\w+)\s+(\w+)\s+unsigned/i) {
$quoted_column=quote_and_lc($2);
s/^(\s+)(\w+)\s+(\w+)\s+unsigned/$1 $quoted_column $3 CHECK ($quoted_column >= 0)/i;
}
if (m/^(\s*)(\w+)\s+(bigint.*)unsigned/) {
$quoted_column=quote_and_lc($2);
# see http://archives.postgresql.org/pgsql-general/2005-07/msg01178.php
# and see http://www.postgresql.org/docs/8.2/interactive/datatype-numeric.html
# see http://dev.mysql.com/doc/refman/5.1/en/numeric-types.html max size == 20 digits
s/^(\s*)(\w+)\s+bigint(.*)unsigned/$1 $quoted_column NUMERIC (20,0) CHECK ($quoted_column >= 0)/i;
}
# int type conversion
# TINYINT (signed) -128 to 127 (unsigned) 0 255
# SMALLINT A small integer. The signed range is -32768 to 32767. The unsigned range is 0 to 65535.
# MEDIUMINT A medium-sized integer. The signed range is -8388608 to 8388607. The unsigned range is 0 to 16777215.
# INT A normal-size integer. The signed range is -2147483648 to 2147483647. The unsigned range is 0 to 4294967295.
# BIGINT The signed range is -9223372036854775808 to 9223372036854775807. The unsigned range is 0 to 18446744073709551615
# for postgres see http://www.postgresql.org/docs/8.2/static/datatype-numeric.html#DATATYPE-INT
s/^(\s+"*\w+"*\s+)tinyint/$1 smallint/i;
s/^(\s+"*\w+"*\s+)mediumint/$1 integer/i;
# the floating point types
# double -> double precision
# double(n,m) -> double precision
# float - no need for conversion
# float(n) - no need for conversion
# float(n,m) -> double precision
s/(^\s*\w+\s+)double(\(\d+,\d+\))?/$1float/i;
s/float(\(\d+,\d+\))/float/i;
#
# CHARACTER TYPES
#
# set
# enum
# binary(M), VARBINARy(M), tinyblob, tinytext,
# bit
# char(M), varchar(M)
# blob -> text
# mediumblob
# longblob, longtext
# text -> text
# mediumtext
# longtext
# mysql docs: A BLOB is a binary large object that can hold a variable amount of data.
# set
# For example, a column specified as SET('one', 'two') NOT NULL can have any of these values:
# ''
# 'one'
# 'two'
# 'one,two'
if (/(\w*)\s+set\(((?:['"]\w+['"]\s*,*)+(?:['"]\w+['"])*)\)(.*)$/i) { # example: `au_auth` set('r','w','d') NOT NULL default '',
$column_name = $1;
$constraints{$column_name}{'values'} = $2; # 'abc','def', ...
$constraints{$column_name}{'type'} = "set"; # 'abc','def', ...
$_ = qq~ $column_name varchar , ~;
$column_name = quote_and_lc($1);
$create_sql.=$_;
next;
}
if (/(\S*)\s+enum\(((?:['"][^'"]+['"]\s*,)+['"][^'"]+['"])\)(.*)$/i) { # enum handling
# example: `test` enum('?','+','-') NOT NULL default '?'
# $2 is the values of the enum 'abc','def', ...
$quoted_column=quote_and_lc($1);
# "test" NOT NULL default '?' CONSTRAINT test_test_constraint CHECK ("test" IN ('?','+','-'))
$_ = qq~ $quoted_column varchar CHECK ($quoted_column IN ( $2 ))$3\n~; # just assume varchar?
$create_sql.=$_;
next;
}
# Take care of "binary" option for char and varchar
# (pre-4.1.2, it indicated a byte array; from 4.1.2, indicates
# a binary collation)
s/(?:var)?char(?:\(\d+\))? (?:byte|binary)/text/i;
if (m/(?:var)?binary\s*\(\d+\)/i) { # c varBINARY(3) in Mysql
warn "WARNING in table '$table' '$_': binary type is converted to bytea (unsized) for Postgres\n";
}
s/(?:var)?binary(?:\(\d+\))?/text/i; # c varBINARY(3) in Mysql
s/bit(?:\(\d+\))?/bytea/i; # bit datatype -> bytea
# large datatypes
s/\w*blob/bytea/gi;
s/tinytext/text/gi;
s/mediumtext/text/gi;
s/longtext/text/gi;
# char -> varchar -- if specified as a command line option
# PostgreSQL would otherwise pad with spaces as opposed
# to MySQL! Your user interface may depend on this!
if ($CHAR2VARCHAR) {
s/(^\s+\S+\s+)char/${1}varchar/gi;
}
# nuke column's collate and character set
s/(\S+)\s+character\s+set\s+\w+/$1/gi;
s/(\S+)\s+collate\s+\w+/$1/gi;
#
# DATE AND TIME TYPES
#
# date time
# year
# datetime
# timestamp
# date time
# these are the same types in postgres, just do the replacement of 0000-00-00 date
if (m/default '(\d+)-(\d+)-(\d+)([^']*)'/i) { # we grab the year, month and day
# NOTE: times of 00:00:00 are possible and are okay
my $time = '';
my $year=$1;
my $month= $2;
my $day = $3;
if ($4) {
$time = $4;
}
if ($year eq "0000") { $year = '1970'; }
if ($month eq "00") { $month = '01'; }
if ($day eq "00") { $day = '01'; }
s/default '[^']+'/default '$year-$month-$day$time'/i; # finally we replace with $datetime
}
# convert mysql's year datatype to a constraint
if (/(\w*)\s+year\(4\)(.*)$/i) { # can be integer OR string 1901-2155
$constraint_table_name = get_identifier($table,$1 ,"constraint_table");
$column_name=quote_and_lc($1);
@year_holder = ();
$year='';
for (1901 .. 2155) {
$year = "'$_'";
unless ($year =~ /2155/) { $year .= ','; }
push( @year_holder, $year);
}
$constraints{$column_name}{'values'} = join('','',@year_holder); # '1901','1902', ...
$constraints{$column_name}{'type'} = "year";
$_ = qq~ $column_name varchar CONSTRAINT ${table}_${column_name}_constraint REFERENCES $constraint_table_name ("year_values") $2\n~;
$create_sql.=$_;
next;
} elsif (/(\w*)\s+year\(2\)(.*)$/i) { # same for a 2-integer string
$constraint_table_name = get_identifier($table,$1 ,"constraint_table");
$column_name=quote_and_lc($1);
@year_holder = ();
$year='';
for (1970 .. 2069) {
$year = "'$_'";
if ($year =~ /2069/) { next; }
push( @year_holder, $year);
}
push( @year_holder, '0000');
$constraints{$column_name}{'values'} = join(',',@year_holder); # '1971','1972', ...
$constraints{$column_name}{'type'} = "year"; # 'abc','def', ...
$_ = qq~ $1 varchar CONSTRAINT ${table}_${column_name}_constraint REFERENCES $constraint_table_name ("year_values") $2\n~;
$create_sql.=$_;
next;
}
# datetime
# Default on a dump from MySQL 5.0.22 is in the same form as datetime so let it flow down
# to the timestamp section and deal with it there
s/(${sl})datetime /$1timestamp without time zone /i;
# change not null datetime field to null valid ones
# (to support remapping of "zero time" to null
# s/($sl)datetime not null/$1timestamp without time zone/i;
# timestamps
#
# nuke datetime representation (not supported in PostgreSQL)
# change default time of 0000-00-00 to 1970-01-01
# we may possibly need to create a trigger to provide
# equal functionality with ON UPDATE CURRENT TIMESTAMP
if (m/${sl}timestamp/i) {
if ( m/ON UPDATE CURRENT_TIMESTAMP/i ) { # the ... default CURRENT_TIMESTAMP only applies for blank inserts, not updates
s/ON UPDATE CURRENT_TIMESTAMP//i ;
m/^\s*(\w+)\s+timestamp/i ;
# automatic trigger creation
$table_no_quotes =~ s/"//g;
$function_create_sql .= " CREATE OR REPLACE FUNCTION update_". $table_no_quotes . "() RETURNS trigger AS '
BEGIN
NEW.$1 := CURRENT_TIMESTAMP;
RETURN NEW;
END;
' LANGUAGE 'plpgsql';
-- before INSERT is handled by 'default CURRENT_TIMESTAMP'
CREATE TRIGGER add_current_date_to_".$table_no_quotes." BEFORE UPDATE ON ". $table . " FOR EACH ROW EXECUTE PROCEDURE
update_".$table_no_quotes."();\n";
}
if ($tables_first_timestamp_column && m/DEFAULT NULL/i) {
# DEFAULT NULL is the same as DEFAULT CURRENT_TIMESTAMP for the first TIMESTAMP column. (MYSQL manual)
s/($sl)(timestamp\s+)default null/$1 $2 DEFAULT CURRENT_TIMESTAMP/i;
}
$tables_first_timestamp_column= 0;
if (m/${sl}timestamp\s*\(\d+\)/i) { # fix for timestamps with width spec not handled (ID: 1628)
warn "WARNING for in table '$table' '$_': your default timestamp width is being ignored for table $table \n";
s/($sl)timestamp(?:\(\d+\))/$1datetime/i;
}
} # end timestamp section
# KEY AND UNIQUE CREATIONS
#
# unique
if ( /^\s+unique\s+\(([^(]+)\)/i ) { # example UNIQUE `name` (`name`), same as UNIQUE KEY
# POSTGRESQL: treat same as mysql unique
$quoted_column = quote_and_lc($1);
s/\s+unique\s+\(([^(]+)\)/ unique ($quoted_column) /i;
$create_sql.=$_;
next;
} elsif ( /^\s+unique\s+key\s*(\w+)\s*\(([^(]+)\)/i ) { # example UNIQUE KEY `name` (`name`)
# MYSQL: unique key: allows null=YES, allows duplicates=NO (*)
# ... new ... UNIQUE KEY `unique_fullname` (`fullname`) in my mysql v. Ver 14.12 Distrib 5.1.7-beta
# POSTGRESQL: treat same as mysql unique
# just quote columns
$quoted_column = quote_and_lc($2);
s/\s+unique\s+key\s*(\w+)\s*\(([^(]+)\)/ unique ($quoted_column) /i;
$create_sql.=$_;
# the index corresponding to the 'key' is automatically created
next;
}
# keys
if ( /^\s+fulltext key\s+/i) { # example: FULLTEXT KEY `commenttext` (`commenttext`)
# that is key as a word in the first check for a match
# the tsvector datatype is made for these types of things
# example mysql file:
# what is tsvector datatype?
# http://www.sai.msu.su/~megera/postgres/gist/tsearch/V2/docs/tsearch-V2-intro.html
warn "dba must do fulltext key transformation for $table\n";
next;
}
if ( /^(\s+)constraint (\S+) foreign key \((\S+)\) references (\S+) \((\S+)\)(.*)/i ) {
$quoted_column =quote_and_lc($3);
$col=quote_and_lc($5);
$post_create_sql .= "ALTER TABLE $table ADD FOREIGN KEY ($quoted_column) REFERENCES " . quote_and_lc($4) . " ($col);\n";
next;
}
if ( /^\s*primary key\s*\(([^)]+)\)([,\s]+)/i ) { # example PRIMARY KEY (`name`)
# MYSQL: primary key: allows null=NO , allows duplicates=NO
# POSTGRESQL: When an index is declared unique, multiple table rows with equal indexed values will not be
# allowed. Null values are not considered equal.
# POSTGRESQL quote's source: 8.1.3 docs section 11.5 "unique indexes"
# so, in postgres, we need to add a NOT NULL to the UNIQUE constraint
# and, primary key (mysql) == primary key (postgres) so that we *really* don't need change anything
$quoted_column = quote_and_lc($1);
s/(\s*)primary key\s+\(([^)]+)\)([,\s]+)/$1 primary key ($quoted_column)$3/i;
# indexes are automatically created for unique columns
$create_sql.=$_;
next;
} elsif (m/^\s+key\s[-_\s\w]+\((.+)\)/i ) { # example: KEY `idx_mod_english_def_word` (`word`),
# regular key: allows null=YES, allows duplicates=YES
# MYSQL: KEY is normally a synonym for INDEX. http://dev.mysql.com/doc/refman/5.1/en/create-table.html
#
# * MySQL: ALTER TABLE {$table} ADD KEY $column ($column)
# * PostgreSQL: CREATE INDEX {$table}_$column_idx ON {$table}($column) // Please note the _idx "extension"
# PRIMARY KEY (`postid`),
# KEY `ownerid` (`ownerid`)
# create an index for everything which has a key listed for it.
my $col = $1;
# TODO we don't have a translation for the substring syntax in text columns in MySQL (e.g. "KEY my_idx (mytextcol(20))")
# for now just getting rid of the brackets and numbers (the substring specifier):
$col=~s/\(\d+\)//g;
$quoted_column = quote_and_lc($col);
if ($col =~ m/,/) {
$col = s/,/_/;
}
$index = get_identifier($table, $col, 'idx');
$post_create_sql.="CREATE INDEX $index ON $table USING btree ($quoted_column)\;";
# just create index do not add to create table statement
next;
}
# handle 'key' declared at end of column
if (/\w+.*primary key/i) { # mysql: key is normally just a synonym for index
# just leave as is ( postgres has primary key type)
} elsif (/(\w+\s+(?:$mysql_datatypesStr)\s+.*)key/i) { # mysql: key is normally just a synonym for index
# I can't find a reference for 'key' in a postgres command without using the word 'primary key'
s/$1key/$1/i ;
$index = get_identifier($table, $1, 'idx');
$quoted_column =quote_and_lc($1);
$post_create_sql.="CREATE INDEX $index ON $table USING btree ($quoted_column) \;";
$create_sql.=$_;
}
# do we really need this anymore?
# remap colums with names of existing system attribute
if (/"oid"/i) {
s/"oid"/"_oid"/g;
print STDERR "WARNING: table $table uses column \"oid\" which is renamed to \"_oid\"\nYou should fix application manually! Press return to continue.";
my $wait=<STDIN>;
}
s/oid/_oid/i if (/key/i && /oid/i); # fix oid in key
# FINAL QUOTING OF ALL COLUMNS
# quote column names which were not already quoted
# perhaps they were not quoted because they were not explicitly handled
if (!/^\s*"(\w+)"(\s+)/i) {
/^(\s*)(\w+)(\s+)(.*)$/i ;
$quoted_column= quote_and_lc($2);
s/^(\s*)(\w+)(\s+)(.*)$/$1 $quoted_column $3 $4 /;
}
$create_sql.=$_;
# END of if ($create_sql ne "") i.e. were inside create table statement so processed datatypes
}
# add "not in create table" comments or empty lines to pre_create_sql
elsif (/^#/ || /^$/ || /^\s*--/) {
s/^#/--/; # Two hyphens (--) is the SQL-92 standard indicator for comments
$pre_create_sql .= $_ ; # printed above create table statement
next;
}
elsif (/^\s*insert into/i) { # not inside create table and doing insert
# fix mysql's zero/null value for timestamps
s/'0000-00-00/'1970-01-01/gi;
# commented out to fix bug "Field contents interpreted as a timestamp", what was the point of this line anyway?
#s/([12]\d\d\d)([01]\d)([0-3]\d)([0-2]\d)([0-6]\d)([0-6]\d)/'$1-$2-$3 $4:$5:$6'/;
#---- fix data in inserted data: (from MS world)
s!\x96!-!g; # --
s!\x93!"!g; # ``
s!\x94!"!g; # ''
s!\x85!... !g; # \ldots
s!\x92!`!g;
print OUT $pre_create_sql; # print comments preceding the insert section
$pre_create_sql="";
$auto_increment_seq = "";
s/'((?:[^'\\]++|\\.)*+)'(?=[),])/E'$1'/g;
# for the E'' see http://www.postgresql.org/docs/8.2/interactive/release-8-1.html
s!\\\\!\\\\\\\\!g; # replace \\ with ]\\\\
# split 'extended' INSERT INTO statements to something PostgreSQL can understand
( $insert_table, $valueString) = $_ =~ m/^INSERT\s+INTO\s+['`"]*(.*?)['`"]*\s+VALUES\s*(.*)/i;
$insert_table = quote_and_lc($insert_table);
s/^INSERT INTO.*?\);//i; # hose the statement which is to be replaced whether a run-on or not
# guarantee table names are quoted
print OUT qq(INSERT INTO $insert_table VALUES $valueString \n);
} else {
print OUT $_ ; # example: /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
}
# keep looping and get next line of IN file
} # END while(<IN>)
print_post_create_sql(); # in case there is extra from the last table
#################################################################
# 5. print_plgsql function prototype
# emulate the set datatype with the following plpgsql function
# looks ugly so putting at end of file
#################################################################
#
sub make_plpgsql {
my ($table,$column_name) = ($_[0],$_[1]);
$table=~s/\"//g; # make sure that $table doesn't have quotes so we don't end up with redundant quoting
my $constraint_table = get_identifier($table,$column_name ,"constraint_table");
return "
-- this function is called by the insert/update trigger
-- it checks if the INSERT/UPDATE for the 'set' column
-- contains members which comprise a valid mysql set
-- this TRIGGER function therefore acts like a constraint
-- provided limited functionality for mysql's set datatype
-- just verifies and matches for string representations of the set at this point
-- though the set datatype uses bit comparisons, the only supported arguments to our
-- set datatype are VARCHAR arguments
-- to add a member to the set add it to the ".$table."_".$column_name." table
CREATE OR REPLACE FUNCTION check_".$table."_".$column_name."_set( ) RETURNS TRIGGER AS \$\$\n
DECLARE
----
arg_str VARCHAR ;
argx VARCHAR := '';
nobreak INT := 1;
rec_count INT := 0;
psn INT := 0;
str_in VARCHAR := NEW.$column_name;
----
BEGIN
----
IF str_in IS NULL THEN RETURN NEW ; END IF;
arg_str := REGEXP_REPLACE(str_in, '\\',\\'', ','); -- str_in is CONSTANT
arg_str := REGEXP_REPLACE(arg_str, '^\\'', '');
arg_str := REGEXP_REPLACE(arg_str, '\\'\$', '');
-- RAISE NOTICE 'arg_str %',arg_str;
psn := POSITION(',' in arg_str);
IF psn > 0 THEN
psn := psn - 1; -- minus-1 from comma position
-- RAISE NOTICE 'psn %',psn;
argx := SUBSTRING(arg_str FROM 1 FOR psn); -- get one set member
psn := psn + 2; -- go to first starting letter
arg_str := SUBSTRING(arg_str FROM psn); -- hack it off
ELSE
psn := 0; -- minus-1 from comma position
argx := arg_str;
END IF;
-- RAISE NOTICE 'argx %',argx;
-- RAISE NOTICE 'new arg_str: %',arg_str;
WHILE nobreak LOOP
EXECUTE 'SELECT count(*) FROM $constraint_table WHERE set_values = ' || quote_literal(argx) INTO rec_count;
IF rec_count = 0 THEN RAISE EXCEPTION 'one of the set values was not found';
END IF;
IF psn > 0 THEN
psn := psn - 1; -- minus-1 from comma position
-- RAISE NOTICE 'psn %',psn;
argx := SUBSTRING(arg_str FROM 1 FOR psn); -- get one set member
psn := psn + 2; -- go to first starting letter
arg_str := SUBSTRING(arg_str FROM psn); -- hack it off
psn := POSITION(',' in arg_str);
ELSE nobreak = 0;
END IF;
-- RAISE NOTICE 'next argx % and next arg_str %', argx, arg_str;
END LOOP;
RETURN NEW;
----
END;
\$\$ LANGUAGE 'plpgsql' VOLATILE;
drop trigger set_test ON $table;
-- make a trigger for each set field
-- make trigger and hard-code in column names
-- see http://archives.postgresql.org/pgsql-interfaces/2005-02/msg00020.php
CREATE TRIGGER set_test
BEFORE INSERT OR UPDATE ON $table FOR EACH ROW
EXECUTE PROCEDURE check_".$table."_".$column_name."_set();\n";
} # end sub make_plpgsql();

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,6 @@
configure_file(mkdocs.yml ../mkdocs.yml)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/appendix)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/data-sources)
set (DOC_SOURCES
admin
@@ -14,7 +13,6 @@ set (DOC_SOURCES
index.md
extra.css
styles.css
data-sources/overview.md
)
foreach (src ${DOC_SOURCES})
@@ -23,22 +21,6 @@ foreach (src ${DOC_SOURCES})
)
endforeach()
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/us-tiger/README.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/US-Tiger.md
)
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/gb-postcodes/README.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/GB-Postcodes.md
)
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/country-grid/README.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/Country-Grid.md
)
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/country-grid/mexico.quad.png ${CMAKE_CURRENT_BINARY_DIR}/data-sources/mexico.quad.png
)
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/data-sources/wikipedia-wikidata/README.md ${CMAKE_CURRENT_BINARY_DIR}/data-sources/Wikipedia-Wikidata.md
)
ADD_CUSTOM_TARGET(doc
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Centos-7.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Centos-7.md
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bash2md.sh ${PROJECT_SOURCE_DIR}/vagrant/Install-on-Centos-8.sh ${CMAKE_CURRENT_BINARY_DIR}/appendix/Install-on-Centos-8.md

View File

@@ -51,7 +51,8 @@ The file `import_multiple_regions.sh` needs to be edited as per your requirement
!!! tip
If your database already exists and you want to add more countries, replace the setting up part
`${SETUPFILE} --osm-file ${UPDATEDIR}/tmp/combined.osm.pbf --all 2>&1`
with `${UPDATEFILE} --import-file ${UPDATEDIR}/tmp/combined.osm.pbf 2>&1`.
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
@@ -63,7 +64,7 @@ Run the following command from your Nominatim directory after configuring the fi
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
### 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.
@@ -103,7 +104,69 @@ Run the following command from your Nominatim directory after configuring the fi
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.
## Verification and further setup
## Importing Nominatim to an external PostgreSQL database
Instructions for import verification and other details like importing Wikidata can be found in [import and update page](Import-and-Update.md)
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
View 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/`.

View File

@@ -22,6 +22,21 @@ then you can resume with the following command:
If the reported rank is 26 or higher, you can also safely add `--index-noanalyse`.
### PostgreSQL crashed "invalid page in block"
Usually serious problem, can be a hardware issue, not all data written to disc
for example. Check PostgreSQL log file and search PostgreSQL issues/mailing
list for hints.
If it happened during index creation you can try rerunning the step with
```sh
./utils/setup.php --create-search-indices --ignore-errors
```
Otherwise it's best to start the full setup from the beginning.
### PHP "open_basedir restriction in effect" warnings
PHP Warning: file_get_contents(): open_basedir restriction in effect.
@@ -38,7 +53,7 @@ by adding ";" at the beginning of the line. Don't forget to enable this setting
again once you are done with the PHP command line operations.
### PHP timzeone warnings
### PHP timezeone warnings
The Apache log may contain lots of PHP warnings like this:
`PHP Warning: date_default_timezone_set() function.`
@@ -156,7 +171,8 @@ Example error message
The PostgreSQL database, i.e. user `postgres`, needs to have access to that file.
The permission need to be read & executable by everybody, e.g.
The permission need to be read & executable by everybody, but not writeable
by everybody, e.g.
```
-rwxr-xr-x 1 nominatim nominatim 297984 build/module/nominatim.so
@@ -167,6 +183,16 @@ 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.

View File

@@ -1,4 +1,4 @@
# Importing and Updating the Database
# Importing the Database
The following instructions explain how to create a Nominatim database
from an OSM planet file and how to keep the database up to date. It
@@ -29,7 +29,7 @@ Add to your `settings/local.php`:
@define('CONST_Osm2pgsql_Flatnode_File', '/path/to/flatnode.file');
Replace the second part with a suitable path on your system and make sure
the directory exists. There should be at least 64GB of free space.
the directory exists. There should be at least 75GB of free space.
## Downloading additional data
@@ -43,7 +43,7 @@ This data is available as a binary download:
cd $NOMINATIM_SOURCE_DIR/data
wget https://www.nominatim.org/data/wikimedia-importance.sql.gz
The file is about 400MB and adds around 4GB to Nominatim database.
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
@@ -60,21 +60,21 @@ involve a GB or US postcode. This data can be optionally downloaded:
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
## Choosing the data to import
In its default setup Nominatim is configured to import the full OSM data
set for the entire planet. Such a setup requires a powerful machine with
at least 64GB of RAM and around 800GB of SSD hard disks. Depending on your
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 extracts
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
below. There are also
[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
@@ -128,21 +128,23 @@ The style can be changed with the configuration `CONST_Import_Style`.
To give you an idea of the impact of using the different styles, the table
below gives rough estimates of the final database size after import of a
2018 planet and after using the `--drop` option. It also shows the time
needed for the import on a machine with 64GB RAM, 4 CPUS and SSDs. Note that
the given sizes are just an estimate meant for comparison of style requirements.
Your planet import is likely to be larger as the OSM data grows with time.
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 | 5h | 190 GB | 20 GB
street | 42h | 400 GB | 180 GB
address | 59h | 500 GB | 260 GB
full | 80h | 575 GB | 300 GB
extratags | 80h | 585 GB | 310 GB
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. For a description of the
style format see [the development section](../develop/Import.md).
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
@@ -150,15 +152,17 @@ style format see [the development section](../develop/Import.md).
First try the import with a small extract, for example from
[Geofabrik](https://download.geofabrik.de).
Download the data to import and load the data with the following command
from the build directory:
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
```
***Note for full planet imports:*** Even on a perfectly configured machine
the import of a full planet takes at least 2 days. Once you see messages
### 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
@@ -167,11 +171,12 @@ configuration as it may not be optimal for the import.
### Notes on memory usage
In the first step of the import Nominatim uses osm2pgsql to load the OSM data
into the PostgreSQL database. This step is very demanding in terms of RAM usage.
osm2pgsql and PostgreSQL are running in parallel at this point. PostgreSQL
blocks at least the part of RAM that has been configured with the
`shared_buffers` parameter during [PostgreSQL tuning](Installation#postgresql-tuning)
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
@@ -182,12 +187,12 @@ completely and leave the memory for the flatnode file. Nominatim will do this
by default, so you do not need to configure anything in this case.
For imports without a flatnode file, set `--osm2pgsql-cache` approximately to
the size of the OSM pbf file (in MB) you are importing. Make sure you leave
enough RAM for PostgreSQL and osm2pgsql as mentioned above. If the system starts
swapping or you are getting out-of-memory errors, reduce the cache size or
even consider using a flatnode file.
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 import finished
### Verify the import
Run this script to verify all required tables and indices got created successfully.
@@ -195,6 +200,30 @@ Run this script to verify all required tables and indices got created successful
./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
@@ -236,8 +265,6 @@ entire US adds about 10GB to your database.
wget https://nominatim.org/data/tiger2019-nominatim-preprocessed.tar.gz
tar xf tiger2019-nominatim-preprocessed.tar.gz
`data-source/us-tiger/README.md` explains how the data got preprocessed.
2. Import the data into your Nominatim database:
./utils/setup.php --import-tiger-data
@@ -253,70 +280,6 @@ entire US adds about 10GB to your database.
```
## Updates
See the [developer's guide](../develop/data-sources.md#us-census-tiger) for more
information on how the data got preprocessed.
There are many different ways to update your Nominatim database.
The following section describes how to keep it up-to-date with Pyosmium.
For a list of other methods see the output of `./utils/update.php --help`.
!!! warning
If you have configured a flatnode file for the import, then you
need to keep this flatnode file around for updates as well.
#### Installing the newest version of Pyosmium
It is recommended to install Pyosmium via pip. Make sure to use python3.
Run (as the same user who will later run the updates):
```sh
pip3 install --user osmium
```
Nominatim needs a tool called `pyosmium-get-updates` which comes with
Pyosmium. You need to tell Nominatim where to find it. Add the
following line to your `settings/local.php`:
@define('CONST_Pyosmium_Binary', '/home/user/.local/bin/pyosmium-get-changes');
The path above is fine if you used the `--user` parameter with pip.
Replace `user` with your user name.
#### Setting up the update process
Next the update needs to be initialised. By default Nominatim is configured
to update using the global minutely diffs.
If you want a different update source you will need to add some settings
to `settings/local.php`. For example, to use the daily country extracts
diffs for Ireland from Geofabrik add the following:
// base URL of the replication service
@define('CONST_Replication_Url', 'https://download.geofabrik.de/europe/ireland-and-northern-ireland-updates');
// How often upstream publishes diffs
@define('CONST_Replication_Update_Interval', '86400');
// How long to sleep if no update found yet
@define('CONST_Replication_Recheck_Interval', '900');
To set up the update process now run the following command:
./utils/update.php --init-updates
It outputs the date where updates will start. Recheck that this date is
what you expect.
The `--init-updates` command needs to be rerun whenever the replication service
is changed.
#### Updating Nominatim
The following command will keep your database constantly up to date:
./utils/update.php --import-osmosis-all
(Note that even though the old name "import-osmosis-all" has been kept for
compatibility reasons, Osmosis is not required to run this - it uses pyosmium
behind the scenes.)
If you have imported multiple country extracts and want to keep them
up-to-date, [Advanced installations section](Advanced-Installations.md) contains instructions
to set up and update multiple country extracts.

View File

@@ -37,23 +37,19 @@ For compiling:
For running Nominatim:
* [PostgreSQL](https://www.postgresql.org) (9.3+)
* [PostGIS](https://postgis.org) (2.2+)
* [PostGIS](https://postgis.net) (2.2+)
* [Python 3](https://www.python.org/)
* [Psycopg2](https://initd.org/psycopg)
* [Psycopg2](https://www.psycopg.org)
* [PHP](https://php.net) (7.0 or later)
* PHP-pgsql
* PHP-intl (bundled with PHP)
* a webserver (apache or nginx are recommended)
For running continuous updates:
* [pyosmium](https://osmcode.org/pyosmium/) (with Python 3)
For running tests:
* [behave](http://pythonhosted.org/behave/)
* [nose](https://nose.readthedocs.io)
* [phpunit](https://phpunit.de) >= 7.3
For dependencies for running tests and building documentation, see
the [Development section](../develop/Development-Environment.md).
### Hardware
@@ -61,16 +57,14 @@ 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 800GB of hard disk space
(take into account that the OSM database is growing fast). SSD disks
will help considerably to speed up import and queries.
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
at least 2 days. Without SSDs 7-8 days are more realistic.
around 2 days. On traditional spinning disks, 7-8 days are more realistic.
## Setup of the server
### PostgreSQL tuning
## 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
@@ -110,82 +104,44 @@ Don't forget to reenable them after the initial import or you risk database
corruption.
### Webserver setup
## Downloading and building Nominatim
The `website/` directory in the build directory contains the configured
website. Include the directory into your webbrowser to serve php files
from there.
### Downloading the latest release
#### Configure for use with Apache
You can download the [latest release from nominatim.org](https://nominatim.org/downloads/).
The release contains all necessary files. Just unpack it.
Make sure your Apache configuration contains the required permissions for the
directory and create an alias:
### Downloading the latest development version
``` 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
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
```
`/srv/nominatim/build` should be replaced with the location of your
build directory.
The development version does not include the country grid. Download it separately:
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.
``` nginx
root /srv/nominatim/build/website;
index search.php;
location / {
try_files $uri $uri/ @php;
}
location @php {
fastcgi_param SCRIPT_FILENAME "$document_root$uri.php";
fastcgi_param PATH_TRANSLATED "$document_root$uri.php";
fastcgi_param QUERY_STRING $args;
fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
fastcgi_pass unix:/var/run/php7.3-fpm.sock;
fastcgi_index search.php;
include fastcgi.conf;
}
```
wget -O Nominatim/data/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz
```
Restart the nginx and php5-fpm services and the website should now be available
at `http://localhost/`.
### Building Nominatim
The code must be built in a separate directory. Create the directory and
change into it.
Now continue with [importing the database](Import-and-Update.md).
```
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).

View File

@@ -6,6 +6,68 @@ 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
@@ -17,10 +79,32 @@ 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
@@ -38,6 +122,12 @@ CREATE INDEX idx_location_area_country_geometry ON location_area_country USING G
CREATE INDEX idx_location_area_country_place_id ON location_area_country USING BTREE (place_id);
```
Finally, update all SQL functions:
```sh
./utils/setup.php --create-functions --enable-diff-updates --create-partition-functions
```
## 3.2.0 -> 3.3.0
### New database connection string (DSN) format
@@ -54,7 +144,7 @@ The new format is
### Natural Earth country boundaries no longer needed as fallback
```
```sql
DROP TABLE country_naturalearthdata;
```
@@ -80,27 +170,37 @@ following command:
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;
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;
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;
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";
```
@@ -108,7 +208,7 @@ 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;
```
@@ -137,8 +237,8 @@ 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 (
DROP TYPE IF EXISTS nearfeaturecentr CASCADE;
CREATE TYPE nearfeaturecentr AS (
place_id BIGINT,
keywords int[],
rank_address smallint,

View 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
View 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.

View File

@@ -1,19 +1,22 @@
# Place details
Lookup details about a single place by id. The default output is HTML for debugging search logic and results.
Show all details about a single place saved in the database.
**The details page (including JSON output) exists for debugging only and must not be downloaded automatically**, see [Nominatim Usage Policy](https://operations.osmfoundation.org/policies/nominatim/).
!!! 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:
```
https://nominatim.openstreetmap.org/details?osmtype=[N|W|R]&osmid=<value>&class=<value>
``` xml
https://nominatim.openstreetmap.org/details?osmtype=[N|W|R]&osmid=<value>&class=<value>
```
`osmtype` and `osmid` are required parameter. The type is one of node (N), way (W)
`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
@@ -23,36 +26,34 @@ 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.
```
https://nominatim.openstreetmap.org/details?place_id=<value>
``` xml
https://nominatim.openstreetmap.org/details?place_id=<value>
```
Placeids are assigned sequentially during Nominatim data import. The id for a place is different between Nominatim installation (servers) and changes when data gets reimported. Therefore it can't be used as permanent id and shouldn't be used in bug reports.
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
* `format=[html|json]`
See [Place Output Formats](Output.md) for details on each format. (Default: html)
* `json_callback=<string>`
Wrap JSON output in a callback function (JSONP) i.e. `<string>(<json>)`.
Only has an effect for JSON output formats.
* `pretty=[0|1]`
For JSON output will add indentation to make it more human-readable. (Default: 0)
Add indentation to make it more human-readable. (Default: 0)
### Output details
* `addressdetails=[0|1]`
Include a breakdown of the address into elements. (Default for JSON: 0, for HTML: 1)
Include a breakdown of the address into elements. (Default: 0)
* `keywords=[0|1]`
@@ -60,11 +61,16 @@ Include a list of name keywords and address keywords (word ids). (Default: 0)
* `linkedplaces=[0|1]`
Include details of places higher in the address hierarchy. E.g. for a street this is usually the city, state, postal code, country. (Default: 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. E.g. for a city this usually a list of streets, suburbs, rivers. (Default for JSON: 0, for HTML: 1)
Include details of places lower in the address hierarchy. (Default: 0)
* `group_hierarchy=[0|1]`
@@ -72,7 +78,7 @@ For JSON output will group the places by type. (Default: 0)
* `polygon_geojson=[0|1]`
Include geometry of result. (Default for JSON: 0, for HTML: 1)
Include geometry of result. (Default: 0)
### Language of results
@@ -86,10 +92,6 @@ comma-separated list of language codes.
## Examples
##### HTML
[https://nominatim.openstreetmap.org/details.php?osmtype=W&osmid=38210407](https://nominatim.openstreetmap.org/details.php?osmtype=W&osmid=38210407)
##### 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)

View File

@@ -56,6 +56,21 @@ 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

View File

@@ -2,12 +2,10 @@
The [/reverse](Reverse.md), [/search](Search.md) and [/lookup](Lookup.md)
API calls produce very similar output which is explained in this section.
There is one section for each format which is selectable via the `format`
parameter.
There is one section for each format. The format correspond to what was
selected via the `format` parameter.
## Formats
### JSON
## JSON
The JSON format returns an array of places (for search and lookup) or
a single place (for reverse) of the following format:
@@ -41,13 +39,13 @@ a single place (for reverse) of the following format:
"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
* `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
@@ -62,22 +60,22 @@ The possible fields are:
* `geojson`, `svg`, `geotext`, `geokml` - full geometry
(only with the appropriate `polygon_*` parameter)
### JSONv2
## 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
## GeoJSON
This format follows the [RFC7946](https://geojson.org). Every feature includes
a bounding box (`bbox`).
The feature list has the following fields:
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
* `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
@@ -92,13 +90,13 @@ The feature list has the following fields:
Use `polygon_geojson` to output the full geometry of the object instead
of the centroid.
### GeocodeJSON
## 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)
* `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
@@ -110,18 +108,18 @@ The following feature attributes are implemented:
Use `polygon_geojson` to output the full geometry of the object instead
of the centroid.
### XML
## XML
The XML response returns one or more place objects in slightly different
formats depending on the API call.
#### Reverse
### 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"
<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
@@ -148,7 +146,7 @@ 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
* `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))
@@ -159,14 +157,14 @@ The full address of the result can be found in the content of the
Additional information requested with `addressdetails=1`, `extratags=1` and
`namedetails=1` can be found in extra elements.
#### Search and Lookup
### 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"
<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"
@@ -203,11 +201,12 @@ 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
* `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
* `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
@@ -217,17 +216,19 @@ 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 each place.
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 created when a Nominatim database gets installed. A
single place will have a different value on another server or even when
the same data gets re-imported. It's thus not useful to treat it as
permanent for later use.
The `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
@@ -236,20 +237,40 @@ Places can also change their meaning without changing their `osm_id`,
e.g. when a restaurant is retagged as supermarket. For a more in-depth
discussion see [Permanent ID](https://wiki.openstreetmap.org/wiki/Permanent_ID).
Nominatim merges some places (e.g. center node of a city with the boundary
relation) so `osm_type`+`osm_id`+`class_name` would be more unique.
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 we used to pan and center the map on the result, for example with leafletjs
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 planet (See [issue 184](https://github.com/openstreetmap/Nominatim/issues/184)).
essentially covering the entire planet
(see [issue 184](https://github.com/openstreetmap/Nominatim/issues/184)).
### addressdetails

View File

@@ -1,36 +1,48 @@
# Reverse Geocoding
Reverse geocoding generates an address from a latitude and longitude or from
an OSM object.
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?<query>
https://nominatim.openstreetmap.org/reverse?lat=<value>&lon=<value>&<params>
```
There are two ways how the requested location can be specified:
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.
* `lat=<value>` `lon=<value>`
Additional paramters are accepted as listed below.
A geographic location to generate an address for. The coordiantes must be
in WGS84 format.
* `osm_type=[N|W|R]` `osm_id=<value>`
A specific OSM node(N), way(W) or relation(R) to return an address for.
In both cases exactly one object is returned. The two input parameters cannot
be used at the same time. Both accept the additional optional parameters 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: html)
See [Place Output Formats](Output.md) for details on each format. (Default: xml)
* `json_callback=<string>`
@@ -69,8 +81,9 @@ comma-separated list of language codes.
* `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 map frameworks like Leaflet.js, Openlayers etc.
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
@@ -97,7 +110,7 @@ options can be used at a time. (Default: 0)
* `polygon_threshold=0.0`
Simplify the output geometry before returning. The parameter is the
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)

View File

@@ -1,30 +1,27 @@
# Search queries
The search API allows you to look up a location from a textual description.
Nominatim supports structured as well as free-form 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`).
Note that this only limits the items to be found, it's not suited to return complete
lists of OSM objects of a specific type. For those use [Overpass API](https://overpass-api.de/).
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 two formats:
```
https://nominatim.openstreetmap.org/search/<query>?<params>
```
This format only accepts a free-form query string where the
parts of the query are separated by slashes.
The search API has the following format:
```
https://nominatim.openstreetmap.org/search?<params>
```
In this form, the query may be given through two different sets of parameters:
The search term may be specified with two different sets of parameters:
* `q=<query>`
@@ -46,13 +43,13 @@ In this form, the query may be given through two different sets of parameters:
Structured requests are faster but are less robust against alternative
OSM tagging schemas. **Do not combine with** `q=<query>` **parameter**.
All three query forms accept the additional parameters listed below.
Both query forms accept the additional parameters listed below.
### Output format
* `format=[html|xml|json|jsonv2|geojson|geocodejson]`
* `format=[xml|json|jsonv2|geojson|geocodejson]`
See [Place Output Formats](Output.md) for details on each format. (Default: html)
See [Place Output Formats](Output.md) for details on each format. (Default: jsonv2)
* `json_callback=<string>`
@@ -96,16 +93,16 @@ Limit search results to one or more countries. `<countrycode>` must be the
e.g. `gb` for the United Kingdom, `de` for Germany.
Each place in Nominatim is assigned to one country code based
on `admin_level=2` tags, in rare cases to none (for example in
international waters outside any country).
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 broaden 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).
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>`
@@ -116,16 +113,17 @@ Limit the number of returned results. (Default: 10, Maximum: 50)
* `viewbox=<x1>,<y1>,<x2>,<y2>`
The preferred area to find search results. Any two corner points of the box
are accepted in any order as long as they span a real box. `x` is longitude,
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 with that
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. In this case, give the special keyword for the
amenity in square brackets, e.g. `[pub]`. (Default: 0)
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
@@ -140,7 +138,7 @@ options can be used at a time. (Default: 0)
* `polygon_threshold=0.0`
Simplify the output geometry before returning. The parameter is the
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)
@@ -154,13 +152,11 @@ address to identify your requests. See Nominatim's [Usage Policy](https://operat
* `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 in many
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

View File

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

View File

@@ -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
```

View File

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

View File

@@ -1,8 +1,8 @@
# OSM Data Import
OSM data is initially imported using osm2pgsql. Nominatim uses its own data
output style 'gazetteer', which differs from the output style created for
map rendering.
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

View File

@@ -7,24 +7,74 @@ different purposes, which are explained in this chapter.
## Search rank
The search rank describes the extent and importance of a place. It is used
when ranking search result. Simply put, if there are two results for a
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. All other objects have an address rank
of 0.
eligible to be part of an address. Places that should not appear in the
address must have an address rank of 0.
Note that the search rank of a place plays a role in the address computation
as well. When collecting the places that should make up the address parts
then only places are taken into account that have a lower address rank than
the search rank of the base object.
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
@@ -84,7 +134,7 @@ Then the rank is used when no more specific value is found for the given
key.
Countries and key/value combination may appear in multiple definitions. Just
make sure that each combination of counrty/key/value appears only once per
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.

View File

@@ -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,10 +25,9 @@ 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
@@ -54,14 +41,19 @@ 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
@@ -91,49 +83,29 @@ The tests can be configured with a set of environment variables (`behave -D key=
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
https://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.
You need at least 2GB RAM and 10GB discspace.
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 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 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:
cd Nominatim/data
wget https://nominatim.org/data/tiger2018-nominatim-preprocessed.tar.gz
tar xvf tiger2018-nominatim-preprocessed.tar.gz --wildcards --no-anchored '46*'
rm tiger2018-nominatim-preprocessed.tar.gz
The official test dataset is derived from the 180924 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:
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
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
@@ -143,7 +115,7 @@ On Debian/Ubuntu run:
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>
@@ -152,11 +124,11 @@ 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
@@ -167,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.

View 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).

View File

@@ -9,14 +9,14 @@ 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/output-gazetter.[ch]pp`. The result of
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 file `sql/functions.sql`.
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

View File

@@ -1,3 +1,15 @@
.toctree-l3 {
display: none!important
}
table {
margin-bottom: 12pt
}
th, td {
padding: 1pt 12pt;
}
th {
background-color: #eee;
}

View File

@@ -16,22 +16,21 @@ pages:
- 'FAQ': 'api/Faq.md'
- 'Administration Guide':
- 'Basic Installation': 'admin/Installation.md'
- 'Importing and Updating' : 'admin/Import-and-Update.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':
- 'Overview' : 'develop/overview.md'
- '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'
- 'Documentation' : 'develop/Documentation.md'
- 'External Data Sources':
- 'Overview' : 'data-sources/overview.md'
- 'US Census (Tiger)': 'data-sources/US-Tiger.md'
- 'GB Postcodes': 'data-sources/GB-Postcodes.md'
- 'Country Grid': 'data-sources/Country-Grid.md'
- 'Wikipedia & Wikidata': 'data-sources/Wikipedia-Wikidata.md'
- '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'

View File

@@ -18,6 +18,8 @@ function getLabelTag($aPlace, $sCountry = null)
$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) {
@@ -84,7 +86,8 @@ function getBoundaryLabel($iAdminLevel, $sCountry, $sFallback = 'Administrative'
8 => 'City',
9 => 'City District',
10 => 'Suburb',
11 => 'Neighbourhood'
11 => 'Neighbourhood',
12 => 'City Block'
),
'no' => array (
3 => 'State',
@@ -249,13 +252,17 @@ function getIcon($aPlace)
*/
function getIconFile($aPlace)
{
if (CONST_MapIcon_URL === false) {
return null;
}
$sIcon = getIcon($aPlace);
if (!isset($sIcon)) {
return null;
}
return CONST_Website_BaseURL.'images/mapicons/'.$sIcon.'.p.20.png';
return CONST_MapIcon_URL.'/'.$sIcon.'.p.20.png';
}
/**
@@ -272,6 +279,7 @@ function getImportance($aPlace)
if ($aWithImportance === null) {
$aWithImportance = array_flip(array(
'boundary:administrative',
'place:country',
'place:state',
'place:province',

View File

@@ -642,12 +642,6 @@ class Geocode
$oValidTokens = new TokenList();
if (!empty($aTokens)) {
$sSQL = 'SELECT word_id, word_token, word, class, type, country_code, operator, search_name_count';
$sSQL .= ' FROM word ';
$sSQL .= ' WHERE word_token in ('.join(',', $this->oDB->getDBQuotedList($aTokens)).')';
Debug::printSQL($sSQL);
$oValidTokens->addTokensFromDB(
$this->oDB,
$aTokens,
@@ -656,6 +650,8 @@ class Geocode
$this->oNormalizer
);
$oCtx->setFullNameWords($oValidTokens->getFullWordIDs());
// Try more interpretations for Tokens that could not be matched.
foreach ($aTokens as $sToken) {
if ($sToken[0] == ' ' && !$oValidTokens->contains($sToken)) {
@@ -923,6 +919,26 @@ class Geocode
$aResult['lon'],
$aResult['lat']
);
// secondary ordering (for results with same importance (the smaller the better):
// - approximate importance of address parts
if (isset($aResult['addressimportance']) && $aResult['addressimportance']) {
$aResult['foundorder'] = -$aResult['addressimportance']/10;
} else {
$aResult['foundorder'] = -$aResult['importance'];
}
// - number of exact matches from the query
$aResult['foundorder'] -= $aResults[$aResult['place_id']]->iExactMatches;
// - importance of the class/type
$iClassImportance = ClassTypes\getImportance($aResult);
if (isset($iClassImportance)) {
$aResult['foundorder'] += 0.0001 * $iClassImportance;
} else {
$aResult['foundorder'] += 0.01;
}
// - rank
$aResult['foundorder'] -= 0.00001 * (30 - $aResult['rank_search']);
// Adjust importance for the number of exact string matches in the result
$iCountWords = 0;
$sAddress = $aResult['langaddress'];
@@ -933,20 +949,8 @@ class Geocode
}
}
$aResult['importance'] = $aResult['importance'] + ($iCountWords*0.1); // 0.1 is a completely arbitrary number but something in the range 0.1 to 0.5 would seem right
// secondary ordering (for results with same importance (the smaller the better):
// - approximate importance of address parts
$aResult['foundorder'] = -$aResult['addressimportance']/10;
// - number of exact matches from the query
$aResult['foundorder'] -= $aResults[$aResult['place_id']]->iExactMatches;
// - importance of the class/type
$iClassImportance = ClassTypes\getImportance($aResult);
if (isset($iClassImportance)) {
$aResult['foundorder'] += 0.0001 * $iClassImportance;
} else {
$aResult['foundorder'] += 0.01;
}
// 0.1 is a completely arbitrary number but something in the range 0.1 to 0.5 would seem right
$aResult['importance'] = $aResult['importance'] + ($iCountWords*0.1);
}
$aSearchResults[$iIdx] = $aResult;
}

View File

@@ -452,11 +452,20 @@ class PlaceLookup
$aPlace,
$aPlace['country_code']
);
$aResults[$aPlace['place_id']] = $aPlace;
}
Debug::printVar('Places', $aPlaces);
$aResults = array_filter(
$aResults,
function ($v) {
return !($v instanceof Result);
}
);
return $aPlaces;
Debug::printVar('Places', $aResults);
return $aResults;
}
/* returns an array which will contain the keys

View File

@@ -54,6 +54,7 @@ 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 .= ' startnumber, endnumber, interpolationtype,';
@@ -62,6 +63,7 @@ class ReverseGeocode
$sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', linegeo, '.$fSearchDiam.')';
$sSQL .= ' and indexed_status = 0 and startnumber is not NULL ';
$sSQL .= ' ORDER BY distance ASC limit 1';
Debug::printSQL($sSQL);
return $this->oDB->getRow(
$sSQL,
@@ -88,16 +90,20 @@ class ReverseGeocode
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
@@ -115,9 +121,11 @@ class ReverseGeocode
$sSQL .= 'WHERE distance <= reverse_place_diameter(rank_search)';
$sSQL .= ' ORDER BY rank_search DESC, distance ASC';
$sSQL .= ' LIMIT 1';
Debug::printSQL($sSQL);
if (CONST_Debug) var_dump($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']);
}
@@ -131,9 +139,10 @@ class ReverseGeocode
$sSQL .= ' AND class in (\'boundary\', \'place\')';
$sSQL .= ' AND linked_place_id is null';
$sSQL .= ' ORDER BY distance ASC';
Debug::printSQL($sSQL);
if (CONST_Debug) var_dump($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']);
}
@@ -156,6 +165,7 @@ class ReverseGeocode
*/
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
@@ -173,8 +183,10 @@ class ReverseGeocode
$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 ...
@@ -193,6 +205,7 @@ class ReverseGeocode
// 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 ';
@@ -205,11 +218,12 @@ class ReverseGeocode
$sSQL .= ' AND distance <= reverse_place_diameter(rank_search)';
$sSQL .= ' ORDER BY distance ASC, rank_search DESC';
$sSQL .= ' LIMIT 1';
Debug::printSQL($sSQL);
if (CONST_Debug) var_dump($sSQL);
$aPlacNode = $this->oDB->getRow($sSQL, null, 'Could not determine place node.');
if ($aPlacNode) {
return $aPlacNode;
$aPlaceNode = $this->oDB->getRow($sSQL, null, 'Could not determine place node.');
Debug::printVar('Nearest place node', $aPlaceNode);
if ($aPlaceNode) {
return $aPlaceNode;
}
}
}
@@ -227,6 +241,7 @@ class ReverseGeocode
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,
@@ -256,10 +271,11 @@ class ReverseGeocode
$sSQL .= ' and (ST_GeometryType(geometry) not in (\'ST_Polygon\',\'ST_MultiPolygon\') ';
$sSQL .= ' OR ST_DWithin('.$sPointSQL.', centroid, '.$fSearchDiam.'))';
$sSQL .= ' ORDER BY distance ASC limit 1';
if (CONST_Debug) var_dump($sSQL);
Debug::printSQL($sSQL);
$aPlace = $this->oDB->getRow($sSQL, null, 'Could not determine closest place.');
if (CONST_Debug) var_dump($aPlace);
Debug::printVar('POI/street level result', $aPlace);
if ($aPlace) {
$iPlaceID = $aPlace['place_id'];
$oResult = new Result($iPlaceID);
@@ -279,6 +295,7 @@ class ReverseGeocode
}
$aHouse = $this->lookupInterpolation($sPointSQL, $fDistance);
Debug::printVar('Interpolation result', $aPlace);
if ($aHouse) {
$oResult = new Result($aHouse['place_id'], Result::TABLE_OSMLINE);
@@ -305,10 +322,12 @@ class ReverseGeocode
$sSQL .= ' and class not in (\'boundary\')';
$sSQL .= ' and indexed_status = 0 and linked_place_id is null';
$sSQL .= ' ORDER BY distance ASC limit 1';
if (CONST_Debug) var_dump($sSQL);
Debug::printSQL($sSQL);
$aStreet = $this->oDB->getRow($sSQL, null, 'Could not determine closest place.');
Debug::printVar('Closest POI result', $aStreet);
if ($aStreet) {
if (CONST_Debug) var_dump($aStreet);
$oResult = new Result($aStreet['place_id']);
}
}
@@ -326,10 +345,12 @@ class ReverseGeocode
$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';
if (CONST_Debug) var_dump($sSQL);
Debug::printSQL($sSQL);
$aPlaceTiger = $this->oDB->getRow($sSQL, null, 'Could not determine closest Tiger place.');
Debug::printVar('Tiger house number result', $aPlaceTiger);
if ($aPlaceTiger) {
if (CONST_Debug) var_dump('found Tiger housenumber', $aPlaceTiger);
$oResult = new Result($aPlaceTiger['place_id'], Result::TABLE_TIGER);
$oResult->iHouseNumber = closestHouseNumber($aPlaceTiger);
}
@@ -342,6 +363,8 @@ class ReverseGeocode
// lower than street level ($iMaxRank < 26 )
$oResult = $this->lookupLargeArea($sPointSQL, $iMaxRank);
}
Debug::printVar('Final result', $oResult);
return $oResult;
}
}

View File

@@ -32,7 +32,18 @@ class SearchContext
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.

View File

@@ -21,8 +21,6 @@ class SearchDescription
private $bRareName = false;
/// List of word ids making up the address of the object.
private $aAddress = array();
/// Subset of word ids of full words making up the address.
private $aFullNameAddress = array();
/// List of word ids that appear in the name but should be ignored.
private $aNameNonSearch = array();
/// List of word ids that appear in the address but should be ignored.
@@ -199,15 +197,10 @@ class SearchDescription
} elseif (($sPhraseType == '' || $sPhraseType == 'postalcode')
&& is_a($oSearchTerm, '\Nominatim\Token\Postcode')
) {
// We need to try the case where the postal code is the primary element
// (i.e. no way to tell if it is (postalcode, city) OR (city, postalcode)
// so try both.
if (!$this->sPostcode) {
// If we have structured search or this is the first term,
// make the postcode the primary search element.
if ($this->iOperator == Operator::NONE
&& ($sPhraseType == 'postalcode' || $bFirstToken)
) {
if ($this->iOperator == Operator::NONE && $bFirstToken) {
$oSearch = clone $this;
$oSearch->iSearchRank++;
$oSearch->iOperator = Operator::POSTCODE;
@@ -224,6 +217,9 @@ class SearchDescription
) {
$oSearch = clone $this;
$oSearch->iSearchRank++;
if (strlen($oSearchTerm->sPostcode) < 4) {
$oSearch->iSearchRank += 4 - strlen($oSearchTerm->sPostcode);
}
$oSearch->sPostcode = $oSearchTerm->sPostcode;
$aNewSearches[] = $oSearch;
}
@@ -252,6 +248,19 @@ class SearchDescription
$oSearch->iSearchRank++;
}
$aNewSearches[] = $oSearch;
// Housenumbers may appear in the name when the place has its own
// address terms.
if ($oSearchTerm->iId !== null
&& ($this->iNamePhrase >= 0 || empty($this->aName))
&& empty($this->aAddress)
) {
$oSearch = clone $this;
$oSearch->iSearchRank++;
$oSearch->aAddress = $this->aName;
$oSearch->bRareName = false;
$oSearch->aName = array($oSearchTerm->iId => $oSearchTerm->iId);
$aNewSearches[] = $oSearch;
}
}
} elseif ($sPhraseType == ''
&& is_a($oSearchTerm, '\Nominatim\Token\SpecialTerm')
@@ -288,11 +297,9 @@ class SearchDescription
if (!empty($this->aName) || !($bFirstPhrase || $sPhraseType == '')) {
if (($sPhraseType == '' || !$bFirstPhrase) && !$bHasPartial) {
$oSearch = clone $this;
$oSearch->iSearchRank += 2;
$oSearch->iSearchRank += 3 * $oSearchTerm->iTermCount;
$oSearch->aAddress[$iWordID] = $iWordID;
$aNewSearches[] = $oSearch;
} else {
$this->aFullNameAddress[$iWordID] = $iWordID;
}
} else {
$oSearch = clone $this;
@@ -338,16 +345,19 @@ class SearchDescription
) {
if ($oSearchTerm->iSearchNameCount < CONST_Max_Word_Frequency) {
$oSearch = clone $this;
$oSearch->iSearchRank += 2;
$oSearch->iSearchRank += $oSearchTerm->iTermCount + 1;
if (empty($this->aName)) {
$oSearch->iSearchRank++;
}
if (preg_match('#^[0-9]+$#', $sToken)) {
$oSearch->iSearchRank++;
}
$oSearch->aAddress[$iWordID] = $iWordID;
$aNewSearches[] = $oSearch;
} else {
$oSearch = clone $this;
$oSearch->iSearchRank++;
$oSearch->iSearchRank += $oSearchTerm->iTermCount + 1;
$oSearch->aAddressNonSearch[$iWordID] = $iWordID;
if (preg_match('#^[0-9]+$#', $sToken)) {
$oSearch->iSearchRank += 2;
}
if (!empty($aFullTokens)) {
$oSearch->iSearchRank++;
}
@@ -357,7 +367,7 @@ class SearchDescription
foreach ($aFullTokens as $oSearchTermToken) {
if (is_a($oSearchTermToken, '\Nominatim\Token\Word')) {
$oSearch = clone $this;
$oSearch->iSearchRank++;
$oSearch->iSearchRank += 3;
$oSearch->aAddress[$oSearchTermToken->iId]
= $oSearchTermToken->iId;
$aNewSearches[] = $oSearch;
@@ -657,7 +667,7 @@ class SearchDescription
}
if ($this->sHouseNumber) {
$aTerms[] = 'address_rank between 16 and 27';
$aTerms[] = 'address_rank between 16 and 30';
} elseif (!$this->sClass || $this->iOperator == Operator::NAME) {
if ($iMinAddressRank > 0) {
$aTerms[] = "((address_rank between $iMinAddressRank and $iMaxAddressRank) or (search_rank between $iMinAddressRank and $iMaxAddressRank))";
@@ -696,10 +706,11 @@ class SearchDescription
$sImportanceSQL .= $this->oContext->viewboxImportanceSQL('centroid');
$aOrder[] = "$sImportanceSQL DESC";
if (!empty($this->aFullNameAddress)) {
$aFullNameAddress = $this->oContext->getFullNameTerms();
if (!empty($aFullNameAddress)) {
$sExactMatchSQL = ' ( ';
$sExactMatchSQL .= ' SELECT count(*) FROM ( ';
$sExactMatchSQL .= ' SELECT unnest('.$oDB->getArraySQL($this->aFullNameAddress).')';
$sExactMatchSQL .= ' SELECT unnest('.$oDB->getArraySQL($aFullNameAddress).')';
$sExactMatchSQL .= ' INTERSECT ';
$sExactMatchSQL .= ' SELECT unnest(nameaddress_vector)';
$sExactMatchSQL .= ' ) s';

View File

@@ -80,6 +80,21 @@ class TokenList
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.
*
@@ -151,7 +166,8 @@ class TokenList
$oToken = new Token\Word(
$iId,
$aWord['word_token'][0] != ' ',
(int) $aWord['count']
(int) $aWord['count'],
substr_count($aWord['word_token'], ' ')
);
}

View File

@@ -13,12 +13,15 @@ class Word
public $bPartial;
/// Number of appearances in the database.
public $iSearchNameCount;
/// Number of terms in the word.
public $iTermCount;
public function __construct($iId, $bPartial, $iSearchNameCount)
public function __construct($iId, $bPartial, $iSearchNameCount, $iTermCount)
{
$this->iId = $iId;
$this->bPartial = $bPartial;
$this->iSearchNameCount = $iSearchNameCount;
$this->iTermCount = $iTermCount;
}
public function debugInfo()

View File

@@ -16,14 +16,6 @@ function userError($sMsg)
}
function exception_handler_html($exception)
{
http_response_code($exception->getCode());
header('Content-type: text/html; charset=UTF-8');
include(CONST_BasePath.'/lib/template/error-html.php');
exit();
}
function exception_handler_json($exception)
{
http_response_code($exception->getCode());
@@ -41,14 +33,6 @@ function exception_handler_xml($exception)
exit();
}
function shutdown_exception_handler_html()
{
$error = error_get_last();
if ($error !== null && $error['type'] === E_ERROR) {
exception_handler_html(new Exception($error['message'], 500));
}
}
function shutdown_exception_handler_xml()
{
$error = error_get_last();
@@ -73,10 +57,7 @@ function set_exception_handler_by_format($sFormat = null)
// one by default without an explicit $sFormat set.
if (!isset($sFormat)) {
set_exception_handler('exception_handler_html');
} elseif ($sFormat == 'html') {
set_exception_handler('exception_handler_html');
register_shutdown_function('shutdown_exception_handler_html');
set_exception_handler('exception_handler_json');
} elseif ($sFormat == 'xml') {
set_exception_handler('exception_handler_xml');
register_shutdown_function('shutdown_exception_handler_xml');

View File

@@ -2,8 +2,3 @@
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;
}

View File

@@ -55,7 +55,7 @@ function byImportance($a, $b)
if ($a['importance'] != $b['importance'])
return ($a['importance'] > $b['importance']?-1:1);
return ($a['foundorder'] < $b['foundorder']?-1:1);
return $a['foundorder'] <=> $b['foundorder'];
}

View File

@@ -108,17 +108,11 @@ class SetupFunctions
if ($result != 0) fail('Error executing external command: '.$oCmd->escapedCmd());
}
public function connect()
{
$this->oDB = new \Nominatim\DB();
$this->oDB->connect();
}
public function setupDB()
{
info('Setup DB');
$fPostgresVersion = $this->oDB->getPostgresVersion();
$fPostgresVersion = $this->db()->getPostgresVersion();
echo 'Postgres version found: '.$fPostgresVersion."\n";
if ($fPostgresVersion < 9.03) {
@@ -128,7 +122,7 @@ class SetupFunctions
$this->pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
$this->pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
$fPostgisVersion = $this->oDB->getPostgisVersion();
$fPostgisVersion = $this->db()->getPostgisVersion();
echo 'Postgis version found: '.$fPostgisVersion."\n";
if ($fPostgisVersion < 2.2) {
@@ -136,7 +130,7 @@ class SetupFunctions
exit(1);
}
$i = $this->oDB->getOne("select count(*) from pg_user where usename = '".CONST_Database_Web_User."'");
$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";
@@ -207,6 +201,8 @@ class SetupFunctions
$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']);
@@ -223,7 +219,7 @@ class SetupFunctions
$oCmd->addParams($sOSMFile);
$oCmd->run();
if (!$this->sIgnoreErrors && !$this->oDB->getRow('select * from place limit 1')) {
if (!$this->sIgnoreErrors && !$this->db()->getRow('select * from place limit 1')) {
fail('No Data');
}
@@ -257,7 +253,7 @@ class SetupFunctions
}
$oAlParser = new AddressLevelParser(CONST_Address_Level_Config);
$oAlParser->createTable($this->oDB, 'address_levels');
$oAlParser->createTable($this->db(), 'address_levels');
}
public function createTableTriggers()
@@ -305,40 +301,42 @@ class SetupFunctions
{
info('Drop old Data');
$this->oDB->exec('TRUNCATE word');
$oDB = $this->db();
$oDB->exec('TRUNCATE word');
echo '.';
$this->oDB->exec('TRUNCATE placex');
$oDB->exec('TRUNCATE placex');
echo '.';
$this->oDB->exec('TRUNCATE location_property_osmline');
$oDB->exec('TRUNCATE location_property_osmline');
echo '.';
$this->oDB->exec('TRUNCATE place_addressline');
$oDB->exec('TRUNCATE place_addressline');
echo '.';
$this->oDB->exec('TRUNCATE location_area');
$oDB->exec('TRUNCATE location_area');
echo '.';
if (!$this->dbReverseOnly()) {
$this->oDB->exec('TRUNCATE search_name');
$oDB->exec('TRUNCATE search_name');
echo '.';
}
$this->oDB->exec('TRUNCATE search_name_blank');
$oDB->exec('TRUNCATE search_name_blank');
echo '.';
$this->oDB->exec('DROP SEQUENCE seq_place');
$oDB->exec('DROP SEQUENCE seq_place');
echo '.';
$this->oDB->exec('CREATE SEQUENCE seq_place start 100000');
$oDB->exec('CREATE SEQUENCE seq_place start 100000');
echo '.';
$sSQL = 'select distinct partition from country_name';
$aPartitions = $this->oDB->getCol($sSQL);
$aPartitions = $oDB->getCol($sSQL);
if (!$this->bNoPartitions) $aPartitions[] = 0;
foreach ($aPartitions as $sPartition) {
$this->oDB->exec('TRUNCATE location_road_'.$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';
$this->oDB->exec($sSQL);
$oDB->exec($sSQL);
echo ".\n";
// pre-create the word list
@@ -415,13 +413,13 @@ class SetupFunctions
info('Reanalysing database');
$this->pgsqlRunScript('ANALYSE');
$sDatabaseDate = getDatabaseDate($this->oDB);
$this->oDB->exec('TRUNCATE import_status');
$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."')";
$this->oDB->exec($sSQL);
$oDB->exec($sSQL);
echo "Latest data imported from $sDatabaseDate.\n";
}
}
@@ -499,7 +497,7 @@ class SetupFunctions
public function calculatePostcodes($bCMDResultAll)
{
info('Calculate Postcodes');
$this->oDB->exec('TRUNCATE location_postcode');
$this->db()->exec('TRUNCATE location_postcode');
$sSQL = 'INSERT INTO location_postcode';
$sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
@@ -510,7 +508,7 @@ class SetupFunctions
$sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";
$sSQL .= ' AND geometry IS NOT null';
$sSQL .= ' GROUP BY country_code, pc';
$this->oDB->exec($sSQL);
$this->db()->exec($sSQL);
// only add postcodes that are not yet available in OSM
$sSQL = 'INSERT INTO location_postcode';
@@ -520,7 +518,7 @@ class SetupFunctions
$sSQL .= ' FROM us_postcode WHERE postcode NOT IN';
$sSQL .= ' (SELECT postcode FROM location_postcode';
$sSQL .= " WHERE country_code = 'us')";
$this->oDB->exec($sSQL);
$this->db()->exec($sSQL);
// add missing postcodes for GB (if available)
$sSQL = 'INSERT INTO location_postcode';
@@ -529,21 +527,23 @@ class SetupFunctions
$sSQL .= ' FROM gb_postcode WHERE postcode NOT IN';
$sSQL .= ' (SELECT postcode FROM location_postcode';
$sSQL .= " WHERE country_code = 'gb')";
$this->oDB->exec($sSQL);
$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->oDB->exec($sSQL);
$this->db()->exec($sSQL);
}
$sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
$sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
$this->oDB->exec($sSQL);
$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'])
@@ -568,19 +568,27 @@ class SetupFunctions
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');
@@ -592,7 +600,7 @@ class SetupFunctions
info('Index postcodes');
$sSQL = 'UPDATE location_postcode SET indexed_status = 0';
$this->oDB->exec($sSQL);
$this->db()->exec($sSQL);
}
public function createSearchIndices()
@@ -601,11 +609,11 @@ class SetupFunctions
$sSQL = 'SELECT relname FROM pg_class, pg_index ';
$sSQL .= 'WHERE pg_index.indisvalid = false AND pg_index.indexrelid = pg_class.oid';
$aInvalidIndices = $this->oDB->getCol($sSQL);
$aInvalidIndices = $this->db()->getCol($sSQL);
foreach ($aInvalidIndices as $sIndexName) {
info("Cleaning up invalid index $sIndexName");
$this->oDB->exec("DROP INDEX $sIndexName;");
$this->db()->exec("DROP INDEX $sIndexName;");
}
$sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
@@ -675,7 +683,7 @@ class SetupFunctions
);
$aDropTables = array();
$aHaveTables = $this->oDB->getListOfTables();
$aHaveTables = $this->db()->getListOfTables();
foreach ($aHaveTables as $sTable) {
$bFound = false;
@@ -694,6 +702,57 @@ class SetupFunctions
$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) {
@@ -723,13 +782,13 @@ class SetupFunctions
$sTemplate .= file_get_contents($sBasePath.'importance.sql');
$sTemplate .= file_get_contents($sBasePath.'address_lookup.sql');
$sTemplate .= file_get_contents($sBasePath.'interpolation.sql');
if ($this->oDB->tableExists('place')) {
if ($this->db()->tableExists('place')) {
$sTemplate .= file_get_contents($sBasePath.'place_triggers.sql');
}
if ($this->oDB->tableExists('placex')) {
if ($this->db()->tableExists('placex')) {
$sTemplate .= file_get_contents($sBasePath.'placex_triggers.sql');
}
if ($this->oDB->tableExists('location_postcode')) {
if ($this->db()->tableExists('location_postcode')) {
$sTemplate .= file_get_contents($sBasePath.'postcode_triggers.sql');
}
$sTemplate = str_replace('{modulepath}', $this->sModulePath, $sTemplate);
@@ -758,7 +817,7 @@ class SetupFunctions
private function pgsqlRunPartitionScript($sTemplate)
{
$sSQL = 'select distinct partition from country_name';
$aPartitions = $this->oDB->getCol($sSQL);
$aPartitions = $this->db()->getCol($sSQL);
if (!$this->bNoPartitions) $aPartitions[] = 0;
preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
@@ -864,13 +923,11 @@ class SetupFunctions
* @param string $sName Name of table to remove.
*
* @return null
*
* @pre connect() must have been called.
*/
private function dropTable($sName)
{
if ($this->bVerbose) echo "Dropping table $sName\n";
$this->oDB->deleteTable($sName);
$this->db()->deleteTable($sName);
}
/**
@@ -880,6 +937,6 @@ class SetupFunctions
*/
private function dbReverseOnly()
{
return !($this->oDB->tableExists('search_name'));
return !($this->db()->tableExists('search_name'));
}
}

View File

@@ -1,135 +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'); ?>
<div class="top-bar">
<form class="form-inline" role="search" accept-charset="UTF-8" action="<?php echo CONST_Website_BaseURL; ?>reverse.php">
<div class="form-group">
<input name="format" type="hidden" value="html">
lat
<input name="lat" type="text" class="form-control input-sm" placeholder="latitude" value="<?php echo $fLat; ?>" >
<a href="#" class="btn btn-default btn-xs" id="switch-coords" title="switch lat and lon">&lt;&gt;</a>
lon
<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>
<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 detailsPermaLink($aResult, 'details', 'class="btn btn-default btn-xs details"');
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>

View File

@@ -28,6 +28,9 @@ if (empty($aPlace)) {
echo join(',', $aPlace['aBoundingBox']);
echo '"';
}
echo " place_rank='".$aPlace['rank_search']."'";
echo " address_rank='".$aPlace['rank_address']."'";
if (isset($aPlace['asgeojson'])) {
echo ' geojson=\'';

View File

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

View File

@@ -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>

View File

@@ -1,268 +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, $bInMeters = false)
{
if ($bInMeters) {
// $fDistance is in meters
if ($fDistance < 1) {
return '0';
}
elseif ($fDistance < 1000) {
return '<abbr class="distance" title="'.$fDistance.' meters">~'.(round($fDistance,0)).' m</abbr>';
}
else {
return '<abbr class="distance" title="'.$fDistance.' meters">~'.(round($fDistance/1000,1)).' km</abbr>';
}
} else {
if ($fDistance == 0) {
return '0';
} else {
return '<abbr class="distance" title="spheric distance '.$fDistance.'">'.(round($fDistance,4)).'</abbr>';
}
}
}
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($aPlace)
{
$sIcon = Nominatim\ClassTypes\getIconFile($aPlace);
if (isset($sIcon)) {
$sLabel = Nominatim\ClassTypes\getIcon($aPlace);
echo '<img id="mapicon" src="'.$sIcon.'" alt="'.$sLabel.'" />';
}
}
function _one_row($aAddressLine, $bDistanceInMeters = false){
$bNotUsed = isset($aAddressLine['isaddress']) && !$aAddressLine['isaddress'];
echo '<tr class="' . ($bNotUsed?'notused':'') . '">'."\n";
echo ' <td class="name">'.(trim($aAddressLine['localname'])!==null?$aAddressLine['localname']:'<span class="noname">No Name</span>')."</td>\n";
echo ' <td>' . $aAddressLine['class'].':'.$aAddressLine['type'];
if ($aAddressLine['type'] == 'administrative'
&& isset($aAddressLine['place_type']))
{
echo '('.$aAddressLine['place_type'].')';
}
echo "</td>\n";
echo ' <td>' . osmLink($aAddressLine) . "</td>\n";
echo ' <td>' . (isset($aAddressLine['rank_address']) ? $aAddressLine['rank_address'] : '') . "</td>\n";
echo ' <td>' . ($aAddressLine['admin_level'] < 15 ? $aAddressLine['admin_level'] : '') . "</td>\n";
echo ' <td>' . format_distance($aAddressLine['distance'], $bDistanceInMeters)."</td>\n";
echo ' <td>' . detailsPermaLink($aAddressLine,'details &gt;') . "</td>\n";
echo "</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) ?>
</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' , (new DateTime('@'.$aPointDetails['indexed_epoch']))->format(DateTime::RFC822) );
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']?'Polygon':'Point') );
kv('Centre Point' , $aPointDetails['lat'].','.$aPointDetails['lon'] );
kv('OSM' , osmLink($aPointDetails) );
kv('Place Id (<a href="https://nominatim.org/release-docs/develop/api/Output/#place_id-is-not-a-persistent-id">on this server</a>)'
, $aPointDetails['place_id'] );
if ($aPointDetails['wikipedia'])
{
kv('Wikipedia Calculated' , wikipediaLink($aPointDetails) );
}
kv('Computed Postcode', $aPointDetails['postcode']);
kv('Address Tags' , hash_to_subtable($aPointDetails['aAddressTags']) );
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>Address rank</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, true);
}
}
if ($bIncludeKeywords)
{
headline('Name Keywords');
if ($aPlaceSearchNameKeywords) {
foreach ($aPlaceSearchNameKeywords as $aRow) {
_one_keyword_row($aRow['word_token'], $aRow['word_id']);
}
}
headline('Address Keywords');
if ($aPlaceSearchAddressKeywords) {
foreach ($aPlaceSearchAddressKeywords as $aRow) {
_one_keyword_row($aRow['word_token'], $aRow['word_id']);
}
}
}
if (!empty($aHierarchyLines))
{
headline('Parent Of');
$aGroupedAddressLines = array();
foreach ($aHierarchyLines 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 => $aHierarchyLines) {
$sGroupHeading = ucwords($sGroupHeading);
headline3($sGroupHeading);
foreach ($aHierarchyLines as $aAddressLine) {
_one_row($aAddressLine, true);
}
}
if (count($aHierarchyLines) >= 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>

View File

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

View File

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

View File

@@ -1,15 +0,0 @@
<footer>
<p class="disclaimer">
Addresses and postcodes are approximate
</p>
<p class="copyright">
&copy; <a href="https://osm.org/copyright">OpenStreetMap</a> contributors
</p>
</footer>
<script src="js/jquery.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/leaflet.min.js"></script>
<script src="js/Control.Minimap.min.js"></script>
<script src="js/url-search-params.js"></script>
<script src="js/nominatim-ui.js"></script>

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>OpenStreetMap Nominatim: Search</title>
<meta content="IE=edge" http-equiv="x-ua-compatible" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<base href="<?php echo CONST_Website_BaseURL;?>" />
<link href="css/leaflet.css" rel="stylesheet" />
<link href="css/Control.Minimap.min.css" rel="stylesheet" />
<link href="css/bootstrap-theme.min.css" rel="stylesheet" />
<link href="css/bootstrap.min.css" rel="stylesheet" />

View File

@@ -1,51 +0,0 @@
<header class="container-fluid">
<div class="row">
<div class="col-xs-4">
<div class="brand">
<a href="<?php echo CONST_Website_BaseURL;?>">
<img alt="logo" src="images/osm_logo.120px.png" width="30" height="30"/>
<h1>Nominatim</h1>
</a>
</div>
</div>
<div id="last-updated" class="col-xs-4 text-center">
<?php if (isset($sDataDate)){ ?>
Data last updated:
<br>
<?php echo $sDataDate; ?>
<?php } ?>
</div>
<div class="col-xs-4 text-right">
<div class="btn-group">
<button class="dropdown-toggle btn btn-sm btn-default" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
About &amp; Help <span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li><a href="https://nominatim.org/release-docs/develop/api/Overview/" target="_blank">API Reference</a></li>
<li><a href="https://nominatim.org/release-docs/develop/api/Faq/" target="_blank">FAQ</a></li>
<li><a href="https://help.openstreetmap.org/tags/nominatim/">OpenStreetMap Help</a></li>
<li><a href="https://github.com/openstreetmap/Nominatim">Nominatim on Github</a></li>
<li role="separator" class="divider"></li>
<li><a href="#" class="" data-toggle="modal" data-target="#report-modal">Report problem with results</a></li>
</ul>
</div>
</div>
</div>
</header>
<div class="modal fade" id="report-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Report a problem</h4>
</div>
<div class="modal-body">
<?php include(CONST_BasePath.'/lib/template/includes/report-errors.php'); ?>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">OK</button>
</div>
</div>
</div>
</div>

View File

@@ -1,9 +0,0 @@
<h2>Welcome to Nominatim</h2>
<p>Nominatim is a search engine for <a href="https://www.openstreetmap.org">OpenStreetMap</a>
data. This is the debugging interface. You may search for a name or address (forward search) or
look up data by its geographic coordinate (reverse search). Each result comes with a
link to a details page where you can inspect what data about the object is saved in
the database and investigate how the address of the object has been computed.</p>
For more information visit the <a href="https://nominatim.org">Nominatim home page</a>.

View File

@@ -1,42 +0,0 @@
<p>
Before reporting problems please read the <a target="_blank" href="https://nominatim.org/release-docs/develop/api/Overview">user documentation</a>.
<h4>Finding the expected result</h4>
First of all, please make sure that the result that you expect is
available in the OpenStreetMap data.
To find the OpenStreetMap data, do the following:
<ul>
<li>Go to <a href="https://openstreetmap.org">https://openstreetmap.org</a>.</li>
<li>Go to the area of the map where you expect the result
and zoom in until you see the object you are looking for.</li>
<li>Click on the question mark on the right side of the map,
then with the question cursor on the map where your object is located.</li>
<li>Find the object of interest in the list that appears on the left side.</li>
<li>Click on the object and note down the URL that the browser shows.</li>
</ul>
If you cannot find the data you are looking for, there is a good chance
that it has not been entered yet. You should <a href="https://www.openstreetmap.org/fixthemap">report or fix the problem in OpenStreetMap</a> directly.
<h4>Reporting bad searches</h4>
Problems may be reported at the <a target="_blank" href="https://github.com/openstreetmap/nominatim/issues">issue tracker on github</a>. Please read through
the open tickets first and check if your problem has not already been
reported.
When reporting a problem, include the following:
<ul>
<li>A full description of the problem, including the exact term you
were searching for.</li>
<li>The result you get.</li>
<li>The OpenStreetMap object you expect to find (see above).</li>
</ul>
For general questions about installing and searching in Nominatim, please
use <a href="https://help.openstreetmap.org/tags/nominatim/">Help OpenStreetMap</a>.
</p>

View File

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

View File

@@ -1,139 +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="search-page">
<?php include(CONST_BasePath.'/lib/template/includes/html-top-navigation.php'); ?>
<div class="top-bar" id="structured-query-selector">
<div class="search-type-link">
<a id="switch-to-reverse" href="<?php echo CONST_Website_BaseURL; ?>reverse.php?format=html">reverse search</a>
</div>
<div class="radio-inline">
<input type="radio" name="query-selector" id="simple" value="simple">
<label for="simple">simple</label>
</div>
<div class="radio-inline">
<input type="radio" name="query-selector" id="structured" value="structured">
<label for="structured">structured</label>
</div>
<form role="search" accept-charset="UTF-8" action="<?php echo CONST_Website_BaseURL; ?>search.php">
<div class="form-group-simple">
<input id="q" name="q" type="text" class="form-control input-sm" placeholder="Search" value="<?php echo htmlspecialchars($aMoreParams['q'] ?? ''); ?>" >
</div>
<div class="form-group-structured">
<div class="form-inline">
<input id="street" name="street" type="text" class="form-control input-sm" placeholder="House number/Street" value="<?php echo htmlspecialchars($aMoreParams['street'] ?? ''); ?>" >
<input id="city" name="city" type="text" class="form-control input-sm" placeholder="City" value="<?php echo htmlspecialchars($aMoreParams['city'] ?? ''); ?>" >
<input id="county" name="county" type="text" class="form-control input-sm" placeholder="County" value="<?php echo htmlspecialchars($aMoreParams['county'] ?? ''); ?>" >
<input id="state" name="state" type="text" class="form-control input-sm" placeholder="State" value="<?php echo htmlspecialchars($aMoreParams['state'] ?? ''); ?>" >
<input id="country" name="country" type="text" class="form-control input-sm" placeholder="Country" value="<?php echo htmlspecialchars($aMoreParams['country'] ?? ''); ?>" >
<input id="postalcode" name="postalcode" type="text" class="form-control input-sm" placeholder="Postal Code" value="<?php echo htmlspecialchars($aMoreParams['postalcode'] ?? ''); ?>" >
</div></div>
<div class="form-group search-button-group">
<button type="submit" class="btn btn-primary btn-sm">Search</button>
<?php if (CONST_Search_AreaPolygons) { ?>
<input type="hidden" value="1" name="polygon_geojson" />
<?php } ?>
<input type="hidden" name="viewbox" value="<?php echo htmlspecialchars($aMoreParams['viewbox'] ?? ''); ?>" />
<div class="checkbox-inline">
<input type="checkbox" id="use_viewbox" <?php if (!empty($aMoreParams['viewbox'])) echo "checked='checked'"; ?>>
<label for="use_viewbox">apply viewbox</label>
</div>
</div>
</form>
</div>
<div id="content">
<?php if ($sQuery) { ?>
<div id="searchresults" class="sidebar">
<?php
$i = 0;
foreach($aSearchResults as $iResNum => $aResult)
{
echo '<div class="result" data-position=' . $i . '>';
echo (isset($aResult['icon'])?'<img alt="icon" src="'.$aResult['icon'].'"/>':'');
echo ' <span class="name">'.htmlspecialchars($aResult['name']).'</span>';
// echo ' <span class="latlon">'.round($aResult['lat'],3).','.round($aResult['lon'],3).'</span>';
// echo ' <span class="place_id">'.$aResult['place_id'].'</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 detailsPermaLink($aResult, 'details', 'class="btn btn-default btn-xs details"');
echo '</div>';
$i = $i+1;
}
if (!empty($aSearchResults) && $sMoreURL)
{
echo '<div class="more"><a class="btn btn-primary" href="'.htmlentities($sMoreURL).'">Search for more results</a></div>';
}
else
{
echo '<div class="noresults">No search results found</div>';
}
?>
</div>
<?php } else { ?>
<div id="intro" class="sidebar">
<?php include(CONST_BasePath.'/lib/template/includes/introduction.php'); ?>
</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' => CONST_Default_Zoom,
'lat' => CONST_Default_Lat,
'lon' => CONST_Default_Lon,
'tile_url' => CONST_Map_Tile_URL,
'tile_attribution' => CONST_Map_Tile_Attribution
);
echo 'var nominatim_map_init = ' . json_encode($aNominatimMapInit, JSON_PRETTY_PRINT) . ';';
echo 'var nominatim_results = ' . json_encode($aSearchResults, JSON_PRETTY_PRINT) . ';';
$sStructuredQuery = (empty($aMoreParams['q'])
&& !(empty($aMoreParams['street'])
&& empty($aMoreParams['city'])
&& empty($aMoreParams['county'])
&& empty($aMoreParams['state'])
&& empty($aMoreParams['country'])
&& empty($aMoreParams['postalcode'])))
? 'true' : 'false';
echo 'var nominatim_structured_query = '.$sStructuredQuery.';';
?>
</script>
<?php include(CONST_BasePath.'/lib/template/includes/html-footer.php'); ?>
</body>
</html>

View File

@@ -25,6 +25,7 @@ foreach ($aSearchResults as $iResNum => $aResult) {
echo " osm_id='".$aResult['osm_id']."'";
}
echo " place_rank='".$aResult['rank_search']."'";
echo " address_rank='".$aResult['rank_address']."'";
if (isset($aResult['aBoundingBox'])) {
echo ' boundingbox="';

View File

@@ -1,5 +1,14 @@
# just use the pgxs makefile
find_program(PG_CONFIG pg_config)
foreach(suffix ${PostgreSQL_ADDITIONAL_VERSIONS} "13" "12" "11" "10" "9.6" "9.5" "9.4" "9.3")
list(APPEND PG_CONFIG_HINTS
"/usr/pgsql-${suffix}/bin")
endforeach()
find_program(PG_CONFIG pg_config HINTS ${PG_CONFIG_HINTS})
execute_process(COMMAND ${PG_CONFIG} --pgxs
OUTPUT_VARIABLE PGXS
OUTPUT_STRIP_TRAILING_WHITESPACE)

View File

112
nominatim/indexer/db.py Normal file
View File

@@ -0,0 +1,112 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# This file is part of Nominatim.
# Copyright (C) 2020 Sarah Hoffmann
import logging
import psycopg2
from psycopg2.extras import wait_select
log = logging.getLogger()
def make_connection(options, asynchronous=False):
params = {'dbname' : options.dbname,
'user' : options.user,
'password' : options.password,
'host' : options.host,
'port' : options.port,
'async' : asynchronous}
return psycopg2.connect(**params)
class DBConnection(object):
""" A single non-blocking database connection.
"""
def __init__(self, options):
self.current_query = None
self.current_params = None
self.options = options
self.conn = None
self.connect()
def connect(self):
""" (Re)connect to the database. Creates an asynchronous connection
with JIT and parallel processing disabled. If a connection was
already open, it is closed and a new connection established.
The caller must ensure that no query is pending before reconnecting.
"""
if self.conn is not None:
self.cursor.close()
self.conn.close()
self.conn = make_connection(self.options, asynchronous=True)
self.wait()
self.cursor = self.conn.cursor()
# Disable JIT and parallel workers as they are known to cause problems.
# Update pg_settings instead of using SET because it does not yield
# errors on older versions of Postgres where the settings are not
# implemented.
self.perform(
""" UPDATE pg_settings SET setting = -1 WHERE name = 'jit_above_cost';
UPDATE pg_settings SET setting = 0
WHERE name = 'max_parallel_workers_per_gather';""")
self.wait()
def wait(self):
""" Block until any pending operation is done.
"""
while True:
try:
wait_select(self.conn)
self.current_query = None
return
except psycopg2.extensions.TransactionRollbackError as e:
if e.pgcode == '40P01':
log.info("Deadlock detected (params = {}), retry."
.format(self.current_params))
self.cursor.execute(self.current_query, self.current_params)
else:
raise
except psycopg2.errors.DeadlockDetected:
self.cursor.execute(self.current_query, self.current_params)
def perform(self, sql, args=None):
""" Send SQL query to the server. Returns immediately without
blocking.
"""
self.current_query = sql
self.current_params = args
self.cursor.execute(sql, args)
def fileno(self):
""" File descriptor to wait for. (Makes this class select()able.)
"""
return self.conn.fileno()
def is_done(self):
""" Check if the connection is available for a new query.
Also checks if the previous query has run into a deadlock.
If so, then the previous query is repeated.
"""
if self.current_query is None:
return True
try:
if self.conn.poll() == psycopg2.extensions.POLL_OK:
self.current_query = None
return True
except psycopg2.extensions.TransactionRollbackError as e:
if e.pgcode == '40P01':
log.info("Deadlock detected (params = {}), retry.".format(self.current_params))
self.cursor.execute(self.current_query, self.current_params)
else:
raise
except psycopg2.errors.DeadlockDetected:
self.cursor.execute(self.current_query, self.current_params)
return False

View File

@@ -0,0 +1,64 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# This file is part of Nominatim.
# Copyright (C) 2020 Sarah Hoffmann
"""
Helpers for progress logging.
"""
import logging
from datetime import datetime
LOG = logging.getLogger()
INITIAL_PROGRESS = 10
class ProgressLogger:
""" Tracks and prints progress for the indexing process.
`name` is the name of the indexing step being tracked.
`total` sets up the total number of items that need processing.
`log_interval` denotes the interval in seconds at which progres
should be reported.
"""
def __init__(self, name, total, log_interval=1):
self.name = name
self.total_places = total
self.done_places = 0
self.rank_start_time = datetime.now()
self.log_interval = log_interval
self.next_info = INITIAL_PROGRESS if LOG.isEnabledFor(logging.INFO) else total + 1
def add(self, num=1):
""" Mark `num` places as processed. Print a log message if the
logging is at least info and the log interval has passed.
"""
self.done_places += num
if self.done_places < self.next_info:
return
now = datetime.now()
done_time = (now - self.rank_start_time).total_seconds()
if done_time < 2:
self.next_info = self.done_places + INITIAL_PROGRESS
return
places_per_sec = self.done_places / done_time
eta = (self.total_places - self.done_places) / places_per_sec
LOG.info("Done %d in %d @ %.3f per second - %s ETA (seconds): %.2f",
self.done_places, int(done_time),
places_per_sec, self.name, eta)
self.next_info += int(places_per_sec) * self.log_interval
def done(self):
""" Print final statistics about the progress.
"""
rank_end_time = datetime.now()
diff_seconds = (rank_end_time-self.rank_start_time).total_seconds()
LOG.warning("Done %d/%d in %d @ %.3f per second - FINISHED %s\n",
self.done_places, self.total_places, int(diff_seconds),
self.done_places/diff_seconds, self.name)

View File

@@ -28,23 +28,13 @@ import sys
import re
import getpass
from datetime import datetime
import psycopg2
from psycopg2.extras import wait_select
import select
from indexer.progress import ProgressLogger
from indexer.db import DBConnection, make_connection
log = logging.getLogger()
def make_connection(options, asynchronous=False):
params = {'dbname' : options.dbname,
'user' : options.user,
'password' : options.password,
'host' : options.host,
'port' : options.port,
'async' : asynchronous}
return psycopg2.connect(**params)
class RankRunner(object):
""" Returns SQL commands for indexing one rank within the placex table.
"""
@@ -55,24 +45,19 @@ class RankRunner(object):
def name(self):
return "rank {}".format(self.rank)
def sql_index_sectors(self):
return """SELECT geometry_sector, count(*) FROM placex
WHERE rank_search = {} and indexed_status > 0
GROUP BY geometry_sector
def sql_count_objects(self):
return """SELECT count(*) FROM placex
WHERE rank_address = {} and indexed_status > 0
""".format(self.rank)
def sql_get_objects(self):
return """SELECT place_id FROM placex
WHERE indexed_status > 0 and rank_address = {}
ORDER BY geometry_sector""".format(self.rank)
def sql_nosector_places(self):
return """SELECT place_id FROM placex
WHERE indexed_status > 0 and rank_search = {}
ORDER BY geometry_sector""".format(self.rank)
def sql_sector_places(self):
return """SELECT place_id FROM placex
WHERE indexed_status > 0 and rank_search = {}
and geometry_sector = %s""".format(self.rank)
def sql_index_place(self):
return "UPDATE placex SET indexed_status = 0 WHERE place_id = %s"
def sql_index_place(self, ids):
return "UPDATE placex SET indexed_status = 0 WHERE place_id IN ({})"\
.format(','.join((str(i) for i in ids)))
class InterpolationRunner(object):
@@ -83,210 +68,119 @@ class InterpolationRunner(object):
def name(self):
return "interpolation lines (location_property_osmline)"
def sql_index_sectors(self):
return """SELECT geometry_sector, count(*) FROM location_property_osmline
WHERE indexed_status > 0
GROUP BY geometry_sector
ORDER BY geometry_sector"""
def sql_count_objects(self):
return """SELECT count(*) FROM location_property_osmline
WHERE indexed_status > 0"""
def sql_nosector_places(self):
def sql_get_objects(self):
return """SELECT place_id FROM location_property_osmline
WHERE indexed_status > 0
ORDER BY geometry_sector"""
def sql_sector_places(self):
return """SELECT place_id FROM location_property_osmline
WHERE indexed_status > 0 and geometry_sector = %s
ORDER BY geometry_sector"""
def sql_index_place(self):
def sql_index_place(self, ids):
return """UPDATE location_property_osmline
SET indexed_status = 0 WHERE place_id = %s"""
SET indexed_status = 0 WHERE place_id IN ({})"""\
.format(','.join((str(i) for i in ids)))
class DBConnection(object):
""" A single non-blocking database connection.
class BoundaryRunner(object):
""" Returns SQL commands for indexing the administrative boundaries
of a certain rank.
"""
def __init__(self, options):
self.current_query = None
self.current_params = None
def __init__(self, rank):
self.rank = rank
self.conn = None
self.connect()
def name(self):
return "boundaries rank {}".format(self.rank)
def connect(self):
if self.conn is not None:
self.cursor.close()
self.conn.close()
def sql_count_objects(self):
return """SELECT count(*) FROM placex
WHERE indexed_status > 0
AND rank_search = {}
AND class = 'boundary' and type = 'administrative'""".format(self.rank)
self.conn = make_connection(options, asynchronous=True)
self.wait()
self.cursor = self.conn.cursor()
# Disable JIT and parallel workers as they are known to cause problems.
# Update pg_settings instead of using SET because it does not yield
# errors on older versions of Postgres where the settings are not
# implemented.
self.perform(
""" UPDATE pg_settings SET setting = -1 WHERE name = 'jit_above_cost';
UPDATE pg_settings SET setting = 0
WHERE name = 'max_parallel_workers_per_gather';""")
self.wait()
def wait(self):
""" Block until any pending operation is done.
"""
while True:
try:
wait_select(self.conn)
self.current_query = None
return
except psycopg2.extensions.TransactionRollbackError as e:
if e.pgcode == '40P01':
log.info("Deadlock detected (params = {}), retry."
.format(self.current_params))
self.cursor.execute(self.current_query, self.current_params)
else:
raise
except psycopg2.errors.DeadlockDetected:
self.cursor.execute(self.current_query, self.current_params)
def perform(self, sql, args=None):
""" Send SQL query to the server. Returns immediately without
blocking.
"""
self.current_query = sql
self.current_params = args
self.cursor.execute(sql, args)
def fileno(self):
""" File descriptor to wait for. (Makes this class select()able.)
"""
return self.conn.fileno()
def is_done(self):
""" Check if the connection is available for a new query.
Also checks if the previous query has run into a deadlock.
If so, then the previous query is repeated.
"""
if self.current_query is None:
return True
try:
if self.conn.poll() == psycopg2.extensions.POLL_OK:
self.current_query = None
return True
except psycopg2.extensions.TransactionRollbackError as e:
if e.pgcode == '40P01':
log.info("Deadlock detected (params = {}), retry.".format(self.current_params))
self.cursor.execute(self.current_query, self.current_params)
else:
raise
except psycopg2.errors.DeadlockDetected:
self.cursor.execute(self.current_query, self.current_params)
return False
def sql_get_objects(self):
return """SELECT place_id FROM placex
WHERE indexed_status > 0 and rank_search = {}
and class = 'boundary' and type = 'administrative'
ORDER BY partition, admin_level""".format(self.rank)
def sql_index_place(self, ids):
return "UPDATE placex SET indexed_status = 0 WHERE place_id IN ({})"\
.format(','.join((str(i) for i in ids)))
class Indexer(object):
""" Main indexing routine.
"""
def __init__(self, options):
self.minrank = max(0, options.minrank)
self.minrank = max(1, options.minrank)
self.maxrank = min(30, options.maxrank)
self.conn = make_connection(options)
self.threads = [DBConnection(options) for i in range(options.threads)]
def run(self):
""" Run indexing over the entire database.
def index_boundaries(self):
log.warning("Starting indexing boundaries using {} threads".format(
len(self.threads)))
for rank in range(max(self.minrank, 5), min(self.maxrank, 26)):
self.index(BoundaryRunner(rank))
def index_by_rank(self):
""" Run classic indexing by rank.
"""
log.warning("Starting indexing rank ({} to {}) using {} threads".format(
self.minrank, self.maxrank, len(self.threads)))
for rank in range(self.minrank, self.maxrank):
for rank in range(max(1, self.minrank), self.maxrank):
self.index(RankRunner(rank))
if self.maxrank == 30:
self.index(InterpolationRunner())
self.index(RankRunner(0))
self.index(InterpolationRunner(), 20)
self.index(RankRunner(self.maxrank), 20)
else:
self.index(RankRunner(self.maxrank))
self.index(RankRunner(self.maxrank))
def index(self, obj):
def index(self, obj, batch=1):
""" Index a single rank or table. `obj` describes the SQL to use
for indexing.
for indexing. `batch` describes the number of objects that
should be processed with a single SQL statement
"""
log.warning("Starting {}".format(obj.name()))
log.warning("Starting %s (using batch size %s)", obj.name(), batch)
cur = self.conn.cursor(name='main')
cur.execute(obj.sql_index_sectors())
cur = self.conn.cursor()
cur.execute(obj.sql_count_objects())
total_tuples = 0
for r in cur:
total_tuples += r[1]
log.debug("Total number of rows; {}".format(total_tuples))
cur.scroll(0, mode='absolute')
next_thread = self.find_free_thread()
done_tuples = 0
rank_start_time = datetime.now()
sector_sql = obj.sql_sector_places()
index_sql = obj.sql_index_place()
min_grouped_tuples = total_tuples - len(self.threads) * 1000
next_info = 100 if log.isEnabledFor(logging.INFO) else total_tuples + 1
for r in cur:
sector = r[0]
# Should we do the remaining ones together?
do_all = done_tuples > min_grouped_tuples
pcur = self.conn.cursor(name='places')
if do_all:
pcur.execute(obj.sql_nosector_places())
else:
pcur.execute(sector_sql, (sector, ))
for place in pcur:
place_id = place[0]
log.debug("Processing place {}".format(place_id))
thread = next(next_thread)
thread.perform(index_sql, (place_id,))
done_tuples += 1
if done_tuples >= next_info:
now = datetime.now()
done_time = (now - rank_start_time).total_seconds()
tuples_per_sec = done_tuples / done_time
log.info("Done {} in {} @ {:.3f} per second - {} ETA (seconds): {:.2f}"
.format(done_tuples, int(done_time),
tuples_per_sec, obj.name(),
(total_tuples - done_tuples)/tuples_per_sec))
next_info += int(tuples_per_sec)
pcur.close()
if do_all:
break
total_tuples = cur.fetchone()[0]
log.debug("Total number of rows: {}".format(total_tuples))
cur.close()
for t in self.threads:
t.wait()
progress = ProgressLogger(obj.name(), total_tuples)
rank_end_time = datetime.now()
diff_seconds = (rank_end_time-rank_start_time).total_seconds()
if total_tuples > 0:
cur = self.conn.cursor(name='places')
cur.execute(obj.sql_get_objects())
log.warning("Done {}/{} in {} @ {:.3f} per second - FINISHED {}\n".format(
done_tuples, total_tuples, int(diff_seconds),
done_tuples/diff_seconds, obj.name()))
next_thread = self.find_free_thread()
while True:
places = [p[0] for p in cur.fetchmany(batch)]
if len(places) == 0:
break
log.debug("Processing places: {}".format(places))
thread = next(next_thread)
thread.perform(obj.sql_index_place(places))
progress.add(len(places))
cur.close()
for t in self.threads:
t.wait()
progress.done()
def find_free_thread(self):
""" Generator that returns the next connection that is free for
@@ -340,6 +234,9 @@ def nominatim_arg_parser():
p.add_argument('-P', '--port',
dest='port', action='store',
help='PostgreSQL server port')
p.add_argument('-b', '--boundary-only',
dest='boundary_only', action='store_true',
help='Only index administrative boundaries (ignores min/maxrank).')
p.add_argument('-r', '--minrank',
dest='minrank', type=int, metavar='RANK', default=0,
help='Minimum/starting rank.')
@@ -367,4 +264,7 @@ if __name__ == '__main__':
password = getpass.getpass("Database password: ")
options.password = password
Indexer(options).run()
if options.boundary_only:
Indexer(options).index_boundaries()
else:
Indexer(options).index_by_rank()

View File

@@ -2,33 +2,36 @@
{ "tags" : {
"place" : {
"sea" : [2, 0],
"island" : [17, 0],
"islet" : [20, 0],
"continent" : [2, 0],
"country" : [4, 0],
"state" : [8, 0],
"province" : [8, 0],
"region" : [18, 0],
"county" : 12,
"municipality" : [17, 14],
"district" : 12,
"municipality" : 14,
"city" : 16,
"island" : [17, 0],
"town" : [18, 16],
"borough" : 18,
"village" : [19, 16],
"district" : [19, 16],
"borough" : [19, 18],
"suburb" : [19, 20],
"hamlet" : 20,
"suburb" : 20,
"croft" : 20,
"subdivision" : 20,
"isolated_dwelling" : 20,
"allotments" : 20,
"farm" : [20, 0],
"locality" : [20, 0],
"islet" : [20, 0],
"mountain_pass" : [20, 0],
"neighbourhood" : 22,
"quarter" : 22,
"city_block" : 22,
"houses" : [28, 0]
"subdivision" : 22,
"allotments" : 22,
"neighbourhood" : [20, 22],
"quarter" : [20, 22],
"isolated_dwelling" : [22, 20],
"farm" : [22, 20],
"city_block" : 25,
"mountain_pass" : 25,
"square" : 25,
"locality" : 25,
"houses" : [28, 0],
"house" : 30,
"" : [22, 0]
},
"boundary" : {
"administrative2" : 4,
@@ -41,7 +44,8 @@
"administrative9" : 18,
"administrative10" : 20,
"administrative11" : 22,
"administrative12" : 24
"administrative12" : 24,
"" : [25, 0]
},
"landuse" : {
"residential" : 22,
@@ -60,7 +64,15 @@
"peak" : [18, 0],
"volcano" : [18, 0],
"mountain_range" : [18, 0],
"sea" : [4, 0]
"sea" : [4, 0],
"tree" : 30,
"spring" : 30,
"cave_entrance" : 30,
"geyser" : 30,
"hot_spring" : 30,
"rock" : 30,
"stone" : 30,
"" : [22, 0]
},
"waterway" : {
"river" : [19, 0],
@@ -87,7 +99,7 @@
"" : [20, 0]
},
"historic" : {
"neighbourhood" : [30, 0]
"neighbourhood" : [22, 0]
}
}
},
@@ -105,7 +117,14 @@
{ "countries" : [ "be" ],
"tags" : {
"boundary" : {
"administrative7" : [14, 0]
"administrative3" : [5, 0],
"administrative4" : 6,
"administrative5" : [7, 0],
"administrative6" : 8,
"administrative7" : 12,
"administrative8" : 14,
"administrative9" : 16,
"administrative10" : 18
}
}
},
@@ -118,6 +137,43 @@
"administrative4" : 12
}
}
},
{ "countries" : ["id"],
"tags" : {
"place" : {
"municipality" : 18
},
"boundary" : {
"administrative5" : 12,
"administrative6" : 14,
"administrative7" : 16,
"administrative8" : 20,
"administrative9" : 22,
"administrative10" : 24
}
}
},
{ "countries" : ["ru"],
"tags" : {
"place" : {
"municipality" : 18
},
"boundary" : {
"administrative5" : [10, 0],
"administrative7" : [13, 0],
"administrative8" : 14
}
}
},
{ "countries" : [ "nl" ],
"tags" : {
"boundary" : {
"administrative7" : [13, 0],
"administrative8" : 14,
"administrative9" : [15, 0],
"administrative10" : 16
}
}
}
]

View File

@@ -3,10 +3,8 @@
@define('CONST_InstallPath', '@CMAKE_BINARY_DIR@');
if (file_exists(getenv('NOMINATIM_SETTINGS'))) require_once(getenv('NOMINATIM_SETTINGS'));
if (file_exists(CONST_InstallPath.'/settings/local.php')) require_once(CONST_InstallPath.'/settings/local.php');
if (isset($_GET['debug']) && $_GET['debug']) @define('CONST_Debug', true);
// General settings
@define('CONST_Debug', false);
@define('CONST_Database_DSN', 'pgsql:dbname=nominatim'); // or add ;host=...;port=...;user=...;password=...
@define('CONST_Database_Web_User', 'www-data');
@define('CONST_Database_Module_Path', CONST_InstallPath.'/module');
@@ -84,20 +82,16 @@ if (isset($_GET['debug']) && $_GET['debug']) @define('CONST_Debug', true);
// How long to sleep when no update could be found
@define('CONST_Replication_Recheck_Interval', '60');
// Website settings
// If true, send CORS headers to allow access
@define('CONST_NoAccessControl', true);
@define('CONST_Website_BaseURL', 'http://'.php_uname('n').'/');
// Set this to the /mapicon directory of your nominatim-ui to enable returning
// icon URLs with the results.
@define('CONST_MapIcon_URL', false);
// Language to assume when none is supplied with the query.
// When set to false, the local language (i.e. the name tag without suffix)
// will be used.
@define('CONST_Default_Language', false);
// Appearance of the map in the debug interface.
@define('CONST_Default_Lat', 20.0);
@define('CONST_Default_Lon', 0.0);
@define('CONST_Default_Zoom', 2);
@define('CONST_Map_Tile_URL', 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
@define('CONST_Map_Tile_Attribution', ''); // Set if tile source isn't osm.org
@define('CONST_Search_AreaPolygons', true);
@@ -105,7 +99,7 @@ if (isset($_GET['debug']) && $_GET['debug']) @define('CONST_Debug', true);
@define('CONST_Search_NameOnlySearchFrequencyThreshold', 500);
// If set to true, then reverse order of queries will be tried by default.
// When set to false only selected languages alloow reverse search.
// When set to false only selected languages allow reverse search.
@define('CONST_Search_ReversePlanForAll', true);
// Maximum number of OSM ids that may be queried at once

View File

@@ -5,8 +5,14 @@
"no" : "skip"
}
},
{ "keys" : ["wikipedia", "wikipedia:*", "wikidata", "area"],
"values" : {
"" : "extra"
}
},
{
"keys" : ["name:prefix", "name:suffix", "name:botanical", "*wikidata"],
"keys" : ["name:prefix", "name:suffix", "name:prefix:*", "name:suffix:*",
"name:botanical", "*wikidata"],
"values" : {
"" : "skip"
}
@@ -38,7 +44,8 @@
{
"keys" : ["boundary"],
"values" : {
"administrative" : "main"
"administrative" : "main",
"postal_code" : "main"
}
},
{

View File

@@ -1,6 +1,12 @@
[
{ "keys" : ["wikipedia", "wikipedia:*", "wikidata"],
"values" : {
"" : "extra"
}
},
{
"keys" : ["name:prefix", "name:suffix", "name:botanical", "*wikidata"],
"keys" : ["name:prefix", "name:suffix", "name:prefix:*", "name:suffix:*",
"name:botanical", "*wikidata"],
"values" : {
"" : "skip"
}

View File

@@ -6,8 +6,8 @@
}
},
{
"keys" : ["name:prefix", "name:suffix", "name:botanical", "wikidata",
"*:wikidata"],
"keys" : ["name:prefix", "name:suffix", "name:prefix:*", "name:suffix:*",
"name:botanical", "wikidata", "*:wikidata"],
"values" : {
"" : "extra"
}
@@ -76,6 +76,9 @@
"mini_roundabout" : "skip",
"noexit" : "skip",
"crossing" : "skip",
"give_way" : "skip",
"stop" : "skip",
"street_lamp" : "main,with_name",
"traffic_signals" : "main,with_name",
"service" : "main,with_name",
"cycleway" : "main,with_name",
@@ -121,6 +124,8 @@
{
"keys" : ["boundary"],
"values" : {
"place" : "skip",
"postal_code" : "main",
"" : "main,with_name"
}
},
@@ -206,7 +211,7 @@
}
},
{
"keys" : ["addr:*", "is_in:*", "tiger:county", "is_in"],
"keys" : ["addr:*", "is_in:*", "tiger:county"],
"values" : {
"" : "address"
}

View File

@@ -6,8 +6,8 @@
}
},
{
"keys" : ["name:prefix", "name:suffix", "name:botanical", "wikidata",
"*:wikidata"],
"keys" : ["name:prefix", "name:suffix", "name:prefix:*", "name:suffix:*",
"name:botanical", "wikidata", "*:wikidata"],
"values" : {
"" : "extra"
}
@@ -76,6 +76,9 @@
"mini_roundabout" : "skip",
"noexit" : "skip",
"crossing" : "skip",
"give_way" : "skip",
"stop" : "skip",
"street_lamp" : "main,with_name",
"traffic_signals" : "main,with_name",
"service" : "main,with_name",
"cycleway" : "main,with_name",
@@ -121,6 +124,8 @@
{
"keys" : ["boundary"],
"values" : {
"place" : "skip",
"postal_code" : "main",
"" : "main,with_name"
}
},
@@ -206,7 +211,7 @@
}
},
{
"keys" : ["addr:*", "is_in:*", "tiger:county", "is_in"],
"keys" : ["addr:*", "is_in:*", "tiger:county"],
"values" : {
"" : "address"
}
@@ -232,7 +237,8 @@
"population", "description", "image", "attribution", "fax",
"email", "url", "website", "phone", "real_ale", "smoking",
"food", "camera", "brewery", "locality", "wikipedia",
"wikipedia:*", "access:*", "contact:*", "drink:*", "toll:*"],
"wikipedia:*", "access:*", "contact:*", "drink:*", "toll:*",
"area"],
"values" : {
"" : "extra"
}

View File

@@ -1,6 +1,12 @@
[
{ "keys" : ["wikipedia", "wikipedia:*", "wikidata", "area"],
"values" : {
"" : "extra"
}
},
{
"keys" : ["name:prefix", "name:suffix", "name:botanical", "*wikidata"],
"keys" : ["name:prefix", "name:suffix", "name:prefix:*", "name:suffix:*",
"name:botanical", "*wikidata"],
"values" : {
"" : "skip"
}

View File

@@ -79,6 +79,18 @@ END;
$$
LANGUAGE plpgsql STABLE;
DROP TYPE IF EXISTS addressdata_place;
CREATE TYPE addressdata_place AS (
place_id BIGINT,
country_code VARCHAR(2),
housenumber TEXT,
postcode TEXT,
class TEXT,
type TEXT,
name HSTORE,
address HSTORE,
centroid GEOMETRY
);
-- Compute the list of address parts for the given place.
--
@@ -87,124 +99,127 @@ CREATE OR REPLACE FUNCTION get_addressdata(in_place_id BIGINT, in_housenumber IN
RETURNS setof addressline
AS $$
DECLARE
for_place_id BIGINT;
result TEXT[];
search TEXT[];
found INTEGER;
place addressdata_place;
location RECORD;
countrylocation RECORD;
searchcountrycode varchar(2);
searchhousenumber TEXT;
searchhousename HSTORE;
searchrankaddress INTEGER;
searchpostcode TEXT;
postcode_isexact BOOL;
searchclass TEXT;
searchtype TEXT;
countryname HSTORE;
current_rank_address INTEGER;
location_isaddress BOOLEAN;
BEGIN
-- The place ein question might not have a direct entry in place_addressline.
-- Look for the parent of such places then and save if in for_place_id.
postcode_isexact := false;
-- The place in question might not have a direct entry in place_addressline.
-- Look for the parent of such places then and save it in place.
-- first query osmline (interpolation lines)
IF in_housenumber >= 0 THEN
SELECT parent_place_id, country_code, in_housenumber::text, 30, postcode,
null, 'place', 'house'
SELECT parent_place_id as place_id, country_code,
in_housenumber as housenumber, postcode,
'place' as class, 'house' as type,
null as name, null as address,
ST_Centroid(linegeo) as centroid
INTO place
FROM location_property_osmline
WHERE place_id = in_place_id AND in_housenumber>=startnumber
AND in_housenumber <= endnumber
INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress,
searchpostcode, searchhousename, searchclass, searchtype;
WHERE place_id = in_place_id
AND in_housenumber between startnumber and endnumber;
END IF;
--then query tiger data
-- %NOTIGERDATA% IF 0 THEN
IF for_place_id IS NULL AND in_housenumber >= 0 THEN
SELECT parent_place_id, 'us', in_housenumber::text, 30, postcode, null,
'place', 'house'
IF place IS NULL AND in_housenumber >= 0 THEN
SELECT parent_place_id as place_id, 'us' as country_code,
in_housenumber as housenumber, postcode,
'place' as class, 'house' as type,
null as name, null as address,
ST_Centroid(linegeo) as centroid
INTO place
FROM location_property_tiger
WHERE place_id = in_place_id AND in_housenumber >= startnumber
AND in_housenumber <= endnumber
INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress,
searchpostcode, searchhousename, searchclass, searchtype;
WHERE place_id = in_place_id
AND in_housenumber between startnumber and endnumber;
END IF;
-- %NOTIGERDATA% END IF;
-- %NOAUXDATA% IF 0 THEN
IF for_place_id IS NULL THEN
SELECT parent_place_id, 'us', housenumber, 30, postcode, null, 'place', 'house'
IF place IS NULL THEN
SELECT parent_place_id as place_id, 'us' as country_code,
housenumber, postcode,
'place' as class, 'house' as type,
null as name, null as address,
centroid
INTO place
FROM location_property_aux
WHERE place_id = in_place_id
INTO for_place_id,searchcountrycode, searchhousenumber, searchrankaddress,
searchpostcode, searchhousename, searchclass, searchtype;
WHERE place_id = in_place_id;
END IF;
-- %NOAUXDATA% END IF;
-- postcode table
IF for_place_id IS NULL THEN
SELECT parent_place_id, country_code, rank_search, postcode, 'place', 'postcode'
IF place IS NULL THEN
SELECT parent_place_id as place_id, country_code,
null::text as housenumber, postcode,
'place' as class, 'postcode' as type,
null as name, null as address,
null as centroid
INTO place
FROM location_postcode
WHERE place_id = in_place_id
INTO for_place_id, searchcountrycode, searchrankaddress, searchpostcode,
searchclass, searchtype;
WHERE place_id = in_place_id;
END IF;
-- POI objects in the placex table
IF for_place_id IS NULL THEN
SELECT parent_place_id, country_code, housenumber, rank_search,
postcode, address is not null and address ? 'postcode',
name, class, type
IF place IS NULL THEN
SELECT parent_place_id as place_id, country_code,
housenumber, postcode,
class, type,
name, address,
centroid
INTO place
FROM placex
WHERE place_id = in_place_id and rank_search > 27
INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress,
searchpostcode, postcode_isexact, searchhousename, searchclass, searchtype;
WHERE place_id = in_place_id and rank_search > 27;
END IF;
-- If for_place_id is still NULL at this point then the object has its own
-- If place is still NULL at this point then the object has its own
-- entry in place_address line. However, still check if there is not linked
-- place we should be using instead.
IF for_place_id IS NULL THEN
select coalesce(linked_place_id, place_id), country_code,
housenumber, rank_search, postcode,
address is not null and address ? 'postcode', null
from placex where place_id = in_place_id
INTO for_place_id, searchcountrycode, searchhousenumber, searchrankaddress, searchpostcode, postcode_isexact, searchhousename;
IF place IS NULL THEN
select coalesce(linked_place_id, place_id) as place_id, country_code,
housenumber, postcode,
class, type,
null as name, address,
null as centroid
INTO place
FROM placex where place_id = in_place_id;
END IF;
--RAISE WARNING '% % % %',searchcountrycode, searchhousenumber, searchrankaddress, searchpostcode;
--RAISE WARNING '% % % %',searchcountrycode, searchhousenumber, searchpostcode;
found := 1000; -- the lowest rank_address included
-- --- Return the record for the base entry.
-- Return the record for the base entry.
FOR location IN
SELECT placex.place_id, osm_type, osm_id, name,
coalesce(extratags->'linked_place', extratags->'place') as place_type,
class, type, admin_level,
type not in ('postcode', 'postal_code') as isaddress,
CASE WHEN rank_address = 0 THEN 100
WHEN rank_address = 11 THEN 5
ELSE rank_address END as rank_address,
0 as distance, country_code, postcode
country_code
FROM placex
WHERE place_id = for_place_id
WHERE place_id = place.place_id
LOOP
--RAISE WARNING '%',location;
IF searchcountrycode IS NULL AND location.country_code IS NOT NULL THEN
searchcountrycode := location.country_code;
END IF;
IF location.rank_address < 4 THEN
-- no country locations for ranks higher than country
searchcountrycode := NULL;
place.country_code := NULL::varchar(2);
ELSEIF place.country_code IS NULL AND location.country_code IS NOT NULL THEN
place.country_code := location.country_code;
END IF;
countrylocation := ROW(location.place_id, location.osm_type, location.osm_id,
location.name, location.class, location.type, NULL,
location.admin_level, true, location.isaddress,
location.rank_address, location.distance)::addressline;
RETURN NEXT countrylocation;
found := location.rank_address;
RETURN NEXT ROW(location.place_id, location.osm_type, location.osm_id,
location.name, location.class, location.type,
location.place_type,
location.admin_level, true,
location.type not in ('postcode', 'postal_code'),
location.rank_address, 0)::addressline;
current_rank_address := location.rank_address;
END LOOP;
-- --- Return records for address parts.
FOR location IN
SELECT placex.place_id, osm_type, osm_id, name, class, type,
coalesce(extratags->'linked_place', extratags->'place') as place_type,
@@ -212,73 +227,86 @@ BEGIN
CASE WHEN rank_address = 11 THEN 5 ELSE rank_address END as rank_address,
distance, country_code, postcode
FROM place_addressline join placex on (address_place_id = placex.place_id)
WHERE place_addressline.place_id = for_place_id
AND (cached_rank_address >= 4 AND cached_rank_address < searchrankaddress)
WHERE place_addressline.place_id IN (place.place_id, in_place_id)
AND linked_place_id is null
AND (placex.country_code IS NULL OR searchcountrycode IS NULL
OR placex.country_code = searchcountrycode)
ORDER BY rank_address desc, isaddress desc, fromarea desc,
AND (placex.country_code IS NULL OR place.country_code IS NULL
OR placex.country_code = place.country_code)
ORDER BY rank_address desc,
(place_addressline.place_id = in_place_id) desc,
(fromarea and place.centroid is not null and not isaddress
and (place.address is null or avals(name) && avals(place.address))
and ST_Contains(geometry, place.centroid)) desc,
isaddress desc, fromarea desc,
distance asc, rank_search desc
LOOP
--RAISE WARNING '%',location;
IF searchcountrycode IS NULL AND location.country_code IS NOT NULL THEN
searchcountrycode := location.country_code;
-- RAISE WARNING '%',location;
location_isaddress := location.rank_address != current_rank_address;
IF place.country_code IS NULL AND location.country_code IS NOT NULL THEN
place.country_code := location.country_code;
END IF;
IF location.type in ('postcode', 'postal_code')
AND searchpostcode is not null
AND place.postcode is not null
THEN
-- If the place had a postcode assigned, take this one only
-- into consideration when it is an area and the place does not have
-- a postcode itself.
IF location.fromarea AND not postcode_isexact AND location.isaddress THEN
searchpostcode := null; -- remove the less exact postcode
IF location.fromarea AND location.isaddress
AND (place.address is null or not place.address ? 'postcode')
THEN
place.postcode := null; -- remove the less exact postcode
ELSE
location.isaddress := false;
location_isaddress := false;
END IF;
END IF;
countrylocation := ROW(location.place_id, location.osm_type, location.osm_id,
location.name, location.class, location.type,
location.place_type,
location.admin_level, location.fromarea,
location.isaddress, location.rank_address,
location.distance)::addressline;
RETURN NEXT countrylocation;
found := location.rank_address;
RETURN NEXT ROW(location.place_id, location.osm_type, location.osm_id,
location.name, location.class, location.type,
location.place_type,
location.admin_level, location.fromarea,
location_isaddress,
location.rank_address,
location.distance)::addressline;
current_rank_address := location.rank_address;
END LOOP;
-- If no country was included yet, add the name information from country_name.
IF found > 4 THEN
SELECT name FROM country_name
WHERE country_code = searchcountrycode LIMIT 1 INTO countryname;
--RAISE WARNING '% % %',found,searchcountrycode,countryname;
IF countryname IS NOT NULL THEN
location := ROW(null, null, null, countryname, 'place', 'country', NULL,
IF current_rank_address > 4 THEN
FOR location IN
SELECT name FROM country_name WHERE country_code = place.country_code LIMIT 1
LOOP
--RAISE WARNING '% % %',current_rank_address,searchcountrycode,countryname;
RETURN NEXT ROW(null, null, null, location.name, 'place', 'country', NULL,
null, true, true, 4, 0)::addressline;
RETURN NEXT location;
END IF;
END LOOP;
END IF;
-- Finally add some artificial rows.
IF searchcountrycode IS NOT NULL THEN
location := ROW(null, null, null, hstore('ref', searchcountrycode),
IF place.country_code IS NOT NULL THEN
location := ROW(null, null, null, hstore('ref', place.country_code),
'place', 'country_code', null, null, true, false, 4, 0)::addressline;
RETURN NEXT location;
END IF;
IF searchhousename IS NOT NULL THEN
location := ROW(in_place_id, null, null, searchhousename, searchclass,
searchtype, null, null, true, true, 29, 0)::addressline;
IF place.name IS NOT NULL THEN
location := ROW(in_place_id, null, null, place.name, place.class,
place.type, null, null, true, true, 29, 0)::addressline;
RETURN NEXT location;
END IF;
IF searchhousenumber IS NOT NULL THEN
location := ROW(in_place_id, null, null, hstore('ref', searchhousenumber),
IF place.housenumber IS NOT NULL THEN
location := ROW(null, null, null, hstore('ref', place.housenumber),
'place', 'house_number', null, null, true, true, 28, 0)::addressline;
RETURN NEXT location;
END IF;
IF searchpostcode IS NOT NULL THEN
location := ROW(null, null, null, hstore('ref', searchpostcode), 'place',
IF place.address is not null and place.address ? '_unlisted_place' THEN
RETURN NEXT ROW(null, null, null, hstore('name', place.address->'_unlisted_place'),
'place', 'locality', null, null, true, true, 25, 0)::addressline;
END IF;
IF place.postcode is not null THEN
location := ROW(null, null, null, hstore('ref', place.postcode), 'place',
'postcode', null, null, false, true, 5, 0)::addressline;
RETURN NEXT location;
END IF;

View File

@@ -81,7 +81,8 @@ BEGIN
lookup_word := upper(trim(postcode));
lookup_token := ' ' || make_standard_name(lookup_word);
SELECT min(word_id) FROM word
WHERE word_token = lookup_token and class='place' and type='postcode'
WHERE word_token = lookup_token and word = lookup_word
and class='place' and type='postcode'
INTO return_word_id;
IF return_word_id IS NULL THEN
return_word_id := nextval('seq_word');
@@ -206,17 +207,35 @@ CREATE OR REPLACE FUNCTION addr_ids_from_name(lookup_word TEXT)
RETURNS INTEGER[]
AS $$
DECLARE
lookup_token TEXT;
words TEXT[];
id INTEGER;
return_word_id INTEGER[];
word_ids INTEGER[];
j INTEGER;
BEGIN
lookup_token := make_standard_name(lookup_word);
SELECT array_agg(word_id) FROM word
WHERE word_token = lookup_token and class is null and type is null
INTO return_word_id;
words := string_to_array(make_standard_name(lookup_word), ' ');
IF array_upper(words, 1) IS NOT NULL THEN
FOR j IN 1..array_upper(words, 1) LOOP
IF (words[j] != '') THEN
SELECT array_agg(word_id) INTO word_ids
FROM word
WHERE word_token = words[j] and class is null and type is null;
IF word_ids IS NULL THEN
id := nextval('seq_word');
INSERT INTO word VALUES (id, words[j], null, null, null, null, 0);
return_word_id := return_word_id || id;
ELSE
return_word_id := array_merge(return_word_id, word_ids);
END IF;
END IF;
END LOOP;
END IF;
RETURN return_word_id;
END;
$$
LANGUAGE plpgsql STABLE;
LANGUAGE plpgsql;
-- Normalize a string and look up its name ids (full words).
@@ -405,3 +424,103 @@ BEGIN
END;
$$
LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION create_poi_search_terms(obj_place_id BIGINT,
in_partition SMALLINT,
parent_place_id BIGINT,
address HSTORE,
country TEXT,
housenumber TEXT,
initial_name_vector INTEGER[],
geometry GEOMETRY,
OUT name_vector INTEGER[],
OUT nameaddress_vector INTEGER[])
AS $$
DECLARE
parent_name_vector INTEGER[];
parent_address_vector INTEGER[];
addr_place_ids INTEGER[];
addr_item RECORD;
parent_address_place_ids BIGINT[];
filtered_address HSTORE;
BEGIN
nameaddress_vector := '{}'::INTEGER[];
SELECT s.name_vector, s.nameaddress_vector
INTO parent_name_vector, parent_address_vector
FROM search_name s
WHERE s.place_id = parent_place_id;
-- Find all address tags that don't appear in the parent search names.
SELECT hstore(array_agg(ARRAY[k, v])) INTO filtered_address
FROM (SELECT skeys(address) as k, svals(address) as v) a
WHERE not addr_ids_from_name(v) && parent_address_vector
AND k not in ('country', 'street', 'place', 'postcode',
'housenumber', 'streetnumber', 'conscriptionnumber');
-- Compute all search terms from the addr: tags.
IF filtered_address IS NOT NULL THEN
FOR addr_item IN
SELECT * FROM
get_places_for_addr_tags(in_partition, geometry, filtered_address, country)
LOOP
IF addr_item.place_id is null THEN
nameaddress_vector := array_merge(nameaddress_vector,
addr_item.keywords);
CONTINUE;
END IF;
IF parent_address_place_ids is null THEN
SELECT array_agg(parent_place_id) INTO parent_address_place_ids
FROM place_addressline
WHERE place_id = parent_place_id;
END IF;
IF not parent_address_place_ids @> ARRAY[addr_item.place_id] THEN
nameaddress_vector := array_merge(nameaddress_vector,
addr_item.keywords);
INSERT INTO place_addressline (place_id, address_place_id, fromarea,
isaddress, distance, cached_rank_address)
VALUES (obj_place_id, addr_item.place_id, not addr_item.isguess,
true, addr_item.distance, addr_item.rank_address);
END IF;
END LOOP;
END IF;
name_vector := initial_name_vector;
-- Check if the parent covers all address terms.
-- If not, create a search name entry with the house number as the name.
-- This is unusual for the search_name table but prevents that the place
-- is returned when we only search for the street/place.
IF housenumber is not null and not nameaddress_vector <@ parent_address_vector THEN
name_vector := array_merge(name_vector,
ARRAY[getorcreate_housenumber_id(make_standard_name(housenumber))]);
END IF;
IF not address ? 'street' and address ? 'place' THEN
addr_place_ids := addr_ids_from_name(address->'place');
IF not addr_place_ids <@ parent_name_vector THEN
-- make sure addr:place terms are always searchable
nameaddress_vector := array_merge(nameaddress_vector, addr_place_ids);
-- If there is a housenumber, also add the place name as a name,
-- so we can search it by the usual housenumber+place algorithms.
IF housenumber is not null THEN
name_vector := array_merge(name_vector,
ARRAY[getorcreate_name_id(make_standard_name(address->'place'))]);
END IF;
END IF;
END IF;
-- Cheating here by not recomputing all terms but simply using the ones
-- from the parent object.
nameaddress_vector := array_merge(nameaddress_vector, parent_name_vector);
nameaddress_vector := array_merge(nameaddress_vector, parent_address_vector);
END;
$$
LANGUAGE plpgsql;

View File

@@ -162,14 +162,14 @@ BEGIN
IF st_area(NEW.geometry) < 0.000000001 AND st_area(existinggeometry) < 1 THEN
-- re-index points that have moved in / out of the polygon, could be done as a single query but postgres gets the index usage wrong
update placex set indexed_status = 2 where indexed_status = 0 and
(st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry))
AND NOT (st_covers(existinggeometry, placex.geometry) OR ST_Intersects(existinggeometry, placex.geometry))
update placex set indexed_status = 2 where indexed_status = 0
AND ST_Intersects(NEW.geometry, placex.geometry)
AND NOT ST_Intersects(existinggeometry, placex.geometry)
AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null);
update placex set indexed_status = 2 where indexed_status = 0 and
(st_covers(existinggeometry, placex.geometry) OR ST_Intersects(existinggeometry, placex.geometry))
AND NOT (st_covers(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry))
update placex set indexed_status = 2 where indexed_status = 0
AND ST_Intersects(existinggeometry, placex.geometry)
AND NOT ST_Intersects(NEW.geometry, placex.geometry)
AND rank_search > existingplacex.rank_search AND (rank_search < 28 or name is not null);
END IF;
@@ -262,7 +262,7 @@ BEGIN
-- deleting large polygons can have a massive effect on the system - require manual intervention to let them through
IF st_area(OLD.geometry) > 2 and st_isvalid(OLD.geometry) THEN
SELECT bool_or(not (rank_address = 0 or rank_address > 26)) as ranked FROM placex WHERE osm_type = OLD.osm_type and osm_id = OLD.osm_id and class = OLD.class and type = OLD.type INTO has_rank;
SELECT bool_or(not (rank_address = 0 or rank_address > 25)) as ranked FROM placex WHERE osm_type = OLD.osm_type and osm_id = OLD.osm_id and class = OLD.class and type = OLD.type INTO has_rank;
IF has_rank THEN
insert into import_polygon_delete (osm_type, osm_id, class, type) values (OLD.osm_type,OLD.osm_id,OLD.class,OLD.type);
RETURN NULL;

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