问题
做项目的时候发现osm自定义区域导出后,还是会有延伸到区域外的数据,且会影响其他软件导入osm后的锚点,如下图,黑色背景的区域全是多余的数据
推测官网导出的规则为:只要某条道路的其中一部分存在于我们选择的区域内,osm就会导出该条道路包含的全部节点。
这个问题似乎没办法避免,于是想到了用python手动处理一下osm数据,将多余节点删除。
分析
首先进行分析,我们先观察一下osm的数据格式
发现osm数据采用的也是xml格式,所以我们可以使用ElementTree API来处理
我们还能看到,其中第3行bounds正是我们所设置的导出区域范围,而其他node也有它们自己所在的经纬度信息
所以方法很简单:我们先获取到bounds的经纬信息,再用它来遍历所有node做对比,删除经纬度不在范围内的行即可。
注意在所有node列出后,还有一些way块。经过对比可发现,其中子节点nd的ref属性正对应前面node的id属性,说明从这里开始的数据是在引用前面的节点来构成道路。所以我们删了某个node后同样需要到后面把它的引用也删掉,否则后续导入就会报错!
分析完毕,下面直接上代码。
代码
#author:dizzyding
#date:2020/9/16
#verison:python3.8
import xml.etree.ElementTree as xee
def osmProcess():
# 读取文件
domTree = xee.parse("osm.osm")
# 获得所有节点的内容
root = domTree.getroot()
# 获得所选区域的经纬度范围
bound = root.findall("bounds")
maxLat = float(bound[0].get("maxlat"))
maxLon = float(bound[0].get("maxlon"))
minLat = float(bound[0].get("minlat"))
minLon = float(bound[0].get("minlon"))
# 输出所选区域的经纬度范围
print('Bounds:' + '\n' + 'minLat: ' + str(minLat) + '\n' + 'maxLat: ' + str(maxLat) + '\n' +
'minLon: ' + str(minLon) + '\n' + 'maxLon: ' + str(maxLon) + '\n' +'Nodes: ')
# 存储不在所选区域内的node ID
IDlist = []
# 逐个检查node
nodes = root.findall("node")
for node in nodes:
# 当前节点的经纬度和ID
Lat = float(node.get("lat"))
Lon = float(node.get("lon"))
ID = node.get("id")
#输出node信息
print('nodeID:' + ID + ', Lat:' + str(Lat) + ', Lon:' + str(Lon) + ', Bound: ' , end='')
# 判断
if Lat < minLat or Lat > maxLat or Lon < minLon or Lon > maxLon:
root.remove(node)
IDlist.append(ID)
#输出bound比对情况,若Lat和Lon均不符合则只输出Lat
if Lat < minLat:
print('Lat < min')
elif Lat > maxLat:
print('Lat > max')
elif Lon < minLon:
print('Lon < min')
elif Lon > maxLon:
print('Lon > max')
else:
print('Satisfied')
# 删除不在所选区域内的node在后续道路的参照行
ways = root.findall("way")
for ID in IDlist:
for way in ways:
refnodes = way.findall("nd")
for node in refnodes:
if node.get("ref") == ID:
way.remove(node)
# 输出文件
domTree.write("out.osm",encoding="utf8")
if __name__=="__main__":
osmProcess()
效果
运行完后效果是这样的,超出的节点全被干掉了
丢进Houdini里也能直接居中对齐了,不用再手动调参数