Skip to content

Implement multipolygon support for table buildings #2476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 13 additions & 21 deletions analysers/Analyser_Osmosis.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,34 +242,25 @@ class Analyser_Osmosis(Analyser):
sql_create_buildings = """
CREATE UNLOGGED TABLE {0}.buildings AS
SELECT
*,
CASE WHEN polygon_proj IS NOT NULL AND wall THEN ST_Area(polygon_proj) ELSE NULL END AS area
FROM (
SELECT DISTINCT ON (id)
id,
type || id AS type_id,
tags,
linestring,
CASE WHEN ST_IsValid(linestring) = 't' AND ST_IsSimple(linestring) = 't' AND ST_IsValid(ST_MakePolygon(ST_Transform(linestring, {1}))) THEN ST_MakePolygon(ST_Transform(linestring, {1})) ELSE NULL END AS polygon_proj,
poly,
poly_proj,
(NOT tags?'wall' OR tags->'wall' != 'no') AND tags->'building' != 'roof' AS wall,
tags?'layer' AS layer,
ST_NPoints(linestring) AS npoints,
relation_members.relation_id IS NOT NULL AS relation
ST_NPoints(poly) AS npoints,
ST_Area(poly_proj) AS area
FROM
ways
LEFT JOIN relation_members ON
relation_members.member_type = 'W' AND
relation_members.member_id = ways.id
polygons
WHERE
tags != ''::hstore AND
tags?'building' AND
tags->'building' != 'no' AND
is_polygon
) AS t
tags->'building' != 'no'
;

CREATE INDEX idx_buildings_linestring ON {0}.buildings USING GIST(linestring);
CREATE INDEX idx_buildings_linestring_wall ON {0}.buildings USING GIST(linestring) WHERE wall;
CREATE INDEX idx_buildings_polygon_proj ON {0}.buildings USING gist(polygon_proj);
CREATE INDEX idx_buildings_poly ON {0}.buildings USING GIST(poly);
CREATE INDEX idx_buildings_poly_wall ON {0}.buildings USING GIST(poly) WHERE wall;
CREATE INDEX idx_buildings_poly_proj ON {0}.buildings USING gist(poly_proj);
ANALYZE {0}.buildings;
"""

