Skip to content
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

Unify building parts with their building #969

Merged
merged 2 commits into from
Aug 18, 2016
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
4 changes: 2 additions & 2 deletions docs/layers.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ Combination of OpenStreetMap administrative boundaries (zoom >= 8) and Natural E

Polygons from OpenStreetMap representing building footprint, building label_placement points, building_part features, and address points. Starts at zoom 13 by including huge buildings, progressively adding all buildings at zoom 16+. Address points are available at zoom 16+, but marked with `min_zoom: 17` to suggest that they are suitable for display at zoom level 17 and higher.

Individual `building:part` geometries from OSM following the [Simple 3D Buildings](http://wiki.openstreetmap.org/wiki/Simple_3D_Buildings) tags at higher zoom levels are now exported as `building_part` features with specified `kind_detail`.
Individual `building:part` geometries from OSM following the [Simple 3D Buildings](http://wiki.openstreetmap.org/wiki/Simple_3D_Buildings) tags at higher zoom levels are now exported as `building_part` features with specified `kind_detail`. Building parts may receive a `root_id` corresponding to the building feature, if any, with which they intersect.

Mapzen calculates the `landuse_kind` value by intercutting `buildings` with the `landuse` layer to determine if a building is over a parks, hospitals, universities or other landuse features. Use this property to modify the visual appearance of buildings over these features. For instance, light grey buildings look great in general, but aren't legible over most landuse colors unless they are darkened (or colorized to match landuse styling).

Expand Down Expand Up @@ -480,7 +480,7 @@ To resolve inconsistency in data tagging in OpenStreetMap we normalize several o
* `light_rail_routes` a list of light rail or rapid-transit passenger train routes.
* `tram_routes` a list of tram routes.
* `is_*` a set of boolean flags indicating whether this station has any routes of the given type. These are: `is_train`, `is_subway`, `is_light_rail`, `is_tram`, corresponding to the above `*_routes`. This is provided as a convenience for styling.
* `root_relation_id` an integer ID (of an OSM relation) which can be used to link or group together features which are related by being part of a larger feature. A full explanation of [relations](http://wiki.openstreetmap.org/wiki/Relation) wouldn't fit here, but the general idea is that all the station features which are part of the same [site](http://wiki.openstreetmap.org/wiki/Relation:site), [stop area](http://wiki.openstreetmap.org/wiki/Tag:public_transport%3Dstop_area) or [stop area group](http://wiki.openstreetmap.org/wiki/Relation:public_transport) should have the same ID to show they're related. Note that this information is only present on some stations.
* `root_id` an integer ID (of an OSM relation) which can be used to link or group together features which are related by being part of a larger feature. A full explanation of [relations](http://wiki.openstreetmap.org/wiki/Relation) wouldn't fit here, but the general idea is that all the station features which are part of the same [site](http://wiki.openstreetmap.org/wiki/Relation:site), [stop area](http://wiki.openstreetmap.org/wiki/Tag:public_transport%3Dstop_area) or [stop area group](http://wiki.openstreetmap.org/wiki/Relation:public_transport) should have the same ID to show they're related. Note that this information is only present on some stations.

#### POI properties (only on `kind:bicycle_rental_station`):

Expand Down
41 changes: 41 additions & 0 deletions integration-test/653-unify-building-part.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# http://www.openstreetmap.org/way/264768910
# Way: One Madison
assert_has_feature(
16, 19298, 24633, 'buildings',
{ 'id': 264768910, 'kind': 'building', 'root_id': type(None) })

# http://www.openstreetmap.org/way/160967738
assert_has_feature(
16, 19298, 24633, 'buildings',
{ 'id': 160967738, 'kind': 'building_part', 'root_id': 264768910 })

#http://www.openstreetmap.org/way/160967739
assert_has_feature(
16, 19298, 24633, 'buildings',
{ 'id': 160967739, 'kind': 'building_part', 'root_id': 264768910 })


# http://www.openstreetmap.org/relation/6062613
# Relation: Ferry Building
assert_has_feature(
16, 10486, 25326, 'buildings',
{ 'id': 24460886, 'kind': 'building', 'root_id': type(None) })

# http://www.openstreetmap.org/way/404449724
assert_has_feature(
16, 10486, 25326, 'buildings',
{ 'id': 404449724, 'kind': 'building_part', 'root_id': 24460886 })


# http://www.openstreetmap.org/relation/1242762
# Relation: Waterloo (tube and rail)
# http://www.openstreetmap.org/relation/238793
# Relation: Waterloo (tube station)
# http://www.openstreetmap.org/relation/238792
# Relation: London Waterloo
assert_has_feature(
16, 32747, 21793, 'pois',
{ 'id': 3638795617, 'root_id': 1242762, 'root_relation_id': type(None) })
assert_has_feature(
16, 32747, 21793, 'pois',
{ 'id': 3638795618, 'root_id': 1242762, 'root_relation_id': type(None) })
10 changes: 8 additions & 2 deletions queries.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -651,27 +651,33 @@ post_process:
params:
source_layer: buildings
start_zoom: 13
end_zoom: 15
end_zoom: 14
quantize:
13: vectordatasource.transform.quantize_height_round_nearest_10_meters
14: vectordatasource.transform.quantize_height_round_nearest_5_meters
15: vectordatasource.transform.quantize_height_round_nearest_meter
drop:
- name
- addr_housenumber
- addr_street

- fn: vectordatasource.transform.simplify_and_clip
params: {simplify_before: 16}

- fn: vectordatasource.transform.intercut
params:
base_layer: roads
cutting_layer: landuse
attribute: kind
target_attribute: landuse_kind
cutting_attrs: { sort_key: 'sort_key', reverse: True }

- fn: vectordatasource.transform.merge_features
params:
source_layer: roads
start_zoom: 8
end_zoom: 15

- fn: vectordatasource.transform.buildings_unify
params:
source_layer: buildings
start_zoom: 15
91 changes: 91 additions & 0 deletions test/test_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,94 @@ def test_geometry_type(self):
self.assertIsNotNone(sort_key_result)
_, sort_key = sort_key_result
self.assertEquals(int(sort_key), 223)


class BuildingsUnifyTest(unittest.TestCase):

def _call_fut(self, building_shapes, building_part_shapes):
from tilequeue.tile import deserialize_coord
from tilequeue.process import Context

building_features = []
building_id = 1
for building_shape in building_shapes:
building_props = dict(
id=building_id,
kind='building',
)
building_feature = building_shape, building_props, building_id
building_features.append(building_feature)
building_id += 1

part_features = []
building_part_id = building_id
for part_shape in building_part_shapes:
part_props = dict(
id=building_part_id,
kind='building_part',
)
part_feature = part_shape, part_props, building_part_id
part_features.append(part_feature)
building_part_id += 1

building_features = building_features + part_features
building_feature_layer = dict(
features=building_features,
layer_datum=dict(name='buildings'),
)
feature_layers = [building_feature_layer]

ctx = Context(
feature_layers=feature_layers,
tile_coord=deserialize_coord('0/0/0'),
unpadded_bounds=None,
params=dict(source_layer='buildings'),
resources=None)
from vectordatasource.transform import buildings_unify
buildings_unify(ctx)
return building_feature_layer['features']

def test_no_overlap(self):
import shapely.geometry
building_shape = shapely.geometry.Polygon(
[(1, 1), (2, 2), (1, 2), (1, 1)])
part_shape = shapely.geometry.Polygon(
[(10, 10), (20, 20), (10, 20), (10, 10)])
result = self._call_fut([building_shape], [part_shape])
building, part = result
part_props = part[1]
assert 'root_id' not in part_props

def test_overlap(self):
import shapely.geometry
building_shape = shapely.geometry.Polygon(
[(1, 1), (20, 20), (10, 20), (1, 1)])
part_shape = shapely.geometry.Polygon(
[(10, 10), (20, 20), (10, 20), (10, 10)])
result = self._call_fut([building_shape], [part_shape])
building, part = result
part_props = part[1]
root_id = part_props.get('root_id')
self.assertEquals(root_id, 1)

def test_best_overlap(self):
import shapely.geometry
building1_shape = shapely.geometry.Polygon(
[(2, 1), (2, 2), (0, 2), (2, 1)])
building2_shape = shapely.geometry.Polygon(
[(1, 1), (20, 20), (10, 20), (1, 1)])
building3_shape = shapely.geometry.Polygon(
[(19, 1), (30, 30), (19, 30), (19, 1)])
part_shape = shapely.geometry.Polygon(
[(10, 10), (20, 20), (10, 20), (10, 10)])

building_shapes = [building1_shape, building2_shape, building3_shape]
part_shapes = [part_shape]
result = self._call_fut(building_shapes, part_shapes)
for feature in result:
props = feature[1]
if props['kind'] == 'building_part':
root_id = props.get('root_id')
self.assertEquals(root_id, 2)
else:
assert 'root_id' not in props
65 changes: 64 additions & 1 deletion vectordatasource/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -2441,7 +2441,7 @@ def normalize_station_properties(ctx):
# that as a way for the client to link together related
# features.
if root_relation_id:
props['root_relation_id'] = root_relation_id
props['root_id'] = root_relation_id

return layer

Expand Down Expand Up @@ -3825,3 +3825,66 @@ def network_key(t):
properties['all_shield_texts'] = [n[2] for n in networks]

return (shape, properties, fid)


def buildings_unify(ctx):
"""
Unify buildings with their parts. Building parts will receive a
root_id property which will be the id of building parent they are
associated with.
"""
zoom = ctx.tile_coord.zoom
start_zoom = ctx.params.get('start_zoom', 0)

if zoom < start_zoom:
return None

source_layer = ctx.params.get('source_layer')
assert source_layer is not None, 'unify_buildings: missing source_layer'
feature_layers = ctx.feature_layers
layer = _find_layer(feature_layers, source_layer)
if layer is None:
return None

class geom_with_building_id(object):
def __init__(self, geom, building_id):
self.geom = geom
self.building_id = building_id
self._geom = geom._geom
self.is_empty = geom.is_empty

indexable_buildings = []
parts = []
for feature in layer['features']:
shape, props, feature_id = feature
kind = props.get('kind')
if kind == 'building':
building_id = props.get('id')
if building_id:
indexed_building = geom_with_building_id(shape, building_id)
indexable_buildings.append(indexed_building)
elif kind == 'building_part':
parts.append(feature)

if not (indexable_buildings and parts):
return

buildings_index = STRtree(indexable_buildings)

for part in parts:
best_overlap = 0
root_building_id = None

part_shape, part_props, part_feature_id = part

indexed_buildings = buildings_index.query(part_shape)
for indexed_building in indexed_buildings:
building_shape = indexed_building.geom
intersection = part_shape.intersection(building_shape)
overlap = intersection.area
if overlap > best_overlap:
best_overlap = overlap
root_building_id = indexed_building.building_id

if root_building_id is not None:
part_props['root_id'] = root_building_id