#!/usr/bin/python
# Software License Agreement (BSD License)
#
# Copyright (C) 2012, Jack O'Quin
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * 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.
# * Neither the name of the author nor of other contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
# COPYRIGHT OWNER 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.
#
# Revision $Id$
"""
Generate geographic information maps based on Open Street Map XML data.
.. _`geographic_msgs/BoundingBox`: http://ros.org/doc/api/geographic_msgs/html/msg/BoundingBox.html
.. _`geographic_msgs/GeographicMap`: http://ros.org/doc/api/geographic_msgs/html/msg/GeographicMap.html
.. _`geographic_msgs/KeyValue`: http://ros.org/doc/api/geographic_msgs/html/msg/KeyValue.html
.. _`geographic_msgs/UniqueID`: http://ros.org/doc/api/geographic_msgs/html/msg/UniqueID.html
"""
from __future__ import print_function
from xml.etree import ElementTree
PKG_NAME = 'osm_cartography'
import roslib; roslib.load_manifest(PKG_NAME)
import unique_id
from geodesy import bounding_box
from geographic_msgs.msg import GeographicMap
from geographic_msgs.msg import KeyValue
from geographic_msgs.msg import MapFeature
from geographic_msgs.msg import WayPoint
try:
from geographic_msgs.msg import UniqueID
except ImportError:
from uuid_msgs.msg import UniqueID
[docs]def get_required_attribute(el, key):
""" Get attribute key of element *el*.
:raises: :exc:`ValueError` if key not found
"""
val = el.get(key)
if val == None:
raise ValueError('required attribute missing: ' + key)
return val
[docs]def makeOsmUniqueID(namespace, el_id):
"""Make UniqueID message for *el_id* number in OSM sub-namespace *namespace*.
:param namespace: OSM sub-namespace
:type namespace: string
:param el_id: OSM identifier within that namespace
:type el_id: int or string containing an integer
:returns: corresponding `geographic_msgs/UniqueID`_ message.
:raises: :exc:`ValueError`
"""
if not namespace in set(['node', 'way', 'relation']):
raise ValueError('invalid OSM namespace: ' + namespace)
ns = 'http://openstreetmap.org/' + namespace + '/'
return unique_id.toMsg(unique_id.fromURL(ns + str(el_id)))
[docs]def get_tag(el):
""" :returns: `geographic_msgs/KeyValue`_ message for `<tag>` *el* if any, None otherwise. """
pair = None
key = el.get('k')
if key != None:
pair = KeyValue()
pair.key = key
pair.value = get_required_attribute(el, 'v')
return pair
[docs]def get_osm(url, bounds):
"""Get `geographic_msgs/GeographicMap`_ from Open Street Map XML data.
The latitude and longitude of the bounding box returned may differ
from the requested bounds.
:param url: Uniform Resource Locator for map.
:param bounds: Desired `geographic_msgs/BoundingBox`_ for map (presently ignored).
:returns: `geographic_msgs/GeographicMap`_ message (header not filled in).
"""
# parse the URL
filename = ''
if url.startswith('file:///'):
filename = url[7:]
elif url.startswith('package://'):
pkg_name, slash, pkg_path = url[10:].partition('/')
pkg_dir = roslib.packages.get_pkg_dir(pkg_name)
filename = pkg_dir + '/' + pkg_path
else:
raise ValueError('unsupported URL: ' + url)
gmap = GeographicMap(id = unique_id.toMsg(unique_id.fromURL(url)))
xm = None
try:
f = open(filename, 'r')
xm = ElementTree.parse(f)
except IOError:
raise ValueError('unable to read ' + str(url))
except ElementTree.ParseError:
raise ValueError('XML parse failed for ' + str(url))
osm = xm.getroot()
# get map bounds
for el in osm.iterfind('bounds'):
minlat = float(get_required_attribute(el, 'minlat'))
minlon = float(get_required_attribute(el, 'minlon'))
maxlat = float(get_required_attribute(el, 'maxlat'))
maxlon = float(get_required_attribute(el, 'maxlon'))
gmap.bounds = bounding_box.makeBounds2D(minlat, minlon, maxlat, maxlon)
# get map way-point nodes
for el in osm.iterfind('node'):
way = WayPoint()
el_id = el.get('id')
if el_id is None:
raise ValueError('node id missing')
way.id = makeOsmUniqueID('node', el_id)
way.position.latitude = float(get_required_attribute(el, 'lat'))
way.position.longitude = float(get_required_attribute(el, 'lon'))
way.position.altitude = float(el.get('ele', float('nan')))
for tag_list in el.iterfind('tag'):
kv = get_tag(tag_list)
if kv != None:
way.props.append(kv)
gmap.points.append(way)
# get map paths
for el in osm.iterfind('way'):
feature = MapFeature()
el_id = el.get('id')
if el_id is None:
raise ValueError('way id missing')
feature.id = makeOsmUniqueID('way', el_id)
for nd in el.iterfind('nd'):
way_id = get_required_attribute(nd, 'ref')
feature.components.append(makeOsmUniqueID('node', way_id))
for tag_list in el.iterfind('tag'):
kv = get_tag(tag_list)
if kv != None:
feature.props.append(kv)
gmap.features.append(feature)
# get relations
for el in osm.iterfind('relation'):
feature = MapFeature()
el_id = el.get('id')
if el_id is None:
raise ValueError('relation id missing')
feature.id = makeOsmUniqueID('relation', el_id)
for mbr in el.iterfind('member'):
mbr_type = get_required_attribute(mbr, 'type')
if mbr_type in set(['node', 'way', 'relation']):
mbr_id = get_required_attribute(mbr, 'ref')
feature.components.append(makeOsmUniqueID(mbr_type, mbr_id))
else:
print('unknown relation member type: ' + mbr_type)
for tag_list in el.iterfind('tag'):
kv = get_tag(tag_list)
if kv != None:
feature.props.append(kv)
gmap.features.append(feature)
return gmap
interesting_tags = set(['access',
'amenity',
'boundary',
'bridge',
'building',
'ele',
'highway',
'landuse',
'lanes',
'layer',
'maxheight',
'maxspeed',
'maxwidth',
'name',
'network',
'oneway',
'railway',
'ref',
'restriction',
'route',
'street',
'tunnel',
'type',
'width'])
ignored_values = set(['bridleway',
'construction',
'cycleway',
'footway',
'path',
'pedestrian',
'proposed',
'steps'])