1
0
Fork 0

WS30 - Scenery features from OSM

This commit is contained in:
Stuart Buchanan 2021-05-03 15:59:30 +01:00
parent 8be5b2fc52
commit bd01aad8e6
4 changed files with 457 additions and 0 deletions

8
ws30/README.TXT Normal file
View file

@ -0,0 +1,8 @@
A set of trivial python scripts to generate World Scener 3.0 (ws30) scenery
features from openstreetmap data, using the overpass API to retrieve data at
runtime.
These scripts are very simplistic and don't attempt to be particularly efficient
in their use of the API. The should be improved before use on the entire world,
as they will (and do) result in being blocked from the overpass API if used on
and unreasonably large area.

122
ws30/gencoastline.py Executable file
View file

@ -0,0 +1,122 @@
#!/usr/bin/python
# gencoastline.py - create appropriate COASTLINE_LIST STG files for ws30 from openstreetmap
# Copyright (C) 2021 Stuart Buchanan
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# You will need Python Overpass API - "pip install overpy"
import xml.etree.ElementTree as etree
import os
import shutil
import re
import sys
import collections
from math import floor
import calc_tile
import overpy
nodes = {}
road_count = 0
river_count = 0
if (len(sys.argv) != 6):
print("Simple generation of COASTLINE_LIST files")
print("")
print("Usage: " + sys.argv[0] + " <scenery_dir> <lon1> <lat1> <lon2> <lat2>")
print(" <scenery_dir> \tScenery directory to write to")
print(" <lon1> <lat1> \tBottom left lon/lat of bounding box")
print(" <lon2> <lat2> \tTop right lon/lat of bounding box")
exit(1)
scenery_prefix = sys.argv[1]
lon1 = sys.argv[2]
lat1 = sys.argv[3]
lon2 = sys.argv[4]
lat2 = sys.argv[5]
os.makedirs(scenery_prefix, exist_ok=True)
def feature_file(lat, lon):
index = calc_tile.calc_tile_index((lon,lat))
return str(index) + "_Coastline.txt"
def add_to_stg(lat, lon):
index = calc_tile.calc_tile_index((lon, lat))
stg = os.path.join(scenery_prefix, calc_tile.directory_name((lon, lat)), str(index) + ".stg")
#print("Writing " + stg)
with open(stg, 'a') as f:
f.write("COASTLINE_LIST " + feature_file(lat, lon) + "\n")
def write_feature(lon, lat, coast):
index = calc_tile.calc_tile_index((lon,lat))
dirname = os.path.join(scenery_prefix, calc_tile.directory_name((lon, lat)))
os.makedirs(dirname, exist_ok=True)
txt = os.path.join(scenery_prefix, calc_tile.directory_name((lon, lat)), feature_file(lat, lon))
#print("Writing " + txt)
with open(txt, 'a') as f:
for pt in coast :
f.write(" " + str(pt.lon) + " " + str(pt.lat))
f.write("\n")
stg = os.path.join(scenery_prefix, calc_tile.directory_name((lon, lat)), str(index) + ".stg")
if not os.path.isfile(stg) :
# No STG - generate
add_to_stg(lat, lon)
else :
road_exists = 0
with open(stg, 'r') as f:
for line in f:
if line.startswith("COASTLINE_LIST " + feature_file(lat, lon)) :
road_exists = 1
if road_exists == 0 :
add_to_stg(lat, lon)
def parse_way(way) :
global road_count, river_count, lat1, lon1, lat2, lon2
pts = []
width = 6.0
road = 0
river = 0
# It's a road or river. Add it to appropriate tile entries.
tileids = set()
for pt in way.nodes:
lon = float(pt.lon)
lat = float(pt.lat)
idx = calc_tile.calc_tile_index([lon, lat])
if ((float(lon1) <= lon <= float(lon2)) and (float(lat1) <= lat <= float(lat2)) and (idx not in tileids)) :
# Write the feature to a bucket provided it's within the lat/lon bounds and if we've not already written it there
write_feature(lon, lat, way.nodes)
tileids.add(idx)
def writeOSM(result):
for child in result.ways:
parse_way(child)
# Get River data
osm_bbox = ",".join([lat1, lon1, lat2, lon2])
api = overpy.Overpass(url="https://lz4.overpass-api.de/api/interpreter")
coast_query = "way[\"natural\"=\"coastline\"](" + osm_bbox + ");(._;>;);out;"
result = api.query(coast_query)
writeOSM(result)