Expand Down Expand Up @@ -411,13 +402,14 @@ def requires_tables_build(self, tables):
self.requires_tables_build(["polygons"])
self.create_view_not_touched('polygons', ['W', 'R'])
elif table == 'buildings':
self.requires_tables_build(["polygons"])
self.giscurs.execute(self.sql_create_buildings.format(self.config.db_schema.split(',')[0], self.config.options.get("proj")))
elif table == 'touched_buildings':
self.requires_tables_build(["buildings"])
self.create_view_touched('buildings', 'W')
self.create_view_touched('buildings', ['W', 'R'])
elif table == 'not_touched_buildings':
self.requires_tables_build(["buildings"])
self.create_view_not_touched('buildings', 'W')
self.create_view_not_touched('buildings', ['W', 'R'])
else:
raise Exception('Unknown table name {0}'.format(table))
self.giscurs.execute('COMMIT')
Expand Down
9 changes: 4 additions & 5 deletions analysers/analyser_osmosis_building_geodesie_FR.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,12 @@
DROP VIEW IF EXISTS vicinity CASCADE;
CREATE VIEW vicinity AS
SELECT
survery_building.id AS s_id,
ways.id AS b_id
survery_building.id AS s_id
FROM
survery_building
JOIN buildings AS ways ON
survery_building.geom && ways.linestring AND
ST_DWithIn(survery_building.geom_transform, polygon_proj, 0.5)
JOIN buildings AS b ON
survery_building.geom && b.poly AND
ST_DWithIn(survery_building.geom_transform, poly_proj, 0.5)
"""

sql13 = """
Expand Down
34 changes: 8 additions & 26 deletions analysers/analyser_osmosis_building_in_polygon.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,11 @@
sql10 = """
CREATE TEMP TABLE poly_landuse AS
SELECT
type_id,
type || id AS type_id,
poly_proj,
tags->'landuse' AS landuse
FROM (
SELECT
'W' || id AS type_id,
tags,
ST_Transform(ST_MakePolygon(ways.linestring), {proj}) AS poly_proj
FROM
ways
WHERE
is_polygon AND
tags != ''::hstore
UNION ALL
SELECT
'R' || id AS type_id,
tags,
poly_proj
FROM
multipolygons
WHERE
is_valid
) AS t
FROM
polygons
WHERE
tags?'landuse' AND
tags->'landuse' IN ('farmland', 'vineyard', 'orchard', 'plant_nursery')
Expand All @@ -57,15 +39,15 @@
sql11 = """
SELECT DISTINCT ON (poly_landuse.type_id)
poly_landuse.type_id,
buildings.id,
buildings.type_id,
-- Use the building location to point out the error, even though the landuse should be changed
ST_AsText(way_locate(buildings.linestring)),
ST_AsText(polygon_locate(buildings.poly)),
buildings.tags->'building',
poly_landuse.landuse
FROM
buildings
JOIN poly_landuse ON
ST_Contains(poly_landuse.poly_proj, buildings.polygon_proj)
ST_Contains(poly_landuse.poly_proj, buildings.poly_proj)
WHERE
-- ignore e.g. small utility buildings that happen to be on the farmland or are fully surrounded by farmland
buildings.area > 36 AND
Expand Down Expand Up @@ -96,13 +78,13 @@ def __init__(self, config, logger = None):
For areas dedicated to greenhouse horticulture, use `landuse=greenhouse_horticulture`.'''),
resource = "https://wiki.openstreetmap.org/wiki/Tag:landuse%3Dfarmland")

requires_tables_common = ['buildings', 'multipolygons']
requires_tables_common = ['buildings', 'polygons']

def analyser_osmosis_common(self):
self.run(sql10.format(proj=self.config.options["proj"]))
self.run(sql11, lambda res: {
"class": 1,
"data": [self.any_full, self.way, self.positionAsText],
"data": [self.any_full, self.any_id, self.positionAsText],
"text": T_("`{0}` inside `{1}`", "building=" + res[3], 'landuse=' + res[4])
})

Expand Down
93 changes: 40 additions & 53 deletions analysers/analyser_osmosis_building_overlaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,12 @@
sql20 = """
CREATE TEMP TABLE bnodes AS
SELECT
id,
ST_PointN(ST_ExteriorRing(polygon_proj), generate_series(1, npoints)) AS point_proj
id, -- needed for 'touched' view
type_id,
ST_PointN(ST_ExteriorRing(poly_proj), generate_series(1, npoints)) AS point_proj
FROM
buildings
WHERE
polygon_proj IS NOT NULL AND
NOT relation AND
NOT layer AND
wall
"""
Expand All @@ -46,28 +45,24 @@
sql30 = """
CREATE TEMP TABLE intersection_{0}_{1} AS
SELECT
b1.id AS id1,
b2.id AS id2,
ST_AsText(ST_Transform(ST_Centroid(ST_Intersection(b1.polygon_proj, b2.polygon_proj)), 4326)),
ST_Area(ST_Intersection(b1.polygon_proj, b2.polygon_proj)) AS intersectionArea,
b1.type_id AS id1,
b2.type_id AS id2,
ST_AsText(ST_Transform(ST_Centroid(ST_Intersection(b1.poly_proj, b2.poly_proj)), 4326)),
ST_Area(ST_Intersection(b1.poly_proj, b2.poly_proj)) AS intersectionArea,
least(b1.area, b2.area) * 0.10 AS threshold,
b1.polygon_proj
b1.poly_proj
FROM
{0}buildings AS b1
JOIN {1}buildings AS b2 ON
b1.id > b2.id AND
b1.linestring && b2.linestring AND
ST_IsValid(ST_Intersection(b1.polygon_proj, b2.polygon_proj)) AND
ST_Area(ST_Intersection(b1.polygon_proj, b2.polygon_proj)) > 0
b1.type_id > b2.type_id AND
b1.poly && b2.poly AND
ST_IsValid(ST_Intersection(b1.poly_proj, b2.poly_proj)) AND
ST_Area(ST_Intersection(b1.poly_proj, b2.poly_proj)) > 0
WHERE
b1.wall AND
b2.wall AND
NOT b1.relation AND
NOT b2.relation AND
NOT b1.layer AND
NOT b2.layer AND
b1.polygon_proj IS NOT NULL AND
b2.polygon_proj IS NOT NULL
NOT b2.layer
"""

sql31 = """
Expand All @@ -79,41 +74,37 @@

sql40 = """
SELECT
id,
ST_AsText(ST_Transform(ST_Centroid(polygon_proj), 4326))
type_id,
ST_AsText(ST_Transform(ST_Centroid(poly_proj), 4326))
FROM
{0}buildings
WHERE
NOT relation AND
NOT layer AND
polygon_proj IS NOT NULL AND
wall AND
area < 0.5 * 0.5
"""

sql50 = """
SELECT
DISTINCT ON (bnodes.id)
buildings.id,
bnodes.id,
DISTINCT ON (bnodes.type_id)
buildings.type_id,
bnodes.type_id,
ST_AsText(ST_Transform(bnodes.point_proj, 4326))
FROM
{0}buildings AS buildings
JOIN {1}bnodes AS bnodes ON
buildings.id > bnodes.id AND
buildings.type_id > bnodes.type_id AND
-- Help planner to use index, both ST_Expand to let planner choose
ST_Expand(buildings.polygon_proj, 0.01) && bnodes.point_proj AND
ST_Expand(bnodes.point_proj, 0.01) && buildings.polygon_proj AND
ST_Expand(bnodes.point_proj, 0.01) && buildings.polygon_proj AND
ST_DWithin(buildings.polygon_proj, bnodes.point_proj, 0.01) AND
ST_Disjoint(buildings.polygon_proj, bnodes.point_proj)
ST_Expand(buildings.poly_proj, 0.01) && bnodes.point_proj AND
ST_Expand(bnodes.point_proj, 0.01) && buildings.poly_proj AND
ST_Expand(bnodes.point_proj, 0.01) && buildings.poly_proj AND
ST_DWithin(buildings.poly_proj, bnodes.point_proj, 0.01) AND
ST_Disjoint(buildings.poly_proj, bnodes.point_proj)
WHERE
NOT buildings.relation AND
NOT buildings.layer AND
buildings.polygon_proj IS NOT NULL AND
buildings.wall
ORDER BY
bnodes.id
bnodes.type_id
"""

sql60 = """
Expand All @@ -123,7 +114,7 @@
FROM
(
SELECT
(ST_Dump(ST_Union(ST_Buffer(polygon_proj, 200, 'quad_segs=2')))).geom AS geom
(ST_Dump(ST_Union(ST_Buffer(poly_proj, 200, 'quad_segs=2')))).geom AS geom
FROM
intersection_{0}_{1}
WHERE
Expand All @@ -135,28 +126,24 @@

sql70 = """
SELECT
DISTINCT ON (b2.id)
b2.id,
b1.id,
ST_AsText(way_locate(b2.linestring))
DISTINCT ON (b2.type_id)
b2.type_id,
b1.type_id,
ST_AsText(polygon_locate(b2.poly))
FROM
{0}buildings AS b1
JOIN {1}buildings AS b2 ON
b2.id != b1.id AND
b2.type_id != b1.type_id AND
b1.tags->'building' = b2.tags->'building' AND
b1.wall = b2.wall AND
ST_Intersects(b1.polygon_proj, b2.polygon_proj) AND
ST_Dimension(ST_Intersection(b1.polygon_proj, b2.polygon_proj)) > 0 AND
ST_Intersects(b1.poly_proj, b2.poly_proj) AND
ST_Dimension(ST_Intersection(b1.poly_proj, b2.poly_proj)) > 0 AND
b2.npoints = 4
WHERE
NOT b1.relation AND
NOT b2.relation AND
NOT b1.layer AND
NOT b2.layer AND
b1.polygon_proj IS NOT NULL AND
b2.polygon_proj IS NOT NULL
NOT b2.layer
ORDER BY
b2.id
b2.type_id
"""

class Analyser_Osmosis_Building_Overlaps(Analyser_Osmosis):
Expand Down Expand Up @@ -199,12 +186,12 @@ def __init__(self, config, logger = None):
title = T_("Building in parts"),
fix = T_('Merge the building parts together as appropriate.'))

self.callback30 = lambda res: {"class":2 if res[3] > res[4] else 1, "data":[self.way, self.way, self.positionAsText]}
self.callback40 = lambda res: {"class":3, "data":[self.way, self.positionAsText]}
self.callback50 = lambda res: {"class":4, "data":[self.way, self.way, self.positionAsText]}
self.callback30 = lambda res: {"class":2 if res[3] > res[4] else 1, "data":[self.any_id, self.any_id, self.positionAsText]}
self.callback40 = lambda res: {"class":3, "data":[self.any_full, self.positionAsText]}
self.callback50 = lambda res: {"class":4, "data":[self.any_id, self.any_id, self.positionAsText]}
self.callback60 = lambda res: {"class":5, "subclass": stablehash64(res[0]), "data":[self.positionAsText]}
if self.FR:
self.callback70 = lambda res: {"class":6, "data":[self.way, self.way, self.positionAsText]}
self.callback70 = lambda res: {"class":6, "data":[self.any_id, self.any_id, self.positionAsText]}

def analyser_osmosis_full(self):
self.run(sql20)
Expand All @@ -220,7 +207,7 @@ def analyser_osmosis_full(self):
def analyser_osmosis_diff(self):
self.run(sql20)
self.run(sql21)
self.create_view_touched("bnodes", "W")
self.create_view_touched("bnodes", ["W", "R"])
self.run(sql30.format("touched_", ""))
self.run(sql30.format("not_touched_", "touched_"))
self.run(sql31.format("touched_", ""), self.callback30)
Expand Down
20 changes: 9 additions & 11 deletions analysers/analyser_osmosis_building_shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,29 @@

sql10 = """
SELECT
id,
ST_AsText(way_locate(linestring))
type_id,
ST_AsText(polygon_locate(poly))
FROM
{0}buildings
WHERE
wall AND
npoints > 15 AND
polygon_proj IS NOT NULL AND
area / ST_Area(ST_MinimumBoundingCircle(polygon_proj)) > 0.95 AND
ST_MaxDistance(polygon_proj, polygon_proj) > 5 AND
area / ST_Area(ST_MinimumBoundingCircle(poly_proj)) > 0.95 AND
ST_MaxDistance(poly_proj, poly_proj) > 5 AND
tags - ARRAY['created_by', 'source', 'name', 'building', 'man_made', 'note:qadastre'] = ''::hstore AND
tags->'building' NOT IN ('hut', 'ger', 'yurt', 'slurry_tank') AND
tags->'man_made' NOT IN ('water_tower', 'reservoir_covered', 'wastewater_plant', 'storage_tank', 'windmill', 'dovecote', 'silo', 'gasometer', 'lighthouse', 'bioreactor')
"""

sql20 = """
SELECT
id,
ST_AsText(way_locate(linestring))
type_id,
ST_AsText(polygon_locate(poly))
FROM
{0}buildings
WHERE
wall AND
polygon_proj IS NOT NULL AND
ST_MaxDistance(polygon_proj, polygon_proj) > 300 AND
ST_MaxDistance(poly_proj, poly_proj) > 300 AND
tags - ARRAY['created_by', 'source', 'name', 'building', 'note:qadastre'] = ''::hstore AND
tags->'building' NOT IN ('warehouse', 'industrial', 'greenhouse', 'manufacture', 'hospital', 'university')
"""
Expand All @@ -73,7 +71,7 @@ def __init__(self, config, logger = None):
title = T_('Special building (large)'),
detail = detail)

self.callback10 = lambda res: {"class":1, "data":[self.way_full, self.positionAsText], "fix":[
self.callback10 = lambda res: {"class":1, "data":[self.any_full, self.positionAsText], "fix":[
{"+":{"man_made":"water_tower"}},
{"+":{"man_made":"reservoir_covered"}},
{"+":{"man_made":"wastewater_plant"}},
Expand All @@ -84,7 +82,7 @@ def __init__(self, config, logger = None):
{"+":{"building":"hut"}},
{"+":{"building":"ger"}},
]}
self.callback20 = lambda res: {"class":2, "data":[self.way_full, self.positionAsText], "fix":[
self.callback20 = lambda res: {"class":2, "data":[self.any_full, self.positionAsText], "fix":[
{"+":{"man_made":"works"}},
{"+":{"shop":"mall"}},
{"+":{"shop":"supermarket"}},
Expand Down
Loading
Loading