From bbc924984ad2ee73e34506e035cdd79c804669c6 Mon Sep 17 00:00:00 2001 From: marstr Date: Sat, 7 Sep 2024 20:50:39 +0200 Subject: [PATCH] Removed wedgen in favor of the initial code for a class that generates X-Plane scenery. Class is not yet complete, but is final piece of the toolchain. --- defines.py | 8 +++ layergen.py | 12 ++-- maskgen.py | 17 ++---- orthographic.py | 12 ++-- photogen.py | 2 +- tiledb.py | 9 +-- tilegen.py | 39 ++---------- xp_dsfgen.py | 158 ++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 192 insertions(+), 65 deletions(-) create mode 100644 xp_dsfgen.py diff --git a/defines.py b/defines.py index f2a756c..fc77d12 100644 --- a/defines.py +++ b/defines.py @@ -66,6 +66,14 @@ mstr_shadow_casters = [ ] +# Whether or not to generate X-Plane Scenery files +mstr_xp_genscenery = True + +# X-Plane specific +mstr_xp_dsftool = "M:/Developer/Projects/orthographic/bin/DSFTool.exe" +mstr_xp_ddstool = "M:/Developer/Projects/orthographic/bin/DDSTool.exe" +mstr_xp_folder = "M:/Flight Sim/Simulator/11/" + # How much of a tile we need for each zoom level. The higher # the zoom level, the smaller the area to generate a mask of - but also # higher detail. diff --git a/layergen.py b/layergen.py index d3e157e..fdd9932 100644 --- a/layergen.py +++ b/layergen.py @@ -61,7 +61,7 @@ class mstr_layergen: # other effects. def genborder(self, edgemask, tag, value): layer = Image.new("RGBA", (self._imgsize, self._imgsize)) - root_folder = mstr_datafolder + "Textures/" + tag + "/" + value + root_folder = mstr_datafolder + "textures/" + tag + "/" + value # Determine which sources we use brd = glob.glob(root_folder + "/brd/b*.png") @@ -136,7 +136,7 @@ class mstr_layergen: if (self._isline == False and self._tag != "building") or (self._is_completion == True): # Determine where we get the our source material from - root_folder = mstr_datafolder + "Textures/" + root_folder = mstr_datafolder + "textures/" for s in mstr_ortho_layers: if s[0] == self._tag and s[1] == self._value: fld_main = len(s)-2 @@ -179,7 +179,7 @@ class mstr_layergen: self._tag = al[0][2] self._value = al[0][3] - root_folder = mstr_datafolder + "Textures/" + root_folder = mstr_datafolder + "textures/" for s in mstr_ortho_layers: if s[0] == self._tag and s[1] == self._value: fld_main = len(s)-2 @@ -261,7 +261,7 @@ class mstr_layergen: self._tag = ald[0][2] self._value = ald[0][3] - root_folder = mstr_datafolder + "Textures/" + root_folder = mstr_datafolder + "textures/" for s in mstr_ortho_layers: if s[0] == self._tag and s[1] == self._value: fld_main = len(s)-2 @@ -373,7 +373,7 @@ class mstr_layergen: amt = randrange(1,5) for i in range(1, amt+1): ptc = randrange(1, 14) - img = Image.open(mstr_datafolder + "Textures/tile/completion/p" + str(ptc)+".png") + img = Image.open(mstr_datafolder + "textures/tile/completion/p" + str(ptc)+".png") lx = randrange( int(layer.width/20), layer.width - (int(layer.width/20)) - img.width ) ly = randrange( int(layer.width/20), layer.width - (int(layer.width/20)) - img.height ) layer.alpha_composite( img, (lx, ly) ) @@ -692,7 +692,7 @@ class mstr_layergen: for i in range(1, numtrees+1): # Pick some file pick = str(randrange(1, 11)) - tree = Image.open(mstr_datafolder + "Textures/building/area/p" + pick + ".png") + tree = Image.open(mstr_datafolder + "textures/building/area/p" + pick + ".png") # Do a correction for the location if needed if shf_x < 1: shf_x = 1 if shf_y < 1: shf_y = 1 diff --git a/maskgen.py b/maskgen.py index 4a0d0b4..c315360 100644 --- a/maskgen.py +++ b/maskgen.py @@ -43,9 +43,7 @@ class mstr_maskgen: self._vstep = vstep self._scale = 1 / math.cos(math.radians(self._box[0])) self._isline = isline - self._tiledb = mstr_tiledb(self._box[0], self._box[2]) - if xml != None: - self._xml = xml + #mstr_msg("maskgen", "Intialized mask gen.") @@ -68,6 +66,11 @@ class mstr_maskgen: latlng.append(float(i[2])) break return latlng + + + # Only needed if X-Plane scenery is built + def _set_xpscenery_datagroup(self, dg): + self._xpdg = dg # Builds the required mask @@ -139,9 +142,6 @@ class mstr_maskgen: if len(latlng) == 2: # For some reason, sometimes the array is empty. Make sure we have two data points. if len(latlng) == 2: - # Insert a WED data point should this be a forest: - if self._tag == "landuse" and self._value == "forest": - self._tiledb.insert_wed_datapoint(1, 1, latlng[0], latlng[1]) # Project the pixel, and add to the polygon shape. p_lat = self.project_pixel(latlng[0], bbox[1]) @@ -190,8 +190,3 @@ class mstr_maskgen: mask_img.save(mstr_datafolder + "_cache/" + fstr + "_" + self._tag + "-" + self._value + ".png") # Inform mstr_msg("maskgen", "Mask built.") - - # Close the DB - self._tiledb.close_db() - - diff --git a/orthographic.py b/orthographic.py index a860a01..4beb2bb 100644 --- a/orthographic.py +++ b/orthographic.py @@ -131,6 +131,9 @@ class mstr_orthographic: bb_lat = self._lat bb_lng = self._long + # For X-Plane scenery generation + xp_datagroup = 1 + # Previously, I downloaded all XML files in one go - but to ease the # stress on OSM servers and my server, we will do acquire the data # only for the current processed part of the tile. @@ -141,7 +144,7 @@ class mstr_orthographic: mstr_msg("orthographic", "Adjusted bounding box for XML object") # Determine what to do... maybe work was interrupted - if os.path.isfile(mstr_datafolder + "Tiles/" + str(self._lat) + "_" + str(self._long) + "/Textures/" + str(cur_tile_y) + "_" + str(cur_tile_x) + ".jpg") == False: + if os.path.isfile(mstr_datafolder + "Tiles/" + str(self._lat) + "_" + str(self._long) + "/textures/" + str(cur_tile_y) + "_" + str(cur_tile_x) + ".jpg") == False: # Let the user know mstr_msg("orthographic", "Generating missing orthophoto " + str(cur_tile_y) + "-" + str(cur_tile_x)) @@ -176,6 +179,8 @@ class mstr_orthographic: # Generate the mask mg = mstr_maskgen( [self._lat, cur_tile_y, self._long, cur_tile_x], self._vstep, layer[0], layer[1], layer[2]) + if mstr_xp_genscenery == True: + mg._set_xpscenery_datagroup(xp_datagroup) mg._build_mask() # Generate the layer @@ -191,6 +196,8 @@ class mstr_orthographic: pg = mstr_photogen(self._lat, self._long, cur_tile_y, cur_tile_x, maxlatlng[0], maxlatlng[1]) pg.genphoto() mstr_msg("orthographic", " -- Ortho photo generated -- ") + if mstr_xp_genscenery == True: + xp_datagroup = xp_datagroup + 1 print("") print("") @@ -235,10 +242,7 @@ class mstr_orthographic: tg.genTiles() mstr_msg("orthographic", "Final step completed.") print("") - print("") mstr_msg("orthographic", "Tile data in: " + mstr_datafolder + "/Tiles/" + str(self._lat) + "_" + self._lng) - mstr_msg("orthographic", "Orthos are in the Textures subfolder") - mstr_msg("orthographic", "X-Plane .ter's are in the terrain subfolder") print("") print("") mstr_msg("orthographic", "Thanks for using Orthographic! -- Best, Marcus") diff --git a/photogen.py b/photogen.py index 4610d4d..9c19206 100644 --- a/photogen.py +++ b/photogen.py @@ -104,7 +104,7 @@ class mstr_photogen: self._tile = self._tile.resize((mstr_photores, mstr_photores), Image.Resampling.BILINEAR) # This we can save accordingly. - self._tile.convert('RGB').save(mstr_datafolder + "Tiles/" + str(self._lat) + "_" + str(self._lng) + "/Textures/" + str(self._ty) + "_" + str(self._tx) + ".jpg", format='JPEG', subsampling=0, quality=100) + self._tile.convert('RGB').save(mstr_datafolder + "Tiles/" + str(self._lat) + "_" + str(self._lng) + "/textures/" + str(self._ty) + "_" + str(self._tx) + ".jpg", format='JPEG', subsampling=0, quality=100) # This checks the final image for empty patches. Should one be diff --git a/tiledb.py b/tiledb.py index c933b90..021f59e 100644 --- a/tiledb.py +++ b/tiledb.py @@ -40,8 +40,6 @@ class mstr_tiledb: self._conn.execute("CREATE TABLE IF NOT EXISTS tiledata (tile_v INTEGER, tile_h INTEGER, tag TEXT, value TEXT, source INTEGER, adjacent TEXT);") self._conn.execute("CREATE TABLE IF NOT EXISTS airports (icao TEXT, tile_v INTEGER, tile_h INTEGER, latitude REAL, longitude REAL);") self._conn.execute("CREATE TABLE IF NOT EXISTS completion (tile_v INTEGER, tile_h INTEGER, tag TEXT, value TEXT, source INTEGER, adjacent TEXT);") - self._conn.execute("CREATE TABLE IF NOT EXISTS weddata (datatype INTEGER, datagroup INTEGER, latitude REAL, longitude REAL);") - #mstr_msg("tiledb", "Tables created") # Insert data into their segments @@ -56,12 +54,6 @@ class mstr_tiledb: def insert_icao(self, icao, tv, th, lat, lng): self._conn.execute("INSERT INTO airports VALUES ('"+icao+"', "+str(tv)+", "+str(th)+", "+str(lat)+", "+str(lng)+");") - # Inserts a data point for WED generation - def insert_wed_datapoint(self, dtype, dgroup, lat, lng): - self._conn.execute("INSERT INTO weddata VALUES( "+str(dtype)+","+str(dgroup)+","+str(lat)+","+str(lng)+" );") - self.commit_query() - - # Commit a query or a number of queries def commit_query(self): self._conn.commit() @@ -133,6 +125,7 @@ class mstr_tiledb: return latlng + # Get all tiles with detected airports (ICAO codes) def get_tiles_with_airports(self): r = self._crs.execute("SELECT * FROM airports") diff --git a/tilegen.py b/tilegen.py index d64dc26..92e7040 100644 --- a/tilegen.py +++ b/tilegen.py @@ -68,7 +68,7 @@ class mstr_tilegen: for lt in range(1, steps_lat): for ln in range(1, steps_lng): # Check if we need to do something - if os.path.isfile(mstr_datafolder + "Tiles/" + str(self._lat) + "_" + str(self._lng) + "/Textures/" + str(self._lat) + "-" + str(ln) + "_" + str(self._lng) + "-" + str(lt) + "_OG16.jpg") == False: + if os.path.isfile(mstr_datafolder + "Tiles/" + str(self._lat) + "_" + str(self._lng) + "/textures/" + str(self._lat) + "-" + str(ln) + "_" + str(self._lng) + "-" + str(lt) + "_OG16.jpg") == False: mstr_msg("tilegen", "Generating missing zoom level 16 ortho " + str(self._lat) + "-" + str(ln) + "_" + str(self._lng) + "-" + str(lt) + "_OG16.jpg") @@ -85,7 +85,7 @@ class mstr_tilegen: for j in range(0, 3): # We may run into situations where ask for tiles that don't exist... # Let's make sure we can continue - fpath = mstr_datafolder + "Tiles/" + str(self._lat) + "_" + str(self._lng) + "/Textures/" + str(tiles[i][j][0]) + "_" + str(tiles[i][j][1]) + ".jpg" + fpath = mstr_datafolder + "Tiles/" + str(self._lat) + "_" + str(self._lng) + "/textures/" + str(tiles[i][j][0]) + "_" + str(tiles[i][j][1]) + ".jpg" if os.path.isfile( fpath ): tlimg = Image.open(fpath) tlimg = tlimg.resize((scaled_res,scaled_res), Image.Resampling.BILINEAR) @@ -95,19 +95,7 @@ class mstr_tilegen: ypos = ypos - scaled_res # Now save this image - zl16.save(mstr_datafolder + "Tiles/" + str(self._lat) + "_" + str(self._lng) + "/Textures/" + str(self._lat) + "-" + str(ln) + "_" + str(self._lng) + "-" + str(lt) + "_OG16.jpg", format='JPEG', subsampling=0, quality=100) - - # Store a terrain file - dmt = self._findWidthOfLongitude(a_lat) * mstr_zl_16 - ter_content = """A -800 -TERRAIN - -LOAD_CENTER """ + str(a_lat) + " " + str(a_lng) + " " + str(dmt) + " " + "../Textures/" + str(self._lat) + "-" + str(ln) + "_" + str(self._lng) + "-" + str(lt) + "_OG_16.jpg"+""" -NO_ALPHA""" - with open(mstr_datafolder + "/Tiles/"+str(self._lat)+"_"+str(self._lng)+"/terrain/"+str(self._lat)+"_"+str(lt)+"-"+str(self._lng)+"-"+str(ln)+"_OG16.ter", 'w') as textfile: - textfile.write(ter_content) - mstr_msg("tilegen", "Wrote .ter file") + zl16.save(mstr_datafolder + "Tiles/" + str(self._lat) + "_" + str(self._lng) + "/textures/" + str(self._lat) + "-" + str(ln) + "_" + str(self._lng) + "-" + str(lt) + "_OG16.jpg", format='JPEG', subsampling=0, quality=100) # Adjust a_lng = a_lng + (mstr_zl_16 * 4) @@ -156,28 +144,9 @@ NO_ALPHA""" found = True break if found == False: - os.remove(mstr_datafolder + "/Tiles/" + str(self._lat) + "_" + str(self._lng) + "/Textures/" + fn) + os.remove(mstr_datafolder + "/Tiles/" + str(self._lat) + "_" + str(self._lng) + "/textures/" + fn) mstr_msg("tilegen", "Cleanup completed") - - # And now for the final act of tonight's entertainment - mstr_msg("tilegen", "Writing .ter files for ZL18 tiles") - - for k in keeping: - k_lat = self._lat + (k[0] * self._vstep) + (self._vstep * 0.5) - k_lng = self._lat + (k[1] * mstr_zl_18) + (mstr_zl_18 * 0.5) - k_dmt = self._findWidthOfLongitude(self._lng * mstr_zl_18) - k_fln = mstr_datafolder + "/Tiles/" + str(self._lat) + "_" + str(self._lng) + "/terrain/" + str(k[0]) + "_" + str(k[1]) + ".ter" - ter_content = """A -800 -TERRAIN - -LOAD_CENTER """ + str(k_lat) + " " + str(k_lng) + " " + str(k_dmt) + " " + "../Textures/" + str(k[0]) + "_" + str(k[1]) + ".jpg"+""" -NO_ALPHA""" - with open(k_fln, 'w') as textfile: - textfile.write(ter_content) - mstr_msg("tilegen", "Wrote all .ter files for ZL18 tiles.") - mstr_msg("tilegen", "Work complete.") diff --git a/xp_dsfgen.py b/xp_dsfgen.py new file mode 100644 index 0000000..e4ccfd8 --- /dev/null +++ b/xp_dsfgen.py @@ -0,0 +1,158 @@ + +# ------------------------------------------------------------------- +# ORTHOGRAPHIC +# Your personal aerial satellite. Always on. At any altitude.* +# Developed by MarStrMind +# License: Open Software License 3.0 +# Up to date version always on marstr.online +# ------------------------------------------------------------------- +# xp_dsfgen.py +# This class is coming into play at the very end of the tile +# generation process, and builds the DSF (Distributable Scenery +# Format) file for X-Plane. +# +# For this, you will need two tools which I cannot re-distribute: +# - DSFTool +# - DDSTool +# +# You can download both of these for free from X-Plane's website. +# Place them somewhere convenient, and point to them in the +# xp_ variables in defines.py +# ------------------------------------------------------------------- + +import os +import glob +import math +from random import randrange +from log import * +from tiledb import * + +class mstr_xp_dsfgen: + # Instantiate with Lat/Lng, as usual + def __init__(self, lat, lng, amtdg): + self._latitude = lat + self._longitude = lng + self._tiledb = mstr_tiledb(lat, lng) + self._tmpdsf = mstr_datafolder + "_cache/tiledsf.txt" + self._dsfstring = "" + self._amtdg = amtdg + mstr_msg("xp_dsfgen", "DSFgen initialized") + self.build_header() + + + # Construct header of DSF txt + def build_header(self): + self._dsfstring = self._dsfstring + "PROPERTY sim/west " + str(int(self._longitude)) + "\n" + self._dsfstring = self._dsfstring + "PROPERTY sim/east " + str(int(self._longitude + 1)) + "\n" + self._dsfstring = self._dsfstring + "PROPERTY sim/south " + str(int(self._latitude)) + "\n" + self._dsfstring = self._dsfstring + "PROPERTY sim/north " + str(int(self._latitude+1)) + "\n" + self._dsfstring = self._dsfstring + "PROPERTY sim/planet earth\n" + self._dsfstring = self._dsfstring + "PROPERTY sim/creation_agent Orthographic\n" + mstr_msg("xp_dsfgen", "Header built") + + + # Let's now walk through the single polygon definitions + def build_polygon_defs(self): + mstr_msg("xp_dsfgen", "Walking through forest polygons") + + # Pick the kind of forest we work with + for f in range(1, self._amtdg+1): + #rws = self._tiledb.perform_query("SELECT * FROM xp_scenery WHERE datagroup="+str(f)+";") + frs = self.pick_forest_type() + self._dsfstring = self._dsfstring + "POLYGON_DEF lib/g8/"+frs+"\n" + + # Put in the data + curpoly=0 + for f in range(1, self._amtdg+1): + rws = self._tiledb.perform_query("SELECT * FROM xpscenery WHERE datagroup="+str(f)+";") + self._dsfstring = self._dsfstring + "BEGIN_POLYGON "+str(curpoly)+" 255 2\n" + self._dsfstring = self._dsfstring + "BEGIN_WINDING\n" + for r in rws: + self._dsfstring = self._dsfstring + "POLYGON_POINT " + str(r[2]) + " " + str(r[1]) + "\n" + self._dsfstring = self._dsfstring + "END_WINDING\n" + self._dsfstring = self._dsfstring + "END_POLYGON\n" + curpoly = curpoly + 1 + + mstr_msg("xp_dsfgen", "Forest definitions complete") + + + # Write the text file + def write_dsf_txt(self): + with open(mstr_datafolder + "_cache/dsf.txt", 'w') as textfile: + textfile.write(self._dsfstring) + + + # Convert the DSF into actual, usable data for X-Plane + def convert_dsf_text(self): + # Find separator + sep = "" + if os.name == "nt": + sep = "\\" + if os.name == "posix": + sep = "/" + + datafolder = mstr_datafolder.replace("/", sep) + + # First, create the Earth nav data folder should it not exist + end_base = datafolder + "Tiles" + sep + str(self._latitude) + "_" + str(self._longitude) + sep + "Earth nav data" + + # Create the appropriate rounded folder + end_round = self.xplane_latlng_folder(self.find_earthnavdata_number()) + if not os.path.exists(end_base): + os.makedirs(end_base) + if not os.path.exists(end_base + sep + end_round): + os.makedirs(end_base + sep + end_round) + + # Get the file name for the DSF + end_latlng = self.xplane_latlng_folder([self._latitude, self._longitude]) + + # Perform conversion + os.system(mstr_xp_dsftool + " --text2dsf " + datafolder + "_cache" + sep + "dsf.txt \"" + end_base + sep + end_round + sep + end_latlng + ".dsf\"") + + + # Find the next "by-ten" numbers for the current latitude and longitude + def find_earthnavdata_number(self): + earthnavdata = [] + lat = abs(int(self._latitude / 10) * 10) + lng = abs(int(self._longitude / 10) * 10) + earthnavdata.append(lat) + earthnavdata.append(lng) + return earthnavdata + + + # Construct an X-Plane compatible folder name for latitude and longitude + def xplane_latlng_folder(self, numbers): + fstr = "" + if numbers[0] >= 0: fstr = "+" + if numbers[0] < 0: fstr = "-" + if abs(numbers[0]) < 10: fstr = fstr + "0" + str(numbers[0]) + if abs(numbers[0]) >= 10 and numbers[0] <= 90: fstr = fstr + str(numbers[0]) + + if numbers[1] >= 0: fstr = fstr + "+" + if numbers[1] < 0: fstr = fstr + "-" + if abs(numbers[1]) < 10: fstr = fstr + "00" + str(numbers[1]) + if abs(numbers[1]) >= 10 and numbers[0] <= 99: fstr = fstr + "0" + str(numbers[1]) + if abs(numbers[1]) >= 100 : fstr = fstr + str(numbers[1]) + + return fstr + + + # Pick some forest type from X-Plane + def pick_forest_type(self): + ftype = 0 + + # Where forests live in X-Plane. + rootfolder = mstr_xp_folder + "Resources/default scenery/1000 forests/" + forests = glob.glob(rootfolder + "mixed_*.for") + ftype = randrange(1, len(forests)-1) + fstring = forests[ftype] + fstring = fstring.replace(mstr_xp_folder + "Resources/default scenery/1000 forests\\", "") + + return fstring + + + +dsf = mstr_xp_dsfgen(51, 7, 1) +dsf.build_polygon_defs() +dsf.write_dsf_txt() +dsf.convert_dsf_text() \ No newline at end of file -- 2.30.2