187
ws30/genroads.py Executable file
View file

@ -0,0 +1,187 @@
#!/usr/bin/python
# genroads.py - create appropriate ROAD_LIST STG files for ws30 from openstreetmap
# Copyright (C) 2021 Stuart Buchanan
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# You will need Python Overpass API - "pip install overpy"
import xml.etree.ElementTree as etree
import os
import shutil
import re
import sys
import collections
from math import floor
import calc_tile
import overpy
nodes = {}
road_count = 0
river_count = 0
if (len(sys.argv) != 6):
print("Simple generation of ROAD_LIST files")
print("")
print("Usage: " + sys.argv[0] + " <scenery_dir> <lon1> <lat1> <lon2> <lat2>")
print(" <scenery_dir> \tScenery directory to write to")
print(" <lon1> <lat1> \tBottom left lon/lat of bounding box")
print(" <lon2> <lat2> \tTop right lon/lat of bounding box")
exit(1)
scenery_prefix = sys.argv[1]
lon1 = sys.argv[2]
lat1 = sys.argv[3]
lon2 = sys.argv[4]
lat2 = sys.argv[5]
os.makedirs(scenery_prefix, exist_ok=True)
def feature_file(lat, lon, type):
index = calc_tile.calc_tile_index((lon,lat))
return str(index) + "_" + type + ".txt"
def add_to_stg(lat, lon, type):
index = calc_tile.calc_tile_index((lon, lat))
stg = os.path.join(scenery_prefix, calc_tile.directory_name((lon, lat)), str(index) + ".stg")
#print("Writing " + stg)
with open(stg, 'a') as f:
f.write("LINE_FEATURE_LIST " + feature_file(lat, lon, type) + " " + type + "\n")
def write_feature(lon, lat, road, type, width):
index = calc_tile.calc_tile_index((lon,lat))
dirname = os.path.join(scenery_prefix, calc_tile.directory_name((lon, lat)))
os.makedirs(dirname, exist_ok=True)
txt = os.path.join(scenery_prefix, calc_tile.directory_name((lon, lat)), feature_file(lat, lon, type))
#print("Writing " + txt)
with open(txt, 'a') as f:
f.write(str(width) + " 0 1 1 1 1") # Width plus currently unused generic attributes.
for pt in road :
f.write(" " + str(pt.lon) + " " + str(pt.lat))
f.write("\n")
stg = os.path.join(scenery_prefix, calc_tile.directory_name((lon, lat)), str(index) + ".stg")
if not os.path.isfile(stg) :
# No STG - generate
add_to_stg(lat, lon, type)
else :
road_exists = 0
with open(stg, 'r') as f:
for line in f:
if line.startswith("LINE_FEATURE_LIST " + feature_file(lat, lon, type)) :
road_exists = 1
if road_exists == 0 :
add_to_stg(lat, lon, type)
def parse_way(way) :
global road_count, river_count, lat1, lon1, lat2, lon2
pts = []
width = 6.0
road = 0
river = 0
highway = way.tags.get("highway")
waterway = way.tags.get("waterway")
feature_type = "None"
if (highway=="motorway_junction") or (highway=="motorway") or (highway=="motorway_link"):
width = 15.0
feature_type = "Road"
road_count = road_count + 1
if (highway=="secondary") or (highway=="primary") or (highway=="trunk") or (highway=="trunk_link") or (highway=="primary_link") or (highway=="secondary_link") :
width = 12.0
feature_type = "Road"
road_count = road_count + 1
if (highway=="unclassified") or (highway=="tertiary") or (highway=="tertiary_link") or (highway=="service") or (highway=="residential"):
width = 6.0
feature_type = "Road"
road_count = road_count + 1
if (waterway=="river") or (waterway=="canal") :
width = 10.0
feature_type = "Watercourse"
river_count = river_count + 1
# Use the width if defined and parseable
if (way.tags.get("width") != None) :
width_str = way.tags.get("width")
try:
if (' m' in width_str) :
width = float(width_str[0:width_str.find(" m")])
if (' ft' in width_str) :
width = 0.3 * float(width_str[0:width_str.find(" ft")])
except ValueError :
print("Unable to parse width " + width_str)
if (feature_type != "None") :
# It's a road or river. Add it to appropriate tile entries.
tileids = set()
for pt in way.nodes:
lon = float(pt.lon)
lat = float(pt.lat)
idx = calc_tile.calc_tile_index([lon, lat])
if ((float(lon1) <= lon <= float(lon2)) and (float(lat1) <= lat <= float(lat2)) and (idx not in tileids)) :
# Write the feature to a bucket provided it's within the lat/lon bounds and if we've not already written it there
write_feature(lon, lat, way.nodes, feature_type, width)
tileids.add(idx)
def writeOSM(result):
for child in result.ways:
parse_way(child)
# Get River data
osm_bbox = ",".join([lat1, lon1, lat2, lon2])
api = overpy.Overpass(url="https://lz4.overpass-api.de/api/interpreter")
river_query = "(way[\"waterway\"=\"river\"](" + osm_bbox + "); way[\"waterway\"=\"canal\"](" + osm_bbox + "););(._;>;);out;"
result = api.query(river_query)
writeOSM(result)
road_query = "("
#road_types = ["unclassified", "tertiary", "service", "secondary", "primary", "motorway_junction", "motorway"]
#road_types = ["tertiary", "secondary", "primary", "motorway_junction", "motorway"]
road_types = ["motorway", "trunk", "primary", "secondary", "tertiary", "unclassified", "residential", "motorway_link", "trunk_link", "primary_link", "secondary_link", "tertiary_link"]
for r in road_types :
road_query = road_query + "way[\"highway\"=\"" + r + "\"](" + osm_bbox + ");"
road_query = road_query + ");(._;>;);out;"
result = api.query(road_query)
writeOSM(result)
print("Wrote total of " + str(road_count) + " roads")
print("Wrote total of " + str(river_count) + " rivers")
#
#
#
#
#time ./genroads.py /tmp/OSMTEST/Terrain/ -3.5 55.0 -2.5 56.0
#2005 time ./genroads.py /tmp/OSMTEST/Terrain/ -12 59.0 -10 61.0
# 2006 time ./genroads.py /tmp/OSMTEST/Terrain/ -10 59.0 -8 61.0
# 2007 time ./genroads.py /tmp/OSMTEST/Terrain/ -8 59.0 -4 61.0
# 2008 time ./genroads.py /tmp/OSMTEST/Terrain/ -4 59.0 0 61.0
# 2009 time ./genroads.py /tmp/OSMTEST/Terrain/ 0 59.0 4 61.0
# 2010 time ./genroads.py /tmp/OSMTEST/Terrain/ -12 57.0 -10 59.0
# 2011 time ./genroads.py /tmp/OSMTEST/Terrain/ -10 57.0 -6 59.0
# 2012 time ./genroads.py /tmp/OSMTEST/Terrain/ -6 57.0 -2 59.0
# 2013 time ./genroads.py /tmp/OSMTEST/Terrain/ -2 57.0 4 59.0

