From c74258e3ad9ab8e371426acb2c834794a9563558 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Mon, 26 Feb 2024 08:55:03 +0100 Subject: [PATCH] Completed automatic algorithm --- frontend/server/routes/resources.js | 123 +------------- frontend/website/src/map/map.ts | 4 +- olympus.json | 3 +- .../python/map_generator/.vscode/launch.json | 2 +- .../configs/LasVegas/LasVegas.yml | 4 - .../LasVegas/{LasVegas.kml => boundary.kml} | 0 .../map_generator/configs/LasVegas/config.yml | 6 + .../map_generator/configs/NTTR/boundary.kml | 84 ++++++++++ .../map_generator/configs/NTTR/config.yml | 5 + .../configs/screen_properties.yml | 10 +- scripts/python/map_generator/main.py | 42 +++-- scripts/python/map_generator/map_generator.py | 158 +++++++++++++----- 12 files changed, 247 insertions(+), 194 deletions(-) delete mode 100644 scripts/python/map_generator/configs/LasVegas/LasVegas.yml rename scripts/python/map_generator/configs/LasVegas/{LasVegas.kml => boundary.kml} (100%) create mode 100644 scripts/python/map_generator/configs/LasVegas/config.yml create mode 100644 scripts/python/map_generator/configs/NTTR/boundary.kml create mode 100644 scripts/python/map_generator/configs/NTTR/config.yml diff --git a/frontend/server/routes/resources.js b/frontend/server/routes/resources.js index e2460f42..391a1550 100644 --- a/frontend/server/routes/resources.js +++ b/frontend/server/routes/resources.js @@ -29,127 +29,6 @@ module.exports = function (configLocation) { res.sendStatus(404); } }); - - router.get('/maps/:map/:z/:x/:y.png', async function (req, res, next) { - let map = req.params.map; - let x = req.params.x; - let y = req.params.y; - let z = req.params.z; - - if (fs.existsSync(`.\\public\\maps\\${map}`)) { - if (!await renderImage(map, z, x, y, res)) { - /* No image was found */ - res.sendStatus(404); - } - } else { - /* The requested map does not exist */ - res.sendStatus(404); - } - }); - - async function renderImage(map, z, x, y, res, recursionDepth = 0) { - if (recursionDepth == 20) { - console.log("Render image, maximum recursion depth reached") - /* We have reached limit recusion depth, something went wrong */ - return false; - } - /* If the requested image exists, send it straight away */ - if (fs.existsSync(`.\\public\\maps\\${map}\\${z}\\${x}\\${y}.png`)) { - res.sendFile(`${process.cwd()}\\public\\maps\\${map}\\${z}\\${x}\\${y}.png`); - return true; - } else { - /* If the requested image doesn't exist check if there is a "source" tile at a lower zoom level we can split up */ - let sourceZoom = z - 1; - let sourceX = Math.floor(x / 2); - let sourceY = Math.floor(y / 2); - - /* Keep decreasing the zoom level until we either find an image or reach maximum zoom out and must return false */ - while (sourceZoom >= 0) { - /* We have found a possible source image */ - if (fs.existsSync(`.\\public\\maps\\${map}\\${sourceZoom}\\${sourceX}\\${sourceY}.png`)) { - /* Split the image into four. We can retry up to 10 times to clear any race condition on the files */ - let retries = 10; - while (!await splitTile(map, sourceZoom, sourceX, sourceY) && retries > 0) { - await new Promise(r => setTimeout(r, 1000)); - retries--; - } - /* Check if we exited because we reached the maximum retry value */ - if (retries != 0) { - /* Recursively recall the function now that we have a new "source" image */ - return await renderImage(map, z, x, y, res, recursionDepth + 1); - } else { - return false; - } - } else { - /* Keep searching at a higher level */ - sourceZoom = sourceZoom - 1; - sourceX = Math.floor(sourceX / 2); - sourceY = Math.floor(sourceY / 2); - } - } - return false; - } - } - - async function splitTile(map, z, x, y) { - try { - /* Load the source image */ - let img = sharp(`${process.cwd()}\\public\\maps\\${map}\\${z}\\${x}\\${y}.png`); - - /* Create the necessary folders */ - await pfs.mkdir(`${process.cwd()}\\public\\maps\\${map}\\${z + 1}\\${2*x}`, { recursive: true }) - await pfs.mkdir(`${process.cwd()}\\public\\maps\\${map}\\${z + 1}\\${2*x + 1}`, { recursive: true }) - - /* Split the image into four parts */ - await resizePromise(img, 0, 0, map, z + 1, 2 * x, 2 * y); - img = sharp(`${process.cwd()}\\public\\maps\\${map}\\${z}\\${x}\\${y}.png`); - await resizePromise(img, 128, 0, map, z + 1, 2 * x + 1, 2 * y); - img = sharp(`${process.cwd()}\\public\\maps\\${map}\\${z}\\${x}\\${y}.png`); - await resizePromise(img, 0, 128, map, z + 1, 2 * x, 2 * y + 1); - img = sharp(`${process.cwd()}\\public\\maps\\${map}\\${z}\\${x}\\${y}.png`); - await resizePromise(img, 128, 128, map, z + 1, 2 * x + 1, 2 * y + 1); - return true; - } catch (err) { - if (err.code !== 'EBUSY') { - console.error(err); - } - return false; - } - } - - /* Returns a promise, extracts a 128x128 pixel chunk from an image, resizes it to 256x256, and saves it to file */ - function resizePromise(img, left, top, map, z, x, y) { - return new Promise((res, rej) => { - if (fs.existsSync(`${process.cwd()}\\public\\maps\\${map}\\${z}\\${x}\\${y}.png`)) { - res(true); - } - img.extract({ left: left, top: top, width: 128, height: 128 }).resize(256, 256).toFile(`${process.cwd()}\\public\\maps\\${map}\\${z}\\${x}\\${y}.temp.png`, function (err) { - if (err) { - rej(err); - } else { - try { - fs.renameSync(`${process.cwd()}\\public\\maps\\${map}\\${z}\\${x}\\${y}.temp.png`, `${process.cwd()}\\public\\maps\\${map}\\${z}\\${x}\\${y}.png`); - } catch (err) { - if (err.code === 'EBUSY') { - /* The resource is busy, someone else is writing or renaming it. Reject the promise so we can try again */ - rej(err); - } else if (err.code === 'ENOENT') { - /* Someone else renamed the file already so this is not a real error */ - if (fs.existsSync(`${process.cwd()}\\public\\maps\\${map}\\${z}\\${x}\\${y}.png`)) { - res(true); - } - /* Something odd happened, reject and try again */ - else { - rej(err); - } - } else { - rej(err); - } - } - res(true) - } - }); - }) - } + return router; } diff --git a/frontend/website/src/map/map.ts b/frontend/website/src/map/map.ts index 32eccb44..4c82614c 100644 --- a/frontend/website/src/map/map.ts +++ b/frontend/website/src/map/map.ts @@ -252,7 +252,9 @@ export class Map extends L.Map { var options: L.TileLayerOptions = { attribution: layerData.attribution, minZoom: layerData.minZoom, - maxZoom: layerData.maxZoom + maxZoom: layerData.maxZoom, + minNativeZoom: layerData.minNativeZoom, + maxNativeZoom: layerData.maxNativeZoom }; this.#layer = new L.TileLayer(layerData.urlTemplate, options); } diff --git a/olympus.json b/olympus.json index 54c8916f..ccac4692 100644 --- a/olympus.json +++ b/olympus.json @@ -17,9 +17,10 @@ }, "additionalMaps": { "DCS": { - "urlTemplate": "http://localhost:3000/resources/maps/dcs/{z}/{x}/{y}.png", + "urlTemplate": "http://localhost:3000/maps/dcs/{z}/{x}/{y}.jpg", "minZoom": 8, "maxZoom": 20, + "maxNativeZoom": 17, "attribution": "Eagle Dynamics" } } diff --git a/scripts/python/map_generator/.vscode/launch.json b/scripts/python/map_generator/.vscode/launch.json index c5fdb6ca..acdc83b5 100644 --- a/scripts/python/map_generator/.vscode/launch.json +++ b/scripts/python/map_generator/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "program": "main.py", "console": "integratedTerminal", - "args": ["./configs/LasVegas/LasVegas.yml"] + "args": ["./configs/NTTR/config.yml"] } ] } \ No newline at end of file diff --git a/scripts/python/map_generator/configs/LasVegas/LasVegas.yml b/scripts/python/map_generator/configs/LasVegas/LasVegas.yml deleted file mode 100644 index 404a661b..00000000 --- a/scripts/python/map_generator/configs/LasVegas/LasVegas.yml +++ /dev/null @@ -1,4 +0,0 @@ -{ - 'output_directory': '.\LasVegas', # Where to save the output files - 'boundary_file': '.\configs\LasVegas\LasVegas.kml' -} \ No newline at end of file diff --git a/scripts/python/map_generator/configs/LasVegas/LasVegas.kml b/scripts/python/map_generator/configs/LasVegas/boundary.kml similarity index 100% rename from scripts/python/map_generator/configs/LasVegas/LasVegas.kml rename to scripts/python/map_generator/configs/LasVegas/boundary.kml diff --git a/scripts/python/map_generator/configs/LasVegas/config.yml b/scripts/python/map_generator/configs/LasVegas/config.yml new file mode 100644 index 00000000..c7b54066 --- /dev/null +++ b/scripts/python/map_generator/configs/LasVegas/config.yml @@ -0,0 +1,6 @@ +{ + 'output_directory': '.\LasVegas', # Where to save the output files + 'boundary_file': '.\configs\LasVegas\boundary.kml', # Input kml file setting the boundary of the map to create + 'zoom_factor': 0.02, # [0: maximum zoom in (things look very big), 1: maximum zoom out (things look very small)] + 'geo_width': 1.14 +} \ No newline at end of file diff --git a/scripts/python/map_generator/configs/NTTR/boundary.kml b/scripts/python/map_generator/configs/NTTR/boundary.kml new file mode 100644 index 00000000..486eb401 --- /dev/null +++ b/scripts/python/map_generator/configs/NTTR/boundary.kml @@ -0,0 +1,84 @@ + + + + Senza titolo + + + + + + + + + normal + #__managed_style_1847AF2A832F1651A60F + + + highlight + #__managed_style_2C7F63B5A12F1651A60F + + + + NTTR + + -117.2703145690532 + 37.39557832822189 + 1754.517427470683 + 359.4706465490362 + 0 + 35 + 1393300.815671235 + absolute + + #__managed_style_043F3D3A202F1651A60F + + + + + -119.7864240113604,34.44074394422174,0 -112.42342379541,34.34217218687283,0 -112.1179107081757,39.75928290264283,0 -120.0041004413372,39.79698539473655,0 -119.7864240113604,34.44074394422174,0 + + + + + + + diff --git a/scripts/python/map_generator/configs/NTTR/config.yml b/scripts/python/map_generator/configs/NTTR/config.yml new file mode 100644 index 00000000..7583cafe --- /dev/null +++ b/scripts/python/map_generator/configs/NTTR/config.yml @@ -0,0 +1,5 @@ +{ + 'output_directory': '.\NTTR', # Where to save the output files + 'boundary_file': '.\configs\NTTR\boundary.kml', # Input kml file setting the boundary of the map to create + 'zoom_factor': 0.5 # [0: maximum zoom in (things look very big), 1: maximum zoom out (things look very small)] +} \ No newline at end of file diff --git a/scripts/python/map_generator/configs/screen_properties.yml b/scripts/python/map_generator/configs/screen_properties.yml index 6e7cd752..d578b910 100644 --- a/scripts/python/map_generator/configs/screen_properties.yml +++ b/scripts/python/map_generator/configs/screen_properties.yml @@ -1,10 +1,4 @@ { - 'width': 1920, # The width of your screen, in pixels - 'height': 1080, # The height of your screen, in pixels - 'geo_resolution': 1.0 # The resolution of the map on the screen, in meters per pixel. - # To measure this value, first set the F10 map at the desired zoom level. - # Then, use F10's map measure tool, and measure the width of the screen in meters. - # Finally, divide that value by the width in pixels. - # A good value would be around 1 meter per pixel, meaning a 1920px wide map would measure about 1 nautical mile across on the F10 map - # Lower values will produce higher resolution maps, but beware of space usage! + 'width': 1920, # The width of your screen, in pixels + 'height': 1080 # The height of your screen, in pixels } \ No newline at end of file diff --git a/scripts/python/map_generator/main.py b/scripts/python/map_generator/main.py index 79cac4a6..baa269d8 100644 --- a/scripts/python/map_generator/main.py +++ b/scripts/python/map_generator/main.py @@ -1,5 +1,9 @@ import sys import yaml +import json +import requests +import time + from pyproj import Geod from fastkml import kml from shapely import wkt @@ -18,20 +22,19 @@ else: screen_config = yaml.safe_load(sp) map_config = yaml.safe_load(cp) - print("#################################################################################################################################################") - print("# IMPORTANT NOTE: the screen properties must be configured according to your screen and desired zoom level. Make sure you set them accordingly. #") - print("#################################################################################################################################################") - print("Screen parameters:") - print(f"-> Screen width: {screen_config["width"]}px") - print(f"-> Screen height: {screen_config["height"]}px") - print(f"-> Geographic resolution: {screen_config["geo_resolution"]} meters/pixel") + print(f"-> Screen width: {screen_config['width']}px") + print(f"-> Screen height: {screen_config['height']}px") print("Map parameters:") - print(f"-> Output directory: {map_config["output_directory"]}") - print(f"-> Boundary file: {map_config["boundary_file"]}") + print(f"-> Output directory: {map_config['output_directory']}") + print(f"-> Boundary file: {map_config['boundary_file']}") + print(f"-> Zoom factor: {map_config['zoom_factor']}") - with open(map_config["boundary_file"], 'rt', encoding="utf-8") as bp: + if 'geo_width' in map_config: + print(f"-> Geo width: {map_config['geo_width']}NM") + + with open(map_config['boundary_file'], 'rt', encoding="utf-8") as bp: # Read the config file and compute the total area of the covered map doc = bp.read() k = kml.KML() @@ -48,8 +51,17 @@ else: print(f"Found {len(features)} features in the provided kml file") - tile_size = 256 * screen_config["geo_resolution"] # meters - tiles_per_screenshot = int(screen_config["width"] / 256) * int(screen_config["height"] / 256) + if 'geo_width' not in map_config: + # Let the user input the size of the screen to compute resolution + data = json.dumps({'lat': features[0].geometry.bounds[1], 'lng': features[0].geometry.bounds[0], 'alt': 1350 + map_config['zoom_factor'] * (25000 - 1350)}) + r = requests.put('http://localhost:8080', data = data) + print("The F10 map in your DCS installation was setup. Please, use the measure tool and measure the width of the screen in Nautical Miles") + map_config['geo_width'] = input("Insert the width of the screen in Nautical Miles: ") + + map_config['mpps'] = float(map_config['geo_width']) * 1852 / screen_config['width'] + + tile_size = 256 * map_config['mpps'] # meters + tiles_per_screenshot = int(screen_config['width'] / 256) * int(screen_config['height'] / 256) tiles_num = int(area / (tile_size * tile_size)) screenshots_num = int(tiles_num / tiles_per_screenshot) total_time = int(screenshots_num / 1.0) @@ -57,11 +69,11 @@ else: print(f"Total area: {int(area / 1e6)} square kilometers") print(f"Estimated number of tiles: {tiles_num}") print(f"Estimated number of screenshots: {screenshots_num}") - print(f"Estimated time to complete: {timedelta(seconds=total_time)} (hh:mm:ss)") - print("The script is ready to go. After you press enter, it will wait for 5 seconds, then it will start.") - + print(f"Estimated time to complete: {timedelta(seconds=total_time * 0.15)} (hh:mm:ss)") input("Press enter to continue...") + map_generator.run(map_config) + diff --git a/scripts/python/map_generator/map_generator.py b/scripts/python/map_generator/map_generator.py index f5adaff2..27726b27 100644 --- a/scripts/python/map_generator/map_generator.py +++ b/scripts/python/map_generator/map_generator.py @@ -4,11 +4,14 @@ import pyautogui import time import os import yaml +import json from fastkml import kml from shapely import wkt, Point from PIL import Image from concurrent import futures +from os import listdir +from os.path import isfile, isdir, join # global counters fut_counter = 0 @@ -16,6 +19,7 @@ tot_futs = 0 # constants C = 40075016.686 # meters, Earth equatorial circumference +R = C / (2 * math.pi) def deg_to_num(lat_deg, lon_deg, zoom): lat_rad = math.radians(lat_deg) @@ -48,33 +52,24 @@ def done_callback(fut): fut_counter += 1 printProgressBar(fut_counter, tot_futs) -def extract_tiles(n, screenshots_coordinates, params): - f = params["f"] - zoom = params["zoom"] - output_directory = params["output_directory"] - n_width = params["n_width"] - n_height = params["n_height"] - screen_resolution = params["screen_resolution"] - mpps = params["mpps"] +def extract_tiles(n, screenshots_XY, params): + f = params['f'] + zoom = params['zoom'] + output_directory = params['output_directory'] + n_width = params['n_width'] + n_height = params['n_height'] - coords = screenshots_coordinates[n] + XY = screenshots_XY[n] if (os.path.exists(os.path.join(output_directory, "screenshots", f"{f}_{n}.jpg"))): # Open the source screenshot img = Image.open(os.path.join(output_directory, "screenshots", f"{f}_{n}.jpg")) - # Scale the image so that tiles are 256x256 - scale = screen_resolution / mpps - w, h = img.size - img = img.resize((int(w * scale), int(h * scale))) - # Compute the Web Mercator Projection position of the top left corner of the most centered tile - lat = coords[0] - lng = coords[1] - X_center, Y_center = deg_to_num(lat, lng, zoom) + X_center, Y_center = XY[0], XY[1] # Compute the position of the top left corner of the top left tile - start_x = w / 2 - n_width / 2 * 256 - start_y = h / 2 - n_height / 2 * 256 + start_x = img.width / 2 - n_width / 2 * 256 + start_y = img.height / 2 - n_height / 2 * 256 # Iterate on the grid for column in range(0, n_width): @@ -96,17 +91,44 @@ def extract_tiles(n, screenshots_coordinates, params): n += 1 else: - raise Exception(f"{os.path.join(output_directory, "screenshots", f"{f}_{n}.jpg")} missing") + raise Exception(f"{os.path.join(output_directory, 'screenshots', f'{f}_{n}.jpg')} missing") + +def merge_tiles(base_path, zoom, tile): + X = tile[0] + Y = tile[1] + positions = [(0, 0), (0, 1), (1, 0), (1, 1)] + + dst = Image.new('RGB', (256, 256), (0, 0, 0, 0)) + for i in range(0, 4): + if os.path.exists(os.path.join(base_path, str(zoom), str(2*X + positions[i][0]), f"{2*Y + positions[i][1]}.jpg")): + im = Image.open(os.path.join(base_path, str(zoom), str(2*X + positions[i][0]), f"{2*Y + positions[i][1]}.jpg")).resize((128, 128)) + else: + im = Image.new('RGB', (128, 128), (0, 0, 0, 0)) + dst.paste(im, (positions[i][0] * 128, positions[i][1] * 128)) + + if not os.path.exists(os.path.join(base_path, str(zoom - 1), str(X))): + try: + os.mkdir(os.path.join(base_path, str(zoom - 1), str(X))) + except FileExistsError: + pass + except Exception as e: + raise e + + dst.save(os.path.join(base_path, str(zoom - 1), str(X), f"{Y}.jpg"), quality=95) + def run(map_config): + global tot_futs, fut_counter + with open('configs/screen_properties.yml', 'r') as sp: screen_config = yaml.safe_load(sp) # Create output folders - output_directory = map_config["output_directory"] + output_directory = map_config['output_directory'] if not os.path.exists(output_directory): os.mkdir(output_directory) + skip_screenshots = False if not os.path.exists(os.path.join(output_directory, "screenshots")): os.mkdir(os.path.join(output_directory, "screenshots")) else: @@ -116,10 +138,10 @@ def run(map_config): os.mkdir(os.path.join(output_directory, "tiles")) # Compute the optimal zoom level - usable_width = screen_config["width"] - 200 # Keep a margin around the center - usable_height = screen_config["height"] - 200 # Keep a margin around the center + usable_width = screen_config['width'] - 400 # Keep a margin around the center + usable_height = screen_config['height'] - 400 # Keep a margin around the center - with open(map_config["boundary_file"], 'rt', encoding="utf-8") as bp: + with open(map_config['boundary_file'], 'rt', encoding="utf-8") as bp: # Read the config file doc = bp.read() k = kml.KML() @@ -134,7 +156,8 @@ def run(map_config): # Iterate over all the closed features in the kml file f = 1 for feature in features: - geo = sub_feature.geometry + ########### Take screenshots + geo = feature.geometry # Define the boundary rect around the area start_lat = geo.bounds[3] @@ -143,15 +166,14 @@ def run(map_config): end_lng = geo.bounds[2] # Find the zoom level that better approximates the provided resolution - screen_resolution = screen_config['geo_resolution'] - mpps_delta = [abs(compute_mpps((start_lat + end_lat) / 2, z) - screen_resolution) for z in range(0, 21)] + mpps_delta = [abs(compute_mpps((start_lat + end_lat) / 2, z) - map_config['mpps']) for z in range(0, 21)] zoom = mpps_delta.index(min(mpps_delta)) print(f"Feature {f} of {len(features)}, using zoom level {zoom}") # Find the maximum dimension of the tiles at the given resolution mpps = compute_mpps(end_lat, zoom) - d = 256 * mpps / screen_resolution + d = 256 * mpps / map_config['mpps'] n_height = math.floor(usable_height / d) n_width = math.floor(usable_width / d) @@ -163,35 +185,60 @@ def run(map_config): end_X, end_Y = deg_to_num(end_lat, end_lng, zoom) # Find all the X, Y coordinates inside of the provided area - screenshots_coordinates = [] + screenshots_XY = [] for X in range(start_X, end_X, n_width): for Y in range(start_Y, end_Y, n_height): lat, lng = num_to_deg(X, Y, zoom) p = Point(lng, lat) if p.within(wkt.loads(geo.wkt)): - screenshots_coordinates.append((lat, lng)) + screenshots_XY.append((X, Y)) - print(f"Feature {f} of {len(features)}, {len(screenshots_coordinates)} screenshots will be taken") + print(f"Feature {f} of {len(features)}, {len(screenshots_XY)} screenshots will be taken") # Start looping if not skip_screenshots: print(f"Feature {f} of {len(features)}, taking screenshots...") n = 0 - for coords in screenshots_coordinates: + for XY in screenshots_XY: # Making PUT request - #data = json.dumps({'lat': coords[0], 'lng': coords[1]}) - #r = requests.put('http://localhost:8080', data = data) + # If the number of rows or columns is odd, we need to take the picture at the CENTER of the tile! + lat, lng = num_to_deg(XY[0] + (n_width % 2) / 2, XY[1] + (n_height % 2) / 2, zoom) + data = json.dumps({'lat': lat, 'lng': lng, 'alt': 1350 + map_config['zoom_factor'] * (25000 - 1350)}) + r = requests.put('http://localhost:8080', data = data) + + geo_data = json.loads(r.text) time.sleep(0.1) - ## Take and save screenshot + # Take and save screenshot. The response to the put request contains data, among which there is the north rotation at that point. screenshot = pyautogui.screenshot() - screenshot.save(os.path.join(output_directory, "screenshots", f"{f}_{n}.jpg")) - printProgressBar(n + 1, len(screenshots_coordinates)) + # Scale the screenshot to account for Mercator Map Deformation + lat1, lng1 = num_to_deg(XY[0], XY[1], zoom) + lat2, lng2 = num_to_deg(XY[0] + 1, XY[1] + 1, zoom) + + deltaLat = abs(lat2 - lat1) + deltaLng = abs(lng2 - lng1) + + # Compute the height and width the screenshot should have + m_height = math.radians(deltaLat) * R * n_height + m_width = math.radians(deltaLng) * R * math.cos(math.radians(lat1)) * n_width + + # Compute the height and width the screenshot has + s_height = map_config['mpps'] * 256 * n_height + s_width = map_config['mpps'] * 256 * n_width + + # Compute the scaling required to achieve that + sx = s_width / m_width + sy = s_height / m_height + + # Resize, rotate and save the screenshot + screenshot.resize((int(sx * screenshot.width), int(sy * screenshot.height))).rotate(math.degrees(geo_data['northRotation'])).save(os.path.join(output_directory, "screenshots", f"{f}_{n}.jpg"), quality=95) + + printProgressBar(n + 1, len(screenshots_XY)) n += 1 - # Extract the tiles + ########### Extract the tiles if not os.path.exists(os.path.join(output_directory, "tiles", str(zoom))): os.mkdir(os.path.join(output_directory, "tiles", str(zoom))) @@ -201,15 +248,12 @@ def run(map_config): "output_directory": output_directory, "n_width": n_width, "n_height": n_height, - "screen_resolution": screen_resolution, - "mpps": mpps } # Extract the tiles with parallel thread execution with futures.ThreadPoolExecutor() as executor: print(f"Feature {f} of {len(features)}, extracting tiles...") - global tot_futs, fut_counter - futs = [executor.submit(extract_tiles, n, screenshots_coordinates, params) for n in range(0, len(screenshots_coordinates))] + futs = [executor.submit(extract_tiles, n, screenshots_XY, params) for n in range(0, len(screenshots_XY))] tot_futs = len(futs) fut_counter = 0 [fut.add_done_callback(done_callback) for fut in futs] @@ -219,6 +263,36 @@ def run(map_config): print(f"Feature {f} of {len(features)} completed!") f += 1 + ########### Assemble tiles to get lower zoom levels + for current_zoom in range(zoom, 8, -1): + Xs = [int(d) for d in listdir(os.path.join(output_directory, "tiles", str(current_zoom))) if isdir(join(output_directory, "tiles", str(current_zoom), d))] + existing_tiles = [] + for X in Xs: + Ys = [int(f.removesuffix(".jpg")) for f in listdir(os.path.join(output_directory, "tiles", str(current_zoom), str(X))) if isfile(join(output_directory, "tiles", str(current_zoom), str(X), f))] + for Y in Ys: + existing_tiles.append((X, Y)) + + tiles_to_produce = [] + for tile in existing_tiles: + if (int(tile[0] / 2), int(tile[1] / 2)) not in tiles_to_produce: + tiles_to_produce.append((int(tile[0] / 2), int(tile[1] / 2))) + + # Merge the tiles with parallel thread execution + with futures.ThreadPoolExecutor() as executor: + print(f"Merging tiles for zoom level {current_zoom - 1}...") + + if not os.path.exists(os.path.join(output_directory, "tiles", str(current_zoom - 1))): + os.mkdir(os.path.join(output_directory, "tiles", str(current_zoom - 1))) + + futs = [executor.submit(merge_tiles, os.path.join(output_directory, "tiles"), current_zoom, tile) for tile in tiles_to_produce] + tot_futs = len(futs) + fut_counter = 0 + [fut.add_done_callback(done_callback) for fut in futs] + [fut.result() for fut in futures.as_completed(futs)] + + + +