From bd2594221df8d38ae243e0c18190ea542d43f3cc Mon Sep 17 00:00:00 2001 From: "Marcus Str." Date: Fri, 18 Oct 2024 21:48:24 +0200 Subject: [PATCH] Last minor adjustments and a needed feature for PBF generation (needed for a later stage) --- defines.py | 1 + layergen.py | 8 ++--- maskgen.py | 16 ++-------- og.py | 24 +++++++++++++-- orthographic.py | 73 ++++++++++++++++++++++++++++++++++++++++++--- osmxml.py | 78 +++++++++++++++++++++++++++++++++++++++++++++---- 6 files changed, 171 insertions(+), 29 deletions(-) diff --git a/defines.py b/defines.py index 89ed631..5dc427a 100644 --- a/defines.py +++ b/defines.py @@ -75,6 +75,7 @@ mstr_xp_scn_normalmaps = True mstr_xp_meshtool = "/home/marcus/Developer/Projects/orthographic/bin/MeshTool" mstr_xp_ddstool = "/home/marcus/Developer/Projects/orthographic/bin/DDSTool" mstr_xp_xessrc = "https://dev.x-plane.com/update/misc/MeshTool/" +mstr_xp_floor_height = 2.8 # 2.5m ceiling height + 30cm concrete per floor # If you set the above to true, you can define for which features you # want to generate normal maps for. The below is my recommendation for diff --git a/layergen.py b/layergen.py index c873a2b..6c02f9b 100644 --- a/layergen.py +++ b/layergen.py @@ -1123,7 +1123,7 @@ class mstr_layergen: ptc_src.append(Image.open(p)) osm_mask = Image.open( mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + ".png" ) - lyr_mask = Image.open( mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer.png" ) + #lyr_mask = Image.open( mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer.png" ) for i in mstr_mask_blur: if i[0] == self._tag and i[1] == self._value: @@ -1147,12 +1147,12 @@ class mstr_layergen: adj_image.alpha_composite( brd_src ) - lyr_pix = lyr_mask.load() + #lyr_pix = lyr_mask.load() for y in range(self._imgsize): for x in range(self._imgsize): - if lyr_pix[x, y][3] > 0: + if mask_pix[x, y][3] > 0: rgb=adj_pix[x,y] - a=lyr_pix[x,y] + a=mask_pix[x,y] adj_pix[x, y] = ( rgb[0], rgb[1], rgb[2], a[3]) # Up until here we mimiced the exact same behavior as layergen. However, now diff --git a/maskgen.py b/maskgen.py index 732d08c..9515e33 100644 --- a/maskgen.py +++ b/maskgen.py @@ -13,12 +13,6 @@ # that this part of the code is the most crucial one, as the other # classes involved rely on what this code is doing, and by extension, # generating. -# -# The PNG generated will be used in this progression: -# - Generate mask from OSM (here) -# - Generate colored photo layer from this mask, for example for -# landuse: forest -# - Compile actual satellite aerial # ------------------------------------------------------------------- import math @@ -83,9 +77,7 @@ class mstr_maskgen: # Builds the required mask def _build_mask(self): # Generate empty image - imgsize = 0 - if mstr_photores == 2048: imgsize=2048 - if mstr_photores == 4096: imgsize=6000 + imgsize = 2048 mask_img = Image.new("RGBA", (imgsize, imgsize)) tilexml = mstr_datafolder + "_cache/tile.xml" @@ -119,7 +111,7 @@ class mstr_maskgen: for d in way: if d[0] == w[0]: if self._tag == "building" and bld_levels == 0: - bld_levels = xml.find_building_levels(tilexml, w[0]) + bld_levels = xml.find_building_levels(w[0]) nd.append(d[1]) frs.append(nd) # Scout through relations as these also make up map data @@ -160,9 +152,7 @@ class mstr_maskgen: p_lng = self.project_pixel(latlng[1], bbox[3]) pixlat = 0 pixlng = 0 - pr = 0 - if mstr_photores == 2048: pr = 2048 - if mstr_photores == 4096: pr = 6000 + pr = 2048 # Draw pixels in direction according to latitude and longitude positions - diff --git a/og.py b/og.py index 3050a04..f8f128e 100644 --- a/og.py +++ b/og.py @@ -22,7 +22,7 @@ from defines import * print(" ") print(" ---------------------------------------------------------------- ") print(" ORTHOGRAPHIC: An ortho-photo generator, using real world data.") -print(" Developed by MarStrMind - Code available on marstr.online") +print(" Developed by MarStr - Code available on marstr.online") print(" ---------------------------------------------------------------- ") print(" ") @@ -30,9 +30,14 @@ print(" ") # Evaluate CLI arguments and process tile. cli = False +pbf = False + if len(sys.argv) == 3: cli = True +if len(sys.argv) == 4: + pbf = True + # Only if we find enough arguments, proceed. if cli == True: lat = int(sys.argv[1]) @@ -44,10 +49,23 @@ if cli == True: og = mstr_orthographic(lat, lng, mstr_datafolder, os.getcwd()) og._buildTile() -else: + +# Only if we find enough arguments, proceed. +if pbf == True: + lat = int(sys.argv[1]) + lng = int(sys.argv[2]) + pbf = sys.argv[3] + + if pbf == "pbf": + # Create the class and init values + og = mstr_orthographic(lat, lng, mstr_datafolder, os.getcwd()) + og._generateData() + + +if cli == False and pbf == False: mstr_msg("_main", "Please provide Latitude and Longitude. Exiting.") print ("") -# * No Space Shuttle or SpaceShipOne needed to deploy. \ No newline at end of file +# * No Space Shuttle or SpaceShipOne needed to deploy. diff --git a/orthographic.py b/orthographic.py index a279c4c..16eb887 100644 --- a/orthographic.py +++ b/orthographic.py @@ -77,6 +77,74 @@ class mstr_orthographic: return round(dm * 1000, 3) + # In this case we only want to acquire PBF for a latitude and longitude. Normally + # not needed for standard ortho generation. + def _generateData(self): + # The tile is constructed of many smaller parts. We walk through the + # smallest possible, from which the bigger ones are later built. + bb_lat = self._lat + bb_lng = self._long + bb_lat_edge = self._lat+self._vstep + bb_lng_edge = self._long+mstr_zl_18 + cur_tile_x = 1 + cur_tile_y = 1 + osmxml = mstr_osmxml(0,0) + mstr_msg("orthographic", "Set initial coordinates and bounding box for OSM acquisition") + + # The highest encountered tile numbers + # This is needed to produce the zoom level 16 images + top_lat = 1 + top_lng = 1 + + # We need to know the highest possible latitude and longitude tile numbers, + # in case we render at the edge + mlat = 1 + mlng = 1 + while bb_lat < self._lat + 1: + bb_lat = bb_lat + self._vstep + mlat = mlat+1 + while bb_lng < self._long + 1: + bb_lng = bb_lng + mstr_zl_18 + mlng = mlng+1 + mstr_msg("orthographic", "Max lat tile: " + str(mlat) + " - max lng tile: " + str(mlng)) + maxlatlng = [ mlat, mlng ] + + # Reset these two + bb_lat = self._lat + bb_lng = self._long + + # 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. + for lat_grid in range(1, maxlatlng[0]+1): + for lng_grid in range(1, maxlatlng[1]+1): + # Adjust bounding box + osmxml.adjust_bbox(bb_lat, bb_lng, bb_lat_edge, bb_lng_edge) + + osmxml.generate_osm(cur_tile_y, cur_tile_x) # <- This acquires current OSM info + + # Adjust longitude coordinates + cur_tile_x = cur_tile_x+1 + bb_lng = bb_lng + mstr_zl_18 + bb_lng_edge = bb_lng_edge + mstr_zl_18 + mstr_msg("orthographic", "Adjustment of longitude performed") + # Adjust peak longitude tile number + if cur_tile_x > top_lng: + top_lng = cur_tile_x + + # Adjust latitude and all other values when we get here + cur_tile_y = cur_tile_y+1 + cur_tile_x = 1 + bb_lng = self._long + bb_lng_edge = self._long + mstr_zl_18 + bb_lat = bb_lat + self._vstep + bb_lat_edge = bb_lat_edge + self._vstep + mstr_msg("orthographic", "Adjustment of latitude performed") + # Adjust peak latitude number + if cur_tile_y > top_lat: + top_lat = cur_tile_y + + # Builds and processes the tile with everything required, in one call. def _buildTile(self): mstr_msg("orthographic", "Beginning construction of tile") @@ -163,9 +231,6 @@ 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. @@ -363,4 +428,4 @@ class mstr_orthographic: lng = abs(int(self._long / 10) * 10) earthnavdata.append(lat) earthnavdata.append(lng) - return earthnavdata \ No newline at end of file + return earthnavdata diff --git a/osmxml.py b/osmxml.py index 3f4c931..c80dc4b 100644 --- a/osmxml.py +++ b/osmxml.py @@ -34,7 +34,27 @@ class mstr_osmxml: self._lng = round(lng, 4) self._curB_lat = round(lat_e, 4) self._curB_lng = round(lng_e, 4) + + def generate_osm(self, v, h, asobject=False): + mstr_msg("osmxml", "Acquiring OSM data for " + str(self._lat)+","+str(self._lng)+" - "+str(self._curB_lat)+","+str(self._curB_lng)) + + # We will use our self-hosted API for this. + data = { + "bbox": { + "lat": str(self._lat), + "lng": str(self._lng), + "lat_b": str(self._curB_lat), + "lng_b": str(self._curB_lng) + }, + "tile_lat": str(self._lat), + "tile_lng": str(self._lng), + "square_lat": str(v), + "square_lng": str(h), + "as_pbf": "true" + } + r = requests.post(mstr_osm_endpoint, json=data) + # Acquire XMLs in chunks, then store them def acquire_osm(self, v, h, asobject=False): @@ -65,7 +85,7 @@ class mstr_osmxml: # 1 second delay in case the request fails if os.path.isfile(mstr_datafolder + "_cache/tile.xml") == False: sleep(1) - + # Provide the object directly if asobject == True: xml_doc = xml.dom.minidom.parse("_cache/tile.xml") @@ -152,9 +172,9 @@ class mstr_osmxml: # Find the levels of a building (for shadow rendering) - def find_building_levels(self, xmlfile, way_id): - lvl = 3 # Standard if we don't find anything else - xml_doc = xml.dom.minidom.parse(xmlfile) + def find_building_levels(self, way_id): + lvl = 2 # Standard if we don't find anything else + xml_doc = xml.dom.minidom.parse(mstr_datafolder + "_cache/tile.xml") wpdata = xml_doc.getElementsByTagName("way") for wp in wpdata: if wp.getAttribute("id") == way_id: @@ -167,6 +187,54 @@ class mstr_osmxml: break return lvl + # Find minimum level of a building + def find_building_minlevel(self, way_id): + lvl = 0 # Standard if we don't find anything else + xml_doc = xml.dom.minidom.parse(mstr_datafolder + "_cache/tile.xml") + wpdata = xml_doc.getElementsByTagName("way") + for wp in wpdata: + if wp.getAttribute("id") == way_id: + tags = wp.getElementsByTagName("tag") + for tag in tags: + a = tag.getAttribute("k") + v = tag.getAttribute("v") + if a == "building:min_level": + lvl = int(v) + break + return lvl + + # Find the roof shape + def find_building_roof_shape(self, way_id): + rs = "flat" # Standard if we don't find anything else + xml_doc = xml.dom.minidom.parse(mstr_datafolder + "_cache/tile.xml") + wpdata = xml_doc.getElementsByTagName("way") + for wp in wpdata: + if wp.getAttribute("id") == way_id: + tags = wp.getElementsByTagName("tag") + for tag in tags: + a = tag.getAttribute("k") + v = tag.getAttribute("v") + if a == "roof:shape": + rs = v + break + return rs + + # Find the roof levels + def find_building_roof_levels(self, way_id): + lvl = 0 # Standard if we don't find anything else + xml_doc = xml.dom.minidom.parse(mstr_datafolder + "_cache/tile.xml") + wpdata = xml_doc.getElementsByTagName("way") + for wp in wpdata: + if wp.getAttribute("id") == way_id: + tags = wp.getElementsByTagName("tag") + for tag in tags: + a = tag.getAttribute("k") + v = tag.getAttribute("v") + if a == "roof:levels": + lvl = int(v) + break + return lvl + # It turns out that some features hide themselves in the relations section. # I figured this out during testing, and almost going insane over the @@ -191,4 +259,4 @@ class mstr_osmxml: tgd.append(i.getAttribute("v")) rls.append((rp.getAttribute("ref"), tgd)) return rls - \ No newline at end of file + -- 2.30.2