140
ws30/genwater.py Executable file
View file

@ -0,0 +1,140 @@
#!/usr/bin/python
# genwater.py - create appropriate AREA_FEATURE_LIST STG files for ws30 from openstreetmap
# Copyright (C) 2021 Stuart Buchanan
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# You will need Python Overpass API - "pip install overpy"
import xml.etree.ElementTree as etree
import os
import shutil
import re
import sys
import collections
from math import floor
import calc_tile
import overpy
nodes = {}
road_count = 0
river_count = 0
if (len(sys.argv) != 6):
print("Simple generation of AREA_FEATURE_LIST files")
print("")
print("Usage: " + sys.argv[0] + " <scenery_dir> <lon1> <lat1> <lon2> <lat2>")
print(" <scenery_dir> \tScenery directory to write to")
print(" <lon1> <lat1> \tBottom left lon/lat of bounding box")
print(" <lon2> <lat2> \tTop right lon/lat of bounding box")
exit(1)
scenery_prefix = sys.argv[1]
lon1 = sys.argv[2]
lat1 = sys.argv[3]
lon2 = sys.argv[4]
lat2 = sys.argv[5]
os.makedirs(scenery_prefix, exist_ok=True)
def feature_file(lat, lon, type):
index = calc_tile.calc_tile_index((lon,lat))
return str(index) + "_area_" + type + ".txt"
def add_to_stg(lat, lon, type):
index = calc_tile.calc_tile_index((lon, lat))
stg = os.path.join(scenery_prefix, calc_tile.directory_name((lon, lat)), str(index) + ".stg")
#print("Writing " + stg)
with open(stg, 'a') as f:
f.write("AREA_FEATURE_LIST " + feature_file(lat, lon, type) + " " + type + "\n")
def write_feature(lon, lat, pts, type):
index = calc_tile.calc_tile_index((lon,lat))
dirname = os.path.join(scenery_prefix, calc_tile.directory_name((lon, lat)))
os.makedirs(dirname, exist_ok=True)
txt = os.path.join(scenery_prefix, calc_tile.directory_name((lon, lat)), feature_file(lat, lon, type))
#print("Writing " + txt)
with open(txt, 'a') as f:
f.write("10000000 0 1 1 1 1") # Currently unused generic attributes. Also set area to 2000m^2
for pt in pts :
f.write(" " + str(pt.lon) + " " + str(pt.lat))
f.write("\n")
stg = os.path.join(scenery_prefix, calc_tile.directory_name((lon, lat)), str(index) + ".stg")
if not os.path.isfile(stg) :
# No STG - generate
add_to_stg(lat, lon, type)
else :
pts_exists = 0
with open(stg, 'r') as f:
for line in f:
if line.startswith("AREA_FEATURE_LIST " + feature_file(lat, lon, type)) :
pts_exists = 1
if pts_exists == 0 :
add_to_stg(lat, lon, type)
def parse_way(way) :
global road_count, river_count, lat1, lon1, lat2, lon2
pts = []
#feature_type = way.tags.get("water")
feature_type = "Lake"
# Add it to appropriate tile entries.
tileids = set()
for pt in way.nodes:
lon = float(pt.lon)
lat = float(pt.lat)
idx = calc_tile.calc_tile_index([lon, lat])
if ((float(lon1) <= lon <= float(lon2)) and (float(lat1) <= lat <= float(lat2)) and (idx not in tileids)) :
# Write the feature to a bucket provided it's within the lat/lon bounds and if we've not already written it there
write_feature(lon, lat, way.nodes, feature_type)
tileids.add(idx)
def parse_multi(relation):
for way in relation.members:
if (way.tags.get("role") == "outer") :
parse_way(way)
def writeOSM(result):
#for child in result.relations:
#parse_multi(child)
for child in result.ways:
parse_way(child)
# Get Lake data
osm_bbox = ",".join([lat1, lon1, lat2, lon2])
api = overpy.Overpass(url="https://lz4.overpass-api.de/api/interpreter")
query = "("
water_types = ["lake", "basin", "oxbow", "lagoon", "reservoir"]
for r in water_types :
query = query + "way[\"water\"=\"" + r + "\"](" + osm_bbox + ");"
query = query + "way[\"landuse\"=\"" + r + "\"](" + osm_bbox + ");"
query = query + "way[\"natural\"=\"water\"](" + osm_bbox + ");"
for r in water_types :
query = query + "relation[\"water\"=\"" + r + "\"][\"type\"=\"multipolygon\"](" + osm_bbox + ");"
query = query + ");(._;>;);out;"
result = api.query(query)
writeOSM(result)