From bd01aad8e664d01c0fda4f2eefda7aae959cdd89 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Mon, 3 May 2021 15:59:30 +0100 Subject: [PATCH] WS30 - Scenery features from OSM --- ws30/README.TXT | 8 ++ ws30/gencoastline.py | 122 ++++++++++++++++++++++++++++ ws30/genroads.py | 187 +++++++++++++++++++++++++++++++++++++++++++ ws30/genwater.py | 140 ++++++++++++++++++++++++++++++++ 4 files changed, 457 insertions(+) create mode 100644 ws30/README.TXT create mode 100755 ws30/gencoastline.py create mode 100755 ws30/genroads.py create mode 100755 ws30/genwater.py diff --git a/ws30/README.TXT b/ws30/README.TXT new file mode 100644 index 0000000..2b979ed --- /dev/null +++ b/ws30/README.TXT @@ -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. diff --git a/ws30/gencoastline.py b/ws30/gencoastline.py new file mode 100755 index 0000000..2f40740 --- /dev/null +++ b/ws30/gencoastline.py @@ -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] + " ") + print(" \tScenery directory to write to") + print(" \tBottom left lon/lat of bounding box") + print(" \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) + + diff --git a/ws30/genroads.py b/ws30/genroads.py new file mode 100755 index 0000000..a02dbd7 --- /dev/null +++ b/ws30/genroads.py @@ -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] + " ") + print(" \tScenery directory to write to") + print(" \tBottom left lon/lat of bounding box") + print(" \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 diff --git a/ws30/genwater.py b/ws30/genwater.py new file mode 100755 index 0000000..e1ca100 --- /dev/null +++ b/ws30/genwater.py @@ -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] + " ") + print(" \tScenery directory to write to") + print(" \tBottom left lon/lat of bounding box") + print(" \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) +