# Whether or not to generate X-Plane Scenery files\r
mstr_xp_genscenery = True\r
\r
-# X-Plane specific\r
-mstr_xp_dsftool = "M:/Developer/Projects/orthographic/bin/DSFTool.exe"\r
-mstr_xp_folder = "M:/Flight Sim/Simulator/11/"\r
-# Whether or not you want normal maps for certain geographical elements\r
-# to make them appear more realistic.\r
-mstr_xp_generate_normal_maps = True\r
+# Generate normal maps for X-Plane scenery?\r
+# Strong recommendation: yes\r
+mstr_xp_scn_normalmaps = True\r
\r
-# If you set the above to true, you can\r
-# 1) define for which features to generate normal maps for, and\r
-# 2) the specularity for each feature.\r
-# A specularity value of 100 is fully specular, useful for water\r
-# A specularity value of 10 is more useful for inland features.\r
-# Sand may also reflect some of its surface, so possibly 25 is good there.\r
+# Paths to required X-Plane scenery tools\r
+mstr_xp_meshtool = "/home/marcus/Developer/Projects/orthographic/bin/MeshTool"\r
+mstr_xp_ddstool = "/home/marcus/Developer/Projects/orthographic/bin/DDSTool"\r
+mstr_xp_xessrc = "https://dev.x-plane.com/update/misc/MeshTool/"\r
+\r
+# If you set the above to true, you can define for which features you\r
+# want to generate normal maps for. The below is my recommendation for\r
+# good-looking orthos in the simulator.\r
mstr_xp_normal_maps = [\r
- ("landuse", "farmland", 10),\r
- ("landuse", "forest", 10),\r
- ("leisure", "nature_reserve", 10),\r
- ("natural", "water", 100),\r
- ("water", "pond", 100),\r
- ("water", "river", 100),\r
- ("water", "lake", 100)\r
+ ("landuse", "farmland"),\r
+ ("landuse", "meadow"),\r
+ ("landuse", "orchard"),\r
+ ("landuse", "forest"),\r
+ ("natural", "wetland"),\r
+ ("natural", "bare_rock"),\r
+ ("natural", "scrub"),\r
+ ("natural", "heath"),\r
+ ("natural", "sand"),\r
+ ("natural", "desert"),\r
+ ("leisure", "nature_reserve"),\r
+ ("building", "*")\r
]\r
\r
# How much of a tile we need for each zoom level. The higher\r
# Depending on if scenery for XP should be made, AND if normal maps should be made, we would\r
# need to make them at this exact point\r
if mstr_xp_genscenery == True:\r
- if mstr_xp_generate_normal_maps == True:\r
+ if mstr_xp_scn_normalmaps == True:\r
nm = False\r
for n in mstr_xp_normal_maps:\r
- if n[0] == self._tag and n[1] == self._value:\r
+ if n[0] == self._tag and (n[1] == self._value or n[1] == "*"):\r
nm = True\r
break\r
if nm == True:\r
\r
self._tiledb.commit_query()\r
self._tiledb.close_db()\r
+\r
+ # Create a water mask we need to remove from the DDS later\r
+ if (self._tag == "natural" and self._value == "water") or (self._tag == "water" and self._value == "lake") or (self._tag == "water" and self._value == "pond") or (self._tag == "water" and self._value == "river"):\r
+ mstr_msg("layergen", "Generating inland water mask")\r
+ inl_mask = Image.new("RGBA", (self._imgsize, self._imgsize), (255,255,255,255))\r
+ lyr_pix = layer_comp.load()\r
+ inl_pix = inl_mask.load()\r
+ for y in range(self._imgsize):\r
+ for x in range(self._imgsize):\r
+ l = lyr_pix[x,y]\r
+ if l[3] > 0:\r
+ b = 255 - l[3]\r
+ inl_pix[x,y] = (b,b,b,255)\r
+ inl_mask.save(mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer_mask.png")\r
+ mstr_msg("layergen", "Inland water mask generated and saved")\r
\r
\r
+ # ---------------------------------------------------------------------------------------\r
+ # ---------------------------------------------------------------------------------------\r
+ # ---------------------------------------------------------------------------------------\r
+\r
# If we encounter one of these road-specific tags, we need to proceed differently.\r
\r
if self._isline == True or self._tag == "building":\r
layer_comp_pix[x, y] = ( r,g,b,a[3] )\r
\r
# We will do some super magic here to let houses look more realistic\r
- if self._tag == "building":\r
+ if self._tag == "building" or self._value == "cemetery":\r
vls = [ "detached", "hotel", "farm", "semidetached_house", "apartments", "civic", "office", "retail", "industrial", "house", "school", "yes" ]\r
if self._value in vls:\r
# Generate a new image\r
# Depending on if scenery for XP should be made, AND if normal maps should be made, we would\r
# need to make them at this exact point\r
if mstr_xp_genscenery == True:\r
- if mstr_xp_generate_normal_maps == True:\r
+ if mstr_xp_scn_normalmaps == True:\r
nm = False\r
for n in mstr_xp_normal_maps:\r
- if n[0] == self._tag and n[1] == self._value:\r
+ if n[0] == self._tag and (n[1] == self._value or n[1] == "*"):\r
nm = True\r
break\r
if nm == True:\r
\r
-import sys\r
-import os\r
-from orthographic import *\r
-from log import *\r
-from defines import *\r
-\r
# -------------------------------------------------------------------\r
# ORTHOGRAPHIC\r
# Your personal aerial satellite. Always on. At any altitude.*\r
# -------------------------------------------------------------------\r
\r
\r
+import sys\r
+import os\r
+from orthographic import *\r
+from log import *\r
+from defines import *\r
+\r
+\r
# Print a welcome message\r
print(" ")\r
print(" ---------------------------------------------------------------- ")\r
import glob\r
from defines import *\r
from log import *\r
+from osmxml import *\r
from maskgen import *\r
from layergen import *\r
from photogen import *\r
-from osmxml import *\r
-from tilegen import *\r
-from xp_dsfgen import *\r
+from xp_scenery import *\r
\r
\r
# The main class which handles the rest\r
os.makedirs(self._output + "/_cache")\r
mstr_msg("orthographic", "Created _cache folder.")\r
\r
- # Generate the Tiles folder for the finished products\r
- if not os.path.exists(self._output + "/Tiles"):\r
- os.makedirs(self._output + "/Tiles")\r
- mstr_msg("orthographic", "Created Tiles folder.")\r
- \r
# Generate the Tiles/lat-lng folder for the finished tile\r
- if not os.path.exists(self._output + "/Tiles/z_orthographic_" + self._latlngfld):\r
- os.makedirs(self._output + "/Tiles/z_orthographic_"+ self._latlngfld)\r
- mstr_msg("orthographic", "Created Tiles sub folder: " + self._latlngfld)\r
+ if not os.path.exists(self._output + "/z_orthographic"):\r
+ os.makedirs(self._output + "/z_orthographic")\r
+ mstr_msg("orthographic", "Created z_orthographic folder")\r
\r
# Generate the orthos folder\r
- if not os.path.exists(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/orthos"):\r
- os.makedirs(self._output + "/Tiles/z_orthographic_" + self._latlngfld +"/orthos")\r
+ if not os.path.exists(self._output + "/z_orthographic/orthos"):\r
+ os.makedirs(self._output + "/z_orthographic/orthos")\r
mstr_msg("orthographic", "Created tile orthos folder")\r
+ if not os.path.exists(self._output + "/z_orthographic/orthos" + self._latlngfld):\r
+ os.makedirs(self._output + "/z_orthographic/orthos/" + self._latlngfld)\r
\r
+ # Generate the database folder\r
+ if not os.path.exists(self._output + "/z_orthographic/data"):\r
+ os.makedirs(self._output + "/z_orthographic/data")\r
+ mstr_msg("orthographic", "Created tile database folder")\r
+ if not os.path.exists(self._output + "/z_orthographic/data/" + self._latlngfld):\r
+ os.makedirs(self._output + "/z_orthographic/data/" + self._latlngfld)\r
+\r
+ # X-Plane specific\r
if mstr_xp_genscenery == True:\r
- if not os.path.exists(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/terrain"):\r
- os.makedirs(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/terrain")\r
- mstr_msg("orthographic", "Created X-Plane tile terrain folder")\r
-\r
- if mstr_xp_generate_normal_maps == True:\r
- if not os.path.exists(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/normals"):\r
- os.makedirs(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/normals")\r
- mstr_msg("orthographic", "Created X-Plane tile normals folder")\r
+ btnum = self.find_earthnavdata_number()\r
+ btstr = self.latlng_folder(btnum)\r
+ if not os.path.exists(self._output + "/z_orthographic/terrain"):\r
+ os.makedirs(self._output + "/z_orthographic/terrain")\r
+ mstr_msg("orthographic", "[X-Plane] Created terrain files folder")\r
+ if not os.path.exists(self._output + "/z_orthographic/terrain/" + self._latlngfld):\r
+ os.makedirs(self._output + "/z_orthographic/terrain/" + self._latlngfld)\r
+ if not os.path.exists(self._output + "/z_orthographic/Earth nav data"):\r
+ os.makedirs(self._output + "/z_orthographic/Earth nav data")\r
+ mstr_msg("orthographic", "[X-Plane] Created Earth nav folder")\r
+ if not os.path.exists(self._output + "/z_orthographic/Earth nav data/" + btstr):\r
+ os.makedirs(self._output + "/z_orthographic/Earth nav data/" + btstr)\r
+ if mstr_xp_scn_normalmaps == True:\r
+ if not os.path.exists(self._output + "/z_orthographic/normals"):\r
+ os.makedirs(self._output + "/z_orthographic/normals")\r
+ mstr_msg("orthographic", "[X-Plane] created tile normal maps folder")\r
+ if not os.path.exists(self._output + "/z_orthographic/normals/" + self._latlngfld):\r
+ os.makedirs(self._output + "/z_orthographic/normals/" + self._latlngfld)\r
\r
# The tile is constructed of many smaller parts. We walk through the\r
# smallest possible, from which the bigger ones are later built.\r
mstr_msg("orthographic", "Adjusted bounding box for XML object")\r
\r
# Determine what to do... maybe work was interrupted\r
- if os.path.isfile(mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(cur_tile_y) + "_" + str(cur_tile_x) + ".dds") == False:\r
+ if os.path.isfile(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(cur_tile_y) + "_" + str(cur_tile_x) + ".dds") == False:\r
\r
# Let the user know\r
mstr_msg("orthographic", "Generating missing orthophoto " + str(cur_tile_y) + "-" + str(cur_tile_x))\r
\r
# Complete scenery\r
if mstr_xp_genscenery == True:\r
- dsf = mstr_xp_dsfgen(self._lat, self._long, mlat, mlng, self._vstep)\r
- dsf.build_dsf_for_tile()\r
- mstr_msg("orthographic", "X-Plane scenery completed")\r
+ scn = mstr_xp_scenery(self._lat, self._long, mlat, mlng, self._vstep, self._latlngfld)\r
+ scn.acquire_elevation_data()\r
+ scn.acquire_xes_data()\r
+ scn.build_mesh_script()\r
+ scn.build_mesh()\r
+ mstr_msg("orthographic", "[X-Plane] Mesh built, and scenery completed")\r
\r
mstr_msg("orthographic", "Final step completed.")\r
- mstr_msg("orthographic", "Tile data in: " + self._output + "/Tiles/z_orthographic_" + self._latlngfld)\r
+ mstr_msg("orthographic", "Tile data in: " + self._output + "z_orthographic/" + self._latlngfld)\r
print("")\r
mstr_msg("orthographic", "Thanks for using Orthographic! -- Best, Marcus")\r
print("")\r
\r
return fstr\r
\r
+\r
+ # Find the next "by-ten" numbers for the current latitude and longitude\r
+ def find_earthnavdata_number(self):\r
+ earthnavdata = []\r
+ lat = abs(int(self._lat / 10) * 10)\r
+ lng = abs(int(self._long / 10) * 10)\r
+ earthnavdata.append(lat)\r
+ earthnavdata.append(lng)\r
+ return earthnavdata
\ No newline at end of file
from defines import *\r
from layergen import *\r
from log import *\r
-from wand import image\r
\r
# -------------------------------------------------------------------\r
# ORTHOGRAPHIC\r
if p[0] == 255 and p[1] == 0 and p[2] == 255:\r
t = (0,0,0,0)\r
ocean_pix[x,y] = t\r
+\r
+ # Now cut out inland water\r
+ water_layers = (\r
+ ["natural", "water"],\r
+ ["water", "lake"],\r
+ ["water", "pond"],\r
+ ["water", "river"]\r
+ )\r
+ for l in water_layers:\r
+ fn = mstr_datafolder + "_cache/" + str(self._lat) + "-" + str(self._ty) + "_" + str(self._lng) + "-" + str(self._tx) + "_" + l[0] + "-" + l[1] + "_layer_mask.png"\r
+ if os.path.isfile(fn) == True:\r
+ wtr = Image.open(fn)\r
+ wtr_pix = wtr.load()\r
+ tilepix = self._tile.load()\r
+ for y in range(wtr.height):\r
+ for x in range(wtr.width):\r
+ wp = wtr_pix[x,y]\r
+ if wp[0] == 0:\r
+ tilepix[x,y] = (0,0,0,0)\r
\r
# We are now in posession of the final image.\r
\r
self._tile = self._tile.resize((mstr_photores, mstr_photores), Image.Resampling.BILINEAR)\r
\r
# This we can save accordingly.\r
- self._tile.convert('RGB').save(mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".png")\r
- \r
+ self._tile.save(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx) + ".png")\r
\r
# Now we convert this into a DDS\r
- with image.Image(filename=mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".png") as img:\r
- img.compression = "dxt1"\r
- img.save(filename=mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".dds")\r
- # TODO: CUT OUT OCEANS AND SEAS IN DDS\r
- os.remove(mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".png")\r
+ _tmpfn = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx)\r
+ os.system(mstr_xp_ddstool + " --png2dxt1 " + _tmpfn + ".png " + _tmpfn + ".dds" )\r
+ \r
+ os.remove(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx) + ".png")\r
\r
\r
\r
self._latlngfld = latlngfld\r
\r
# The db file will be created, should it not exist\r
- self._conn = sqlite3.connect(mstr_datafolder + "Tiles/z_orthographic_" + latlngfld + "/data.db")\r
+ self._conn = sqlite3.connect(mstr_datafolder + "z_orthographic/data/" + latlngfld + "/data.db")\r
self._crs = self._conn.cursor()\r
#mstr_msg("tiledb", "Database object initiated")\r
\r
+++ /dev/null
-\r
-# -------------------------------------------------------------------\r
-# ORTHOGRAPHIC\r
-# Your personal aerial satellite. Always on. At any altitude.*\r
-# Developed by MarStrMind\r
-# License: Open Software License 3.0\r
-# Up to date version always on marstr.online\r
-# -------------------------------------------------------------------\r
-# xp_dsfgen.py\r
-# This class is coming into play at the very end of the tile\r
-# generation process, and builds the DSF (Distributable Scenery\r
-# Format) file for X-Plane.\r
-#\r
-# For this, you will need DSFTool which I cannot re-distribute.\r
-#\r
-# You can download it for free from X-Plane's website.\r
-# Place them somewhere convenient, and point to them in the\r
-# xp_ variables in defines.py\r
-# -------------------------------------------------------------------\r
-\r
-import os\r
-import glob\r
-import math\r
-from random import randrange\r
-from log import *\r
-\r
-class mstr_xp_dsfgen:\r
- # Instantiate with Lat/Lng, as usual\r
- def __init__(self, lat, lng, mlat, mlng, vstep):\r
- self._latitude = lat\r
- self._longitude = lng\r
- self._maxlat = mlat\r
- self._maxlng = mlng\r
- self._tmpdsf = mstr_datafolder + "_cache/tiledsf.txt"\r
- self._vstep = vstep\r
- self._dsfstring = ""\r
- mstr_msg("xp_dsfgen", "[X-Plane] DSFgen initialized")\r
- self.build_header()\r
-\r
- \r
- # Construct header of DSF txt\r
- def build_header(self):\r
- self._dsfstring = self._dsfstring + "PROPERTY sim/west " + str(int(self._longitude)) + "\n"\r
- self._dsfstring = self._dsfstring + "PROPERTY sim/east " + str(int(self._longitude + 1)) + "\n"\r
- self._dsfstring = self._dsfstring + "PROPERTY sim/south " + str(int(self._latitude)) + "\n"\r
- self._dsfstring = self._dsfstring + "PROPERTY sim/north " + str(int(self._latitude+1)) + "\n"\r
- self._dsfstring = self._dsfstring + "PROPERTY sim/planet earth\n"\r
- self._dsfstring = self._dsfstring + "PROPERTY sim/creation_agent Orthographic\n"\r
- self._dsfstring = self._dsfstring + "PROPERTY sim/overlay 1\n" # <- DO NOT REMOVE THIS!!\r
- self._dsfstring = self._dsfstring + "PROPERTY sim/require_facade 6/0\n"\r
- mstr_msg("xp_dsfgen", "[X-Plane] DSF header built")\r
-\r
-\r
- # Write the text file\r
- def write_dsf_txt(self):\r
- mstr_msg("xp_dsfgen", "[X-Plane] Writing DSF txt file")\r
- with open(self._tmpdsf, 'w') as textfile:\r
- textfile.write(self._dsfstring) \r
-\r
-\r
- # Convert the DSF into actual, usable data for X-Plane\r
- def convert_dsf_text(self):\r
- mstr_msg("xp_dsfgen", "[X-Plane] Converting DSF information into X-Plane DSF file")\r
- # Find separator\r
- sep = ""\r
- if os.name == "nt":\r
- sep = "\\"\r
- if os.name == "posix":\r
- sep = "/"\r
- \r
- datafolder = mstr_datafolder.replace("/", sep)\r
-\r
- # First, create the Earth nav data folder should it not exist\r
- end_base = datafolder + "Tiles/z_orthographic_" + self.xplane_latlng_folder([self._latitude, self._longitude]) + sep + "Earth nav data"\r
-\r
- # Create the appropriate rounded folder\r
- end_round = self.xplane_latlng_folder(self.find_earthnavdata_number())\r
- if not os.path.exists(end_base):\r
- os.makedirs(end_base)\r
- if not os.path.exists(end_base + sep + end_round):\r
- os.makedirs(end_base + sep + end_round)\r
-\r
- # Get the file name for the DSF\r
- end_latlng = self.xplane_latlng_folder([self._latitude, self._longitude])\r
-\r
- # Perform conversion\r
- os.system(mstr_xp_dsftool + " --text2dsf " + datafolder + "_cache" + sep + "tiledsf.txt \"" + end_base + sep + end_round + sep + end_latlng + ".dsf\"")\r
- mstr_msg("xp_dsfgen", "[X-Plane] DSF conversion complete")\r
-\r
-\r
- # Find the next "by-ten" numbers for the current latitude and longitude\r
- def find_earthnavdata_number(self):\r
- earthnavdata = []\r
- lat = abs(int(self._latitude / 10) * 10)\r
- lng = abs(int(self._longitude / 10) * 10)\r
- earthnavdata.append(lat)\r
- earthnavdata.append(lng)\r
- return earthnavdata\r
- \r
- # Find with of a longitude (needed for DSF and .pol files)\r
- def _findWidthOfLongitude(self, lat):\r
- dm = math.cos(math.radians(lat)) * 111.321 # <- 1 deg width at equator in km\r
- return round(dm * 1000, 3)\r
-\r
- # Find diameter of an ortho\r
- def find_ortho_diameter(self, side):\r
- sq = side * side\r
- sqsum = sq + sq\r
- dm = math.sqrt(sqsum)\r
- \r
-\r
- # Construct an X-Plane compatible folder name for latitude and longitude\r
- def xplane_latlng_folder(self, numbers):\r
- fstr = ""\r
- if numbers[0] >= 0: fstr = "+"\r
- if numbers[0] < 0: fstr = "-"\r
- if abs(numbers[0]) < 10: fstr = fstr + "0" + str(numbers[0])\r
- if abs(numbers[0]) >= 10 and numbers[0] <= 90: fstr = fstr + str(numbers[0])\r
-\r
- if numbers[1] >= 0: fstr = fstr + "+"\r
- if numbers[1] < 0: fstr = fstr + "-"\r
- if abs(numbers[1]) < 10: fstr = fstr + "00" + str(numbers[1])\r
- if abs(numbers[1]) >= 10 and numbers[0] <= 99: fstr = fstr + "0" + str(numbers[1])\r
- if abs(numbers[1]) >= 100 : fstr = fstr + str(numbers[1])\r
-\r
- return fstr\r
-\r
-\r
- # Build the complete DSF.\r
- # This is the main function to call.\r
- def build_dsf_for_tile(self):\r
- mstr_msg("xp_dsfgen", "[X-Plane] Building DSF file")\r
- # Add the polygon definition file entries\r
- for v in range(1, self._maxlat+1):\r
- for h in range(1, self._maxlng+1):\r
- self._dsfstring = self._dsfstring + "POLYGON_DEF terrain/"+str(v)+"_"+str(h)+".pol\n"\r
-\r
- # Add the definitions for each ortho tile\r
- curpol = 0\r
- cur_lat = self._latitude\r
- cur_lng = self._longitude\r
- for v in range(1, self._maxlat+1):\r
- for h in range(1, self._maxlng+1):\r
- bbox = [ cur_lat, cur_lng, cur_lat+self._vstep-0.00001, cur_lng + mstr_zl_18-0.00001 ]\r
- self._dsfstring = self._dsfstring + "BEGIN_POLYGON "+str(curpol)+" 65535 4\n"\r
- self._dsfstring = self._dsfstring + "BEGIN_WINDING\n"\r
-\r
- self._dsfstring = self._dsfstring + "POLYGON_POINT " + str(bbox[1]) + " " + str(bbox[0]) + " 0.000000000 0.000000000\n"\r
- self._dsfstring = self._dsfstring + "POLYGON_POINT " + str(bbox[3]) + " " + str(bbox[0]) + " 1.000000000 0.000000000\n"\r
- self._dsfstring = self._dsfstring + "POLYGON_POINT " + str(bbox[3]) + " " + str(bbox[2]) + " 1.000000000 1.000000000\n"\r
- self._dsfstring = self._dsfstring + "POLYGON_POINT " + str(bbox[1]) + " " + str(bbox[2]) + " 0.000000000 1.000000000\n"\r
-\r
- self._dsfstring = self._dsfstring + "END_WINDING\n"\r
- self._dsfstring = self._dsfstring + "END_POLYGON\n"\r
-\r
- # Adjust forward\r
- cur_lng = cur_lng + mstr_zl_18\r
- curpol = curpol + 1\r
- \r
- # Adjust up, reset longitude\r
- cur_lng = self._longitude\r
- cur_lat = cur_lat + self._vstep\r
-\r
- # OK... we can now save this\r
- self.write_dsf_txt()\r
-\r
- # Generate the single .pol files now\r
- mstr_msg("xp_dsfgen", "[X-Plane] Beginning generation of terrain/*.pol files")\r
- cur_lat = self._latitude\r
- cur_lng = self._longitude\r
- lclat = cur_lat + (self._vstep/2)\r
- lclng = cur_lng + (mstr_zl_18/2)\r
- for v in range(1, self._maxlat+1):\r
- for h in range(1, self._maxlng+1):\r
- dm = self._findWidthOfLongitude(cur_lng) * mstr_zl_18\r
- dg = self.find_ortho_diameter(dm)\r
- polstr = ""\r
- polstr = polstr + "A\n"\r
- polstr = polstr + "850\n"\r
- polstr = polstr + "DRAPED_POLYGON\n"\r
- polstr = polstr + "\n"\r
- polstr = polstr + "TEXTURE_NOWRAP ../orthos/"+str(v)+"_"+str(h)+".dds\n"\r
-\r
- # Check for existence of a normal map\r
- # If there is one, we will add that too\r
- end_latlng = self.xplane_latlng_folder([self._latitude, self._longitude])\r
- if os.path.isfile(mstr_datafolder + "Tiles/z_orthographic_"+end_latlng+"/normals/" + str(v) + "_" + str(h) + ".png") == True:\r
- polstr = polstr + "TEXTURE_NORMAL 1 ../normals/"+str(v)+"_"+str(h)+".png\n"\r
-\r
- polstr = polstr + "SCALE "+str(dm)+" "+str(dm)+"\n"\r
- polstr = polstr + "LOAD_CENTER "+str(lclat)+" " +str(lclng)+" " +str(dg)+ " " +str(mstr_photores)+"\n"\r
- polstr = polstr + "LAYER_GROUP TERRAIN 1\n"\r
-\r
- # Save this content\r
- with open(mstr_datafolder + "Tiles/z_orthographic_"+end_latlng+"/terrain/"+str(v)+"_"+str(h)+".pol", 'w') as textfile:\r
- textfile.write(polstr) \r
-\r
- # Adjust forward\r
- cur_lng = cur_lng + mstr_zl_18\r
- lclng = cur_lng + (mstr_zl_18/2)\r
-\r
- cur_lng = self._longitude\r
- lclng = cur_lng + (mstr_zl_18/2)\r
-\r
- cur_lat = cur_lat + self._vstep\r
- lclat = cur_lat + (self._vstep/2)\r
-\r
- mstr_msg("xp_dsfgen", "[X-Plane] Converting DSF txt")\r
- self.convert_dsf_text()\r
-\r
- mstr_msg("xp_dsfgen", "[X-Plane] DSF for tile completed")\r
# The Big Mac. Generate the normal map\r
def generate_normal_map_for_layer(self, image):\r
mstr_msg("xp_normalmap", "[X-Plane] Beginning normal map generation")\r
- nmp = Image.new("RGBA", (image.width, image.height), (128,128,255,0)) # no specularity, but default color\r
- #nmp = Image.new("RGBA", (image.width, image.height), (0,0,0,0))\r
+ # No specularity, no reflectivity - but standard color\r
+ # Blue (reflectivity) and alpha (specularity) need to be 1 - but can be adjusted as needed\r
+ nmp = Image.new("RGBA", (image.width, image.height), (128,128,1,1))\r
org = image.load()\r
nmp_pix = nmp.load()\r
\r
- # Find out which alpha value to use\r
- alpha = 0\r
- for n in mstr_xp_normal_maps:\r
- if n[0] == self._tag and n[1] == self._value:\r
- v = n[2] / 100\r
- a = v * 255.0\r
- alpha = int(a)\r
-\r
-\r
# Let's try some shenanigans\r
w = image.width\r
h = image.height\r
v = (dx, dy, dz)\r
nrm = self.normalize_vector(v)\r
\r
- # Invert height for our Orthos - but only in some circumstances\r
- # We want water to go "down"\r
- if (self._tag != "natural" and self._value != "water") or (self._tag != "water" and self._value != "lake") or (self._tag != "water" and self._value != "pond") or (self._tag != "water" and self._value != "river"):\r
- if nrm[1] > 0:\r
- nrm[1] = 0 - (abs(nrm[1]))\r
- else:\r
- nrm[1] = abs(nrm[1])\r
+ if nrm[1] > 0:\r
+ nrm[1] = 0 - (abs(nrm[1]))\r
+ else:\r
+ nrm[1] = abs(nrm[1])\r
\r
# Set pixel\r
- nmp_pix[x,y] = (int(self.map_component(nrm[0])), int(self.map_component(nrm[1])), int(self.map_component(nrm[2])), alpha)\r
+ nmp_pix[x,y] = (int(self.map_component(nrm[0])), int(self.map_component(nrm[1])), int(self.map_component(nrm[2])), 1)\r
\r
mstr_msg("xp_normalmap", "[X-Plane] Normal map generated")\r
return nmp\r
nrm = self.generate_normal_map_for_layer(lyr)\r
\r
# Normal map final file name\r
- nrmfln = mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/normals/" + str(self._tv) + "_" + str(self._th) + ".png"\r
+ nrmfln = mstr_datafolder + "z_orthographic/normals/" + self._latlngfld + "/" + str(self._tv) + "_" + str(self._th) + ".png"\r
\r
# Check for existence of normal map file\r
ex = os.path.isfile(nrmfln)\r
if ex == True:\r
nrmmap = Image.open(nrmfln)\r
nrmmap.alpha_composite(nrm)\r
+\r
+ # Specularity blending correction\r
+ nrmmap_pix = nrmmap.load()\r
+ for y in range(nrmmap.height):\r
+ for x in range(nrmmap.width):\r
+ c = nrmmap_pix[x,y]\r
+ nrmmap_pix[x,y] = (c[0], c[1], c[2], 1)\r
nrmmap.save(nrmfln)\r
+ \r
mstr_msg("xp_normalmap", "[X-Plane] Normal map saved")\r
--- /dev/null
+
+# -------------------------------------------------------------------
+# 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_scenery.py
+# This class builds an elevation mesh from provided DEM data, and
+# generates all required files that are needed to provide a usable
+# scenery package for X-Plane.
+# -------------------------------------------------------------------
+
+import os
+import math
+import urllib.request
+from defines import *
+from log import *
+
+class mstr_xp_scenery:
+ # Set required variables
+ def __init__(self, lat, lng, mlat, mlng, vstep, latlngfld):
+ mstr_msg("xp_scenery", "[X-Plane] Scenery generator instantiated")
+ self._lat = lat
+ self._lng = lng
+ self._mlat = mlat
+ self._mlng = mlng
+ self._vstep = vstep
+ self._latlngfld = latlngfld
+ self._demfn = self.build_dem_filename()
+
+
+ # Build the correct file name for the elevation model
+ def build_dem_filename(self, xes=False):
+ fn = ""
+ if self._lat > 0:
+ fn = fn + "N"
+ else:
+ fn = fn + "S"
+
+ if abs(self._lat) < 10: fn = fn + "0" + str(self._lat)
+ if abs(self._lat) >= 10 and abs(self._lat) <= 90: fn = fn + str(self._lat)
+
+ if self._lng > 0:
+ fn = fn + "E"
+ else:
+ fn = fn + "W"
+
+ if abs(self._lng) < 10: fn = fn + "00" + str(self._lng)
+ if abs(self._lng) >= 10 and abs(self._lng) <= 99: fn = fn + "0" + str(self._lng)
+ if abs(self._lng) >= 100 : fn = fn + str(self._lng)
+
+ if xes == False:
+ fn = fn + ".hgt"
+ if xes == True:
+ fn = fn + ".xes"
+
+ mstr_msg("xp_scenery", "[X-Plane] DEM file name constructed: " + fn)
+
+ return fn
+
+ # Generate the mesh script for the ortho photos
+ def build_mesh_script(self):
+ scr = mstr_datafolder + "z_orthographic/data/meshscript.txt"
+ # Before we blast all these lines into the file, we need to make sure they do not exist already
+ write_lines = True
+
+ if os.path.isfile(scr) == True:
+ fnlines = []
+ with open(scr) as textfile:
+ fnlines = textfile.readlines()
+
+ for line in fnlines:
+ l = line.split(" ")
+ if l[2] == str(self._lng) and l[3] == str(self._lat):
+ write_lines = False
+ break
+ else:
+ open(scr, 'a').close()
+
+ # If we did not find the initial corner coordinate in the script, we can go ahead
+ if write_lines == True:
+ mstr_msg("xp_scenery", "[X-Plane] Writing mesh script file")
+ # We basically run through all tiles and note down the position of the orthos
+ # as needed by X-Plane.
+ cur_lat = self._lat
+ cur_lng = self._lng
+ for lat in range(1, self._mlat+1):
+ for lng in range(1, self._mlng+1):
+ # The '1' after 'ORTHOPHOTO' defines we want water underneath transparent parts of the DDS texture/ortho.
+ # This ensures that even if the mesh does not include information for there being a water body,
+ # we will get 100% correct representation of the water bodies.
+ scrtxt = "ORTHOPHOTO 1 " + str(cur_lng) + " " + str(cur_lat) + " " + str(round(cur_lng+mstr_zl_18, 6)) + " " + str(cur_lat) + " " + str(round(cur_lng+mstr_zl_18, 6)) + " " + str(round(cur_lat+self._vstep, 6)) + " " + str(cur_lng) + " " + str(round(cur_lat+self._vstep, 6)) + " terrain/" + self._latlngfld + "/" + str(lat) + "_" + str(lng) + ".ter\n"
+
+ with open(scr, 'a') as textfile:
+ textfile.write(scrtxt)
+
+ cur_lng = round(cur_lng + mstr_zl_18, 6)
+
+ cur_lng = self._lng
+ cur_lat = round(cur_lat + self._vstep, 6)
+ mstr_msg("xp_scenery", "[X-Plane] Mesh script completed")
+
+
+ # Find the next "by-ten" numbers for the current latitude and longitude
+ def find_earthnavdata_number(self):
+ earthnavdata = []
+ lat = abs(int(self._lat / 10) * 10)
+ lng = abs(int(self._lng / 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
+
+
+ # Acquires the elevation data as needed -
+ # you can either acquire the older STRM set with lower resolution,
+ # or a modern LIDAR scan with higher resolution. However, the
+ # LIDAR files are much larger and generates a more taxing mesh.
+ # If you are testing, acquire the low resolution file.
+ # Both versions are coming from my repository at marstr.online .
+ def acquire_elevation_data(self):
+ mstr_msg("xp_scenery", "[X-Plane] Acquiring DEM model data")
+ url = "https://marstr.online/dem/"
+
+ url = url + self._demfn
+
+ urllib.request.urlretrieve(url, mstr_datafolder + "_cache/" + self._demfn)
+ mstr_msg("xp_scenery", "[X-Plane] DEM data acquired")
+
+
+ # Download the X-Plane definition file from my server
+ def acquire_xes_data(self):
+ mstr_msg("xp_scenery", "[X-Plane] Acquiring XES file")
+ url = "https://marstr.online/xes/"
+ xesfn = self.build_dem_filename(True)
+
+ xp_folder = self.xplane_latlng_folder([self._lat, self._lng])
+ url = url + xp_folder + ".xes"
+
+ urllib.request.urlretrieve(url, mstr_datafolder + "_cache/" + xesfn)
+ mstr_msg("xp_scenery", "[X-Plane] XES data acquired")
+
+
+ # This builds the entire mesh in one go
+ def build_mesh(self):
+ end_bt = self.find_earthnavdata_number()
+ btlfn = str(self.xplane_latlng_folder(end_bt))
+ xp_folder = self.xplane_latlng_folder([self._lat, self._lng])
+ scr = mstr_datafolder + "z_orthographic/data/meshscript.txt"
+ wd = mstr_datafolder + "z_orthographic/data"
+ dsf = mstr_datafolder + "z_orthographic/Earth nav data/" + btlfn + "/" + xp_folder
+ xesfn = self.build_dem_filename(True)
+
+ # The main command to build the mesh
+ cmd = mstr_xp_meshtool + " \"" + scr + "\" \"" + mstr_datafolder + "_cache/" + xesfn + "\"" + " \"" + mstr_datafolder + "_cache/" + self._demfn + "\" \"" + wd + "\" \"" + dsf + ".dsf\""
+
+ os.system(cmd)
+
+
+# Individual testing
+#scn = mstr_xp_scenery(51, 7, 101, 64, 0.010102, "+51+007")
+#scn.acquire_xes_data()
+#scn.build_mesh_script()
+#scn.build_mesh()
\ No newline at end of file