Skip to content

Feat route relations #34

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

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Changelog
---------

unreleased
~~~~~~~~~~
- add possibility to import route relations
- add possibility to use ":" and similar characters in tag/column
names when generalizing such tables

2.5.0 2012-12-06
~~~~~~~~~~~~~~~~

Expand Down
11 changes: 11 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
This fork extends the original `imposm` by

* possibility to import route relations
* allow ":" and similar characters in tag names when creating generalized tables

The changes are inspired by those published by `michalmacki <https://bitbucket.org/michalmacki/imposm-routes/>`_.
As those seem to be based on an outdated imposm, they are contained in an hg
repository instead of git, and I saw the need for some slight changes (most
notably support of routes in the ContainsRelationBuilder, not just
UnionRelationBuilder), I replicated several of his changes.

Imposm is an importer for OpenStreetMap data. It reads XML and PBF files and
can import the data into PostgreSQL/PostGIS databases.

Expand Down
2 changes: 1 addition & 1 deletion imposm/db/postgis.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ def _geom_table_stmt(self):
return stmt

def _stmt(self):
fields = ', '.join([n for n, t in self.mapping.fields])
fields = ', '.join(['"'+n+'"' for n, t in self.mapping.fields])
if fields:
fields += ','

Expand Down
4 changes: 3 additions & 1 deletion imposm/dbimporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,9 @@ def doit(self):
break

for relation in relations:

builder = RelationBuilder(relation, ways_cache, coords_cache)

try:
builder.build()
except IncompletePolygonError, ex:
Expand All @@ -270,7 +272,7 @@ def doit(self):
mappings = self.mapper.for_relations(relation.tags)
if mappings:
inserted = self.insert(mappings, relation.osm_id, relation.geom, relation.tags)
if inserted and any(getattr(m, 'skip_inserted_ways', False) for _, m in mappings):
if inserted and any(getattr(m, 'skip_inserted_ways', False) for _, ms in mappings for m in ms):
builder.mark_inserted_ways(self.inserted_way_queue)

class RelationProcessDict(RelationProcess, DictBasedImporter):
Expand Down
17 changes: 17 additions & 0 deletions imposm/defaultmapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,23 @@ class Highway(LineStrings):
)}
)

routes = LineStrings(
name = 'routes',
fields = (
('name', String()),
('network', String()),
('colour', String()),
('color', String()),
('ref', String()),
('operator', String()),
('osmc:symbol', String()),
),
mapping = {
'route': (
'__any__',
)}
)

transport_areas = Polygons(
name = 'transport_areas',
mapping = {
Expand Down
10 changes: 4 additions & 6 deletions imposm/geom.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
from __future__ import division

import math
import codecs
import os
import shapely.geometry
import shapely.geos
import shapely.prepared
Expand All @@ -32,7 +30,7 @@
rtree = None

from imposm import config
from imposm.util.geom import load_polygons, load_datasource, build_multipolygon
from imposm.util.geom import load_datasource, build_multipolygon

import logging
log = logging.getLogger(__name__)
Expand Down Expand Up @@ -166,8 +164,8 @@ def to_wkt(self, data):
return 'LINESTRING(' + ', '.join('%f %f' % p for p in data) + ')'

def check_geom_type(self, geom):
if geom.type != 'LineString':
raise InvalidGeometryError('expected LineString, got %s' % geom.type)
if geom.type not in ('LineString', 'MultiLineString'):
raise InvalidGeometryError('expected LineString or MultiLineString, got %s' % geom.type)

def to_geom(self, data, max_length=None):
if len(data) <= 1:
Expand All @@ -178,7 +176,7 @@ def to_geom(self, data, max_length=None):
max_length = config.imposm_linestring_max_length
if max_length and len(data) > max_length:
chunks = math.ceil(len(data) / max_length)
length = int(len(data) // chunks)
length = int((len(data)-1) // chunks) + 1
lines = []
for i in xrange(1, len(data), length):
lines.append(geometry.LineString(data[i-1:i+length]))
Expand Down
52 changes: 29 additions & 23 deletions imposm/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,8 @@ def for_ways(self, tags):
self._mapping_for_tags(self.polygon_mappings, tags))

def for_relations(self, tags):
return self._mapping_for_tags(self.polygon_mappings, tags)
return (self._mapping_for_tags(self.line_mappings, tags) +
self._mapping_for_tags(self.polygon_mappings, tags))

def _tag_filter(self, filter_tags):
def filter(tags):
Expand Down Expand Up @@ -252,25 +253,29 @@ def tag_filter_for_relations(self):
tags.setdefault(k, set()).update(v)
for k, v in self.polygon_tags.iteritems():
tags.setdefault(k, set()).update(v)
tags['type'] = set(['multipolygon', 'boundary', 'land_area']) # for type=multipolygon
expected_tags = set(['type', 'name'])
_rel_filter = self._tag_filter(tags)
def rel_filter(tags):
# we only support mulipolygon relations, skip all other
# a lot of the admin boundary/land_area relations are not type=multipolygon
if tags.get('type') not in ('multipolygon', 'boundary', 'land_area'):
tags.clear()
return
tag_count = len(tags)
_rel_filter(tags)
if len(tags) < tag_count:
# we removed tags...
if not set(tags).difference(expected_tags):
# but no tags except name and type are left
# remove all, otherwise tags from longest
# way/ring would be used during MP building
tags.clear()
return rel_filter
for k, v in {'type': set(['multipolygon', 'boundary', 'land_area'])}.iteritems():
tags.setdefault(k, set()).update(v)
return self._tag_filter(tags)

#tags['type'] = set(['multipolygon', 'boundary', 'land_area']) # for type=multipolygon
#expected_tags = set(['type', 'name'])
#_rel_filter = self._tag_filter(tags)
#def rel_filter(tags):
# # we only support mulipolygon relations, skip all other
# # a lot of the admin boundary/land_area relations are not type=multipolygon
# if tags.get('type') not in ('multipolygon', 'boundary', 'land_area'):
# tags.clear()
# return
# tag_count = len(tags)
# _rel_filter(tags)
# if len(tags) < tag_count:
# # we removed tags...
# if not set(tags).difference(expected_tags):
# # but no tags except name and type are left
# # remove all, otherwise tags from longest
# # way/ring would be used during MP building
# tags.clear()
#return rel_filter

def _mapping_for_tags(self, tag_map, tags):
result = []
Expand Down Expand Up @@ -317,12 +322,13 @@ class Points(Mapping):
class LineStrings(Mapping):
"""
Table class for line string features.

:PostGIS datatype: LINESTRING

:PostGIS datatype: GEOMETRY (LINESTRING does not support multi-linestrings)
//:PostGIS datatype: LINESTRING
"""
table = LineStringTable
geom_builder = imposm.geom.LineStringBuilder()
geom_type = 'LINESTRING'
geom_type = 'GEOMETRY'

class Polygons(Mapping):
"""
Expand Down
121 changes: 67 additions & 54 deletions imposm/multipolygon.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,23 @@ def fetch_ways(self):
def build_rings(self, ways):
rings = []
incomplete_rings = []
linestring_rings = []

for ring in (Ring(w) for w in ways):
if ring.is_closed():
if 'route' in self.relation.tags:
ring.geom = self.linestring_builder.build_geom(ring)
linestring_rings.append(ring)
elif ring.is_closed():
ring.geom = self.polygon_builder.build_checked_geom(ring, validate=self.validate_rings)
rings.append(ring)
else:
incomplete_rings.append(ring)

merged_rings = self.build_ring_from_incomplete(incomplete_rings)
if len(rings) + len(merged_rings) == 0:
if len(rings) + len(merged_rings) + len(linestring_rings) == 0:
raise IncompletePolygonError('linestrings from relation %s have no rings' % (self.relation.osm_id, ))

return rings + merged_rings
return rings + merged_rings + linestring_rings

def build_ring_from_incomplete(self, incomplete_rings):

Expand Down Expand Up @@ -224,59 +228,68 @@ def build_relation_geometry(self, rings):
"""
Build relation geometry from rings.
"""
rings.sort(key=lambda x: x.geom.area, reverse=True)
total_rings = len(rings)

shells = set([rings[0]])

for i in xrange(total_rings):
test_geom = shapely.prepared.prep(rings[i].geom)
for j in xrange(i+1, total_rings):
if test_geom.contains(rings[j].geom):
# j in inside of i
if rings[j].contained_by is not None:
# j is inside a larger ring, remove that relationship
# e.g. j is hole inside a hole (i)
rings[rings[j].contained_by].holes.discard(rings[j])
shells.discard(rings[j])

# remember parent
rings[j].contained_by = i

# add ring as hole or shell
if self._ring_is_hole(rings, j):
rings[i].holes.add(rings[j])
else:
shells.add(rings[j])
if rings[i].contained_by is None:
# add as shell if it is not a hole
shells.add(rings[i])

rel_tags = relation_tags(self.relation.tags, rings[0].tags)

# build polygons from rings
polygons = []
for shell in shells:
shell.mark_as_inserted(rel_tags)
exterior = shell.geom.exterior
interiors = []
for hole in shell.holes:
hole.mark_as_inserted(rel_tags)
interiors.append(hole.geom.exterior)
islines = ('route' in self.relation.tags)
#islines = all(ring.geom.geom_type in ('LineString', 'MultiLineString') for ring in rings)
#import sys
#sys.stderr.write("*** ContainsRelationBuilder.build_relation_geometry: Relation %r, found feature types: %r\n" % ( self.relation.tags, [ring.geom.geom_type for ring in rings] ) )
if islines:
self.relation.geom = shapely.ops.linemerge([ring.geom for ring in rings])
for ring in rings:
ring.mark_as_inserted(self.relation.tags)
else: # not a route relation / other types besides LineString/MultiLineString, eg. LinearRing
rings.sort(key=lambda x: x.geom.area, reverse=True)
total_rings = len(rings)

polygons.append(shapely.geometry.Polygon(exterior, interiors))
shells = set([rings[0]])

if len(polygons) == 1:
geom = polygons[0]
else:
geom = shapely.geometry.MultiPolygon(polygons)

geom = imposm.geom.validate_and_simplify(geom)
if not geom.is_valid:
raise InvalidGeometryError('multipolygon relation (%s) result is invalid' %
self.relation.osm_id)
self.relation.geom = geom
self.relation.tags = rel_tags
for i in xrange(total_rings):
test_geom = shapely.prepared.prep(rings[i].geom)
for j in xrange(i+1, total_rings):
if test_geom.contains(rings[j].geom):
# j in inside of i
if rings[j].contained_by is not None:
# j is inside a larger ring, remove that relationship
# e.g. j is hole inside a hole (i)
rings[rings[j].contained_by].holes.discard(rings[j])
shells.discard(rings[j])

# remember parent
rings[j].contained_by = i

# add ring as hole or shell
if self._ring_is_hole(rings, j):
rings[i].holes.add(rings[j])
else:
shells.add(rings[j])
if rings[i].contained_by is None:
# add as shell if it is not a hole
shells.add(rings[i])

rel_tags = relation_tags(self.relation.tags, rings[0].tags)

# build polygons from rings
polygons = []
for shell in shells:
shell.mark_as_inserted(rel_tags)
exterior = shell.geom.exterior
interiors = []
for hole in shell.holes:
hole.mark_as_inserted(rel_tags)
interiors.append(hole.geom.exterior)

polygons.append(shapely.geometry.Polygon(exterior, interiors))

if len(polygons) == 1:
geom = polygons[0]
else:
geom = shapely.geometry.MultiPolygon(polygons)

geom = imposm.geom.validate_and_simplify(geom)
if not geom.is_valid:
raise InvalidGeometryError('multipolygon relation (%s) result is invalid' %
self.relation.osm_id)
self.relation.geom = geom
self.relation.tags = rel_tags
all_ways = []
for r in rings:
all_ways.extend(r.ways)
Expand Down