diff --git a/A_Star.py b/A_Star.py index 7e8aa7b..f5c876a 100644 --- a/A_Star.py +++ b/A_Star.py @@ -1,11 +1,13 @@ -from PIL import Image +from PIL import Image, ImageDraw import heapq from numpy import sqrt -from utils import show_warning, timeit, load_json +from utils import show_warning, load_json, subdivide_path, height_from_rect from ui import get_pathfinding_endpoints import FileManager as fm from tqdm import tqdm +SIZE_CONSTANT = fm.get_size_constant() +GRID = load_json(fm.ASTAR_JSONPATH) class Node: def __init__(self, x, y, parent=None): @@ -14,8 +16,8 @@ def __init__(self, x, y, parent=None): self.parent = parent - self.height = grid[y][x][2] - self.slope = grid[y][x][3] + self.height = GRID[y][x][2] + self.slope = GRID[y][x][3] self.g = 0 self.h = 0 @@ -53,7 +55,60 @@ def new_g(self, other) -> float: return eqn -@timeit +def is_valid_checkpoint(point): + x, y = point[0], point[1] + height = height_from_rect(x, y, GRID) + + ALLOWANCE = 300 # Change this to change the stringency of checkpoint validity + + for i in range(y, SIZE_CONSTANT): + # TODO Swap this with Elevation to be Rubric-Accurate + if height_from_rect(x, i, GRID) > (height + ALLOWANCE): + return False + + return True + + +def generate_comm_path(comm_path): + for index, point in tqdm(enumerate(comm_path), desc="Generating Checkpoints"): + x, y = point[0], point[1] + # If a point is already valid, then just leave it. + if is_valid_checkpoint(point): + continue + + # Define the bounds of the square, using max/min as point validity fail safes. + SEARCH_AREA = 50 + left_bound = max(0, x - SEARCH_AREA) + right_bound = min(SIZE_CONSTANT - 1, x + SEARCH_AREA) + top_bound = max(0, y - SEARCH_AREA) + bottom_bound = min(SIZE_CONSTANT - 1, y + SEARCH_AREA) + + # Loop through each square per each checkpoint. If it's valid, then replace it. + for i in range(left_bound, right_bound + 1): + for j in range(top_bound, bottom_bound + 1): + test_point = (i, j) + if is_valid_checkpoint(test_point): + comm_path[index] = test_point + else: + show_warning("Pathfinding Error", "No valid path with checkpoints was found.") + quit(1) + + + # Now we generate a new path. + final_path = [] + for i in range(len(comm_path) - 1): + (start_x, start_y), (goal_x, goal_y) = (comm_path[i][0], comm_path[i][1]), (comm_path[i+1][0], comm_path[i+1][1]) + global start_node + global goal_node + start_node: Node = Node(start_x, start_y) + goal_node: Node = Node(goal_x, goal_y) + + path_btw = astar() + final_path.extend(path_btw) + + return final_path, comm_path + + def astar(): nodes = [] @@ -81,7 +136,7 @@ def astar(): x2 = current.x + dx y2 = current.y + dy - if 0 <= x2 < len(grid) and 0 <= y2 < len(grid[0]): + if 0 <= x2 < len(GRID) and 0 <= y2 < len(GRID[0]): new_node = Node(x2, y2, current) heapq.heappush(nodes, new_node) @@ -89,60 +144,53 @@ def astar(): return None -def update_image(image_path: str, mvmt_path: list): +def update_image(image_path: str, mvmt_path: list, comm_path: list): path = image_path img = Image.open(path) - color = (255, 0, 0) for i in tqdm(range(len(mvmt_path)), desc="Updating image"): + color = (255, 0, 0) x = mvmt_path[i][0] y = mvmt_path[i][1] img.putpixel((x, y), color) - img.save(fm.images_path + "/AStar_Path.png") + if comm_path is not None: + for i in range(len(comm_path)): + draw = ImageDraw.Draw(img) + color = (0, 255, 0) + radius = 3 + draw.ellipse((comm_path[i][0] - radius, comm_path[i][1] - radius, + comm_path[i][0] + radius, comm_path[i][1] + radius), fill=color) + img.save(fm.ASTAR_PATH) -def div_10_points(final_path : list) -> list: - comm_points = [] - dist = round(len(final_path) / 11) - for i in range(11): - comm_points.append(final_path[i * dist]) - comm_points.pop(0) - return comm_points - - -def line_to_earth(x, y): - m = (y-1250)/(x-638) - b = -m*x + y - return int(m), int(b) - - -if __name__ == "__main__": - - (start_x, start_y), (goal_x, goal_y) = get_pathfinding_endpoints(fm.get_size_constant(), fm.images_path) - - grid = load_json(fm.data_path + "/AStarRawData.json") - - # Testing. To be removed later - # start_x, start_y = 0, 0 - # goal_x, goal_y = 100, 100 +def run_astar(): + (start_x, start_y), (goal_x, goal_y), checkpoints = \ + get_pathfinding_endpoints(fm.get_size_constant(), fm.images_path) + global start_node, goal_node start_node = Node(start_x, start_y) goal_node = Node(goal_x, goal_y) final_path = astar() + sub_10_path = None + + if checkpoints: + sub_10_path = subdivide_path(final_path) + sub_10_path.insert(0, (start_x, start_y)) + final_path, sub_10_path = generate_comm_path(sub_10_path) if final_path is not None: - update_image(fm.images_path + '/moon_surface_texture.png', final_path) + update_image(fm.TEXTURE_PATH, final_path, sub_10_path) else: show_warning("A* Pathfinding Error", "No Valid Path found between points.") - # For Relative Earth position Pathfinding Calculation --- - divided_points = div_10_points(final_path) - - m, b = line_to_earth(divided_points[0][0], divided_points[0][2]) - # Slope of a line spanning from a point to the relative position of earth. - print(f'y = {m}x + {b}') + if checkpoints: + print("Created Path with Communication Checkpoints") + else: + print("Created Path without Communication Checkpoints") +if __name__ == "__main__": + run_astar() \ No newline at end of file diff --git a/Artemis_ADC_Launcher.py b/Artemis_ADC_Launcher.py index 1466058..b585b58 100644 --- a/Artemis_ADC_Launcher.py +++ b/Artemis_ADC_Launcher.py @@ -32,8 +32,8 @@ print("Installed package: PySimpleGUI") run([sys.executable, '-m', 'pip', 'install', 'tqdm'], check=True) print("Installed package: tqdm") - run([sys.executable, '-m', 'pip', 'install', 'msgspec'], check=True) - print("Installed package: msgspec") + run([sys.executable, '-m', 'pip', 'install', 'orjson'], check=True) + print("Installed package: orjson") run([sys.executable, '-m', 'pip', 'install', 'seaborn'], check=True) print("Installed package: seaborn") diff --git a/Cartographer.py b/Cartographer.py index 58f0500..8ec9a8e 100644 --- a/Cartographer.py +++ b/Cartographer.py @@ -9,12 +9,8 @@ from time import time import random -max_z = fm.get_max_height() -CALCULATION_CONS = 255 / max_z -ONE_THIRD = 1 / 3 -TWO_THIRDS = 2 / 3 -SIZE_CONSTANT = fm.get_size_constant() +SIZE_CONSTANT = fm.get_size_constant() Image.MAX_IMAGE_PIXELS = None @@ -32,8 +28,8 @@ def sns_heatmap(arr, cmap, save): print(f'{save} created in {round(time()-start, 2)}s') -heights = get_specific_from_json(8, fm.data_path + "/AStarRawData.json") -slopes = get_specific_from_json(3, fm.data_path + "/AStarRawData.json") +heights = get_specific_from_json(8, fm.ASTAR_JSONPATH) +slopes = get_specific_from_json(3, fm.ASTAR_JSONPATH) def create_surface_texture(): @@ -45,7 +41,7 @@ def create_surface_texture(): for i in range(int(slopes[y][x])): color -= random.randint(2, 5) texture.putpixel((x, y), (color, color, color)) - texture.save(fm.images_path + "/moon_surface_texture.png") + texture.save(fm.TEXTURE_PATH) # Creates RAW_Heightmap, Slopemap, and Heightkey @@ -55,22 +51,22 @@ def draw_all(): sns_heatmap( arr=heights, cmap="gist_gray", - save=fm.images_path + '/RAW_heightmap.png' + save=fm.RAW_HEIGHTMAP_PATH ) # Creates Heightkey - #TODO Add Reduced Opacity Feature to Original Texture for this + # TODO Add Reduced Opacity Feature to Original Texture for this sns_heatmap( arr=heights, cmap='viridis', - save=fm.images_path + '/heightkey_surface.png' + save=fm.SURFACE_HEIGHTKEY_PATH ) # Creates Slopemap sns_heatmap( arr=slopes, cmap='inferno', - save=fm.images_path + '/slopemap.png' + save=fm.SLOPEMAP_PATH ) # Creates Surface Texture @@ -93,15 +89,15 @@ def draw_path(path, image, color): # Image Scaling for Faster Ursina Runs, as well as proper dimensions. proper_heightmap = resize( - image_path=fm.images_path + '/RAW_heightmap.png', + image_path=fm.RAW_HEIGHTMAP_PATH, new_name='processed_heightmap', scale=128, transpose=True ) - move(fm.images_path + '/processed_heightmap.png', getcwd() + '/processed_heightmap.png') + move(fm.images_path + fm.PROCESSED_HEIGHTMAP_PATH, getcwd() + '/processed_heightmap.png') proper_surface_texture = resize( - image_path='Data/Images/moon_surface_texture.png', + image_path=fm.TEXTURE_PATH, new_name='moon_surface_texture', scale=SIZE_CONSTANT, transpose=True diff --git a/DataProcessor.py b/DataProcessor.py index 76a4572..57befcb 100644 --- a/DataProcessor.py +++ b/DataProcessor.py @@ -1,8 +1,9 @@ import FileManager as fm from utils import file2list, get_x_coord, get_y_coord, \ get_z_coord, get_azimuth, get_elevation, timeit, \ - load_json + load_json, push_to_json from tqdm import tqdm +from math import radians, degrees # Get Constants @@ -10,7 +11,7 @@ LUNAR_RAD: float = fm.get_lunar_rad() # Legacy Constants -#DISTANCE_BETWEEN_POINTS: int = fm.get_dist_between_points() +# DISTANCE_BETWEEN_POINTS: int = fm.get_dist_between_points() # Creates Lists of each Data Type from the Paths Given. @@ -21,6 +22,7 @@ data = load_json(fm.INFO_JSONPATH) + @timeit def process_data(): """ @@ -48,16 +50,19 @@ def process_data(): radius: float = LUNAR_RAD + float(height) + latitude = radians(latitude) + longitude = radians(longitude) + x: float = float(get_x_coord(latitude, longitude, radius)) y: float = float(get_y_coord(latitude, longitude, radius)) z: float = float(get_z_coord(latitude, radius)) azi: float = get_azimuth(latitude, longitude) - elev: float = get_elevation(latitude, longitude, radius) + elev: float = get_elevation(latitude, longitude, x, y, z) xs.append(x), ys.append(y), zs.append(z), heights.append(height) - a_star_data_array.append([x, y, z, slope, azi, elev, latitude, longitude, height]) + a_star_data_array.append([x, y, z, slope, azi, elev, degrees(latitude), degrees(longitude), height]) min_x_: float = abs(min(xs)) min_y_: float = abs(min(ys)) @@ -99,8 +104,8 @@ def process_data(): array_to_be_written[j][i][0] = i array_to_be_written[j][i][1] = j - fm.push_to_json(fm.ASTAR_JSONPATH, array_to_be_written) - fm.push_to_json(fm.INFO_JSONPATH, data) + push_to_json(fm.ASTAR_JSONPATH, array_to_be_written) + push_to_json(fm.INFO_JSONPATH, data) if __name__ == "__main__": diff --git a/Display.py b/Display.py index 8cf5fe6..9caaae1 100644 --- a/Display.py +++ b/Display.py @@ -6,9 +6,10 @@ from ursina.prefabs.first_person_controller import FirstPersonController from random import choice from ursina.application import quit # USE THIS, NOT PYTHON quit() +from A_Star import run_astar # Window Declarations and Formatting ------------- -app = Ursina() +app = Ursina() #development_mode=False) window.set_title('Team Cartographer\'s ADC Application') window.cog_button.disable() window.exit_button.color = color.dark_gray @@ -24,26 +25,18 @@ # Load the Data try: AStarData = fm.load_json("Data/AStarRawData.json") -except FileNotFoundError: - show_error("Display Error", "Data Not Processed!") - quit() - - -try: infodata = fm.load_json("info.json") except FileNotFoundError: show_error("Display Error", "Data Not Processed!") quit() -# This File is meant for any Ursina Helper Methods/Classes - # Declaration of Entities ------------- # FirstPersonController Ground Plane ground_player = Entity( - model=Terrain(heightmap='processed_heightmap.png'), - #color = color.gray, + model=Terrain(heightmap=fm.PROCESSED_HEIGHTMAP_PATH), + # color = color.gray, texture='moon_surface_texture.png', collider='mesh', scale=(SIZE_CONSTANT*PLAYER_SCALE_FACTOR, Y_HEIGHT*PLAYER_SCALE_FACTOR, SIZE_CONSTANT*PLAYER_SCALE_FACTOR), @@ -53,8 +46,8 @@ # EditorCamera Ground Plane ground_perspective = Entity( - model=Terrain(heightmap='processed_heightmap.png'), - #color=color.gray, + model=Terrain(heightmap=fm.PROCESSED_HEIGHTMAP_PATH), + # color=color.gray, texture='moon_surface_texture.png', collider='box', scale=(SIZE_CONSTANT*3, Y_HEIGHT*EDITOR_SCALE_FACTOR, SIZE_CONSTANT*3), @@ -91,6 +84,7 @@ enabled=False ) +# TODO Fix Color Keys in Photoshop # Color Key Entity (Activates on Heightmap/Slopemap Toggle) color_key = Entity( parent=camera.ui, @@ -102,18 +96,18 @@ ) # Earth Entity (Scales to Player Position) -#earth = Entity( -# model='sphere', -# scale=(1000, 1000, 1000), -# position=(0, 600, -9000), -# texture='earth_texture.jpg', -# enabled=True -# ) +earth = Entity( + model='sphere', + scale=(500, 500, 500), + position=(0, 600, -9000), + texture='earth_texture.jpg', + enabled=True + ) # Slope and Height Toggle Image Pathing ------------- -slopemap = fm.parent_path + '/slopemap.png' -heightkey = fm.parent_path + '/heightkey_surface.png' +SLOPEMAP = fm.SLOPEMAP_PATH +HEIGHTKEY = fm.SURFACE_HEIGHTKEY_PATH # Textboxes ------------- @@ -124,10 +118,6 @@ t_azi = Text(text='Azimuth:', x=-.54, y=.28, scale=1.1, enabled=False) t_elev = Text(text='Elevation:', x=-.54, y=.23, scale=1.1, enabled=False) t_pos = Text(text='positional data', x=-0.883, y=0.185, z=0, enabled=False) -t_info = Text( - #text='M for Moon, L for Slopemap, H for Heightmap, Esc for Pause, X for Switch', - text='', - x=-.15, y=-.45, scale=1.1, color=color.black, enabled=False) # Player Interactable Declarations ------------- @@ -139,7 +129,7 @@ player = FirstPersonController(position=RESET_LOC, speed=500, mouse_sensitivity=Vec2(25, 25), enabled=False, gravity=False) player.cursor.scale = 0.00000000001 # Hides the Cursor from the App Display -track_list = ['Audio/track_1.mp3', 'Audio/track_2.mp3', 'Audio/track_3.mp3', 'Audio/bouyer_sonata_csharp_minor.mp3'] +track_list = ['Audio/track_1.mp3', 'Audio/track_2.mp3', 'Audio/track_3.mp3'] menu_track_list = ['Audio/bouyer_lonely_wasteland.mp3', 'pause_track.mp3'] run_music = Audio( # TODO Make a Tracklist Randomizer @@ -164,7 +154,7 @@ ) def play_run_music(): - #run_music.clip(choice(track_list)) + # run_music.clip(choice(track_list)) run_music.play() @@ -176,7 +166,7 @@ def input(key): player.set_position(RESET_LOC) # Slopemap Toggle - if key == 'l': + if key == '4': ground_player.texture = 'slopemap.png' ground_perspective.texture = 'slopemap.png' view_cam_player_loc.color = color.green @@ -184,20 +174,20 @@ def input(key): color_key.texture='slopeKey.png' # Heightkey Toggle - if key == 'h': + if key == '3': ground_player.texture = 'heightkey_surface.png' ground_perspective.texture = 'heightkey_surface.png' view_cam_player_loc.color = color.white color_key.enable() color_key.texture='heightKey.png' - if key == 'p': + if key == '2': ground_player.texture = 'AStar_Path.png' ground_perspective.texture = 'AStar_Path.png' color_key.disable() # Moon Texture Toggle (Default) - if key == 'm': + if key == '1': ground_player.texture = 'moon_surface_texture.png' ground_perspective.texture = 'moon_surface_texture.png' view_cam_player_loc.color = color.red @@ -222,7 +212,6 @@ def input(key): t_ht.disable() t_azi.disable() t_slope.disable() - t_info.disable() t_elev.disable() player.disable() vc.disable() @@ -241,6 +230,7 @@ def input(key): run_music.stop(destroy=False) + # Game Loop Update() Functions ------------- height_vals = ground_player.model.height_values def update(): @@ -258,7 +248,7 @@ def update(): # Positions x, y, z = player.position.x, player.position.y, player.position.z - player.y = terraincast(player.world_position, ground_player, height_vals) + 50 # Sets correct height + player.y = terraincast(player.world_position, ground_player, height_vals) + 60 # Sets correct height # Corrected X and Z values for Calculations # Note that in Ursina, 'x' and 'z' are the Horizontal (Plane) Axes, and 'y' is vertical. @@ -272,7 +262,7 @@ def update(): lat = float(latitude_from_rect(nx, nz, AStarData)) long = float(longitude_from_rect(nx, nz, AStarData)) slope = slope_from_rect(nx, nz, AStarData) - height = height_from_rect(nx, nz, AStarData, infodata) + height = height_from_rect(nx, nz, AStarData) azimuth, elevation = get_azi_elev(nx, nz, AStarData) # Updating Variables @@ -285,27 +275,21 @@ def update(): # Sprint Key if held_keys['left shift']: - player.speed = 1500 + player.speed = 1250 else: player.speed = 500 # Mini-Map Dot Positioning - mmsc: int = SIZE_CONSTANT * 10 # minimap size constant + mmsc: int = SIZE_CONSTANT * PLAYER_SCALE_FACTOR # minimap size constant mx, mz = (x/mmsc) + 0.5, (z/mmsc) - 0.5 mini_dot.position = (mx, mz, 0) - # Earth Positioning - #earth.position = (earth.x, 400*(elevation), earth.z) - - # Raycasting Tests - #hit_z = raycast(origin=player, direction=(0, 0, 1), distance=100000, traverse_target=ground_player, ignore=list(), debug=True) - #hit_x = raycast(origin=player, direction=(-1, 0, 0), distance=100000, traverse_target=ground_player, ignore=list(), debug=True) - #if hit_z.hit: - # print(f'Z: {hit_z}') - #if hit_x.hit: - # print(f'X: {hit_x}') - - + # Earth Positioning (haha funny number) + earth.position = (earth.x, 420+elevation*100, earth.z) + if view_cam_player_loc.enabled is True: + earth.z = -4000 + else: + earth.z = -9000 # Create Start Menu ------------- @@ -318,14 +302,13 @@ def start_game(): t_ht.enable() t_azi.enable() t_slope.enable() - t_info.enable() t_elev.enable() t_start_menu.disable() t_start_menu_creds.disable() minimap.enable() mini_dot.enable() t_pos.enable() - load_button.disable() + repath_button.disable() launch_button.disable() t_current_site.disable() volume_slider.disable() @@ -348,7 +331,6 @@ def on_unpause(): t_ht.enable() t_azi.enable() t_slope.enable() - t_info.enable() t_elev.enable() t_start_menu.disable() t_quit.disable() @@ -359,16 +341,10 @@ def on_unpause(): pause_music.stop(destroy=False) play_run_music() -def load_button_init(): - import tkinter as tk - from tkinter.filedialog import askdirectory - root = tk.Tk() - root.withdraw() - save_folder = askdirectory() - # This currently does nothing, just leave it here for saves later on. - #quit() # USE URSINA QUIT, NOT PYTHON QUIT. - #fm.load_save(save_folder) # Proof of Concept Shtuff - #print(save_folder) # This is Reachable. + +def repath_init(): + run_astar() + application.hot_reloader.reload_textures() # Start Menu Text and Buttons ------------- @@ -382,7 +358,7 @@ def main_menu_returner(): start_menu_music.stop(destroy=False) t_current_site.enable() launch_button.enable() - load_button.enable() + repath_button.enable() pause_button.disable() t_quit.disable() t_pause.disable() @@ -409,12 +385,12 @@ def sens_change(): t_current_site = Text(text=f"Currently Visiting: Shackleton", x=-0.2, y=0.1, scale=1.25, enabled=False) launch_button = Button(text="Visualize Site", color=color.gray, highlight_color=color.dark_gray, scale=(0.25, 0.06), x=0, y=0.0, enabled=False) -load_button = Button(text="Load A Site", color=color.dark_gray, highlight_color=Color(0.15, 0.15, 0.15, 1.0), scale=(0.25, 0.06), x=0, y=-0.08, enabled=False) +repath_button = Button(text="Re-Run Pathfinding", color=color.dark_gray, highlight_color=Color(0.15, 0.15, 0.15, 1.0), scale=(0.25, 0.06), x=0, y=-0.08, enabled=False) volume_slider = ThinSlider(text='Volume', value=0.15, dynamic=True, on_value_changed=volume_change, enabled=False, x=-0.23, y=-0.2) sens_slider = ThinSlider(text='Sensitivity', value=0.5, dynamic=True, on_value_changed=sens_change, enabled=False, x=-0.23, y=-0.27) launch_button.on_click = start_game -load_button.on_click = load_button_init +repath_button.on_click = repath_init # Pause Menu Text and Buttons ------------- diff --git a/FileManager.py b/FileManager.py index f2361c1..c71bbc9 100644 --- a/FileManager.py +++ b/FileManager.py @@ -7,53 +7,28 @@ from utils import show_info, file2list, load_json, push_to_json import ui +# Define Pathing +parent_path: str = os.getcwd() +data_path: str = os.path.join(parent_path, 'Data') +images_path: str = os.path.join(data_path, 'Images') +app_files_path: str = os.path.join(parent_path, 'App Files') + + +# DEFINE CONSTANTS INFO_JSONPATH = os.getcwd() + '/info.json' ASTAR_JSONPATH = 'Data/AStarRawData.json' +TEXTURE_PATH = images_path + '/moon_surface_texture.png' +RAW_HEIGHTMAP_PATH = images_path + '/RAW_heightmap.png' +PROCESSED_HEIGHTMAP_PATH = '/processed_heightmap.png' +SLOPEMAP_PATH = images_path + '/slopemap.png' +SURFACE_HEIGHTKEY_PATH = images_path + '/heightkey_surface.png' + +# Image Paths +ASTAR_PATH = images_path + "/AStar_Path.png" -# Currently Disabled as Saves are not a priority task. -''' -class Save: - def __init__(self, name): - latpath, longpath, heightpath, slopepath, dist_between_points = path_fetcher() - self.json_path, self.folder_path, self.data = self.write_json( - latitude_path=latpath, longitude_path=longpath, - height_path=heightpath, slope_path=slopepath, dist=int(dist_between_points), - size_constant=len(file2list(latpath)), player_pos=None, name=name) - - show_info("Save Success!",f'Saved {self.json_path}') - - #def save(self): - - def write_json(self, latitude_path : str, longitude_path : str, - height_path : str, slope_path : str, dist : str, - size_constant : str , player_pos : str, name : str): - - if not name: - name = 0 - - folder_path = os.getcwd() + f"/Save{name}" - if not os.path.exists(folder_path): - os.mkdir(os.getcwd() + f"/Save{name}") - - data : dict = { - "LATITUDE_PATH": latitude_path, - "LONGITUDE_PATH": longitude_path, - "HEIGHT_PATH": height_path, - "SLOPE_PATH": slope_path, - - "DIST_BETWEEN_POINTS": dist, - "SIZE_CONSTANT": size_constant, - "PLAYER_POSITION": player_pos - } - name : str = f"Save{name}" # Hardcoded to 0 for Testing. - jsonpath : str = os.path.join(folder_path, f'{name}.json') - with open(jsonpath, 'w') as f: - json.dump(data, f, ensure_ascii=False, indent=4) - return jsonpath, folder_path, data -''' # Sets up Json File. if not os.path.exists(os.getcwd() + '/info.json'): @@ -103,11 +78,6 @@ def get_slope_file_path() -> str: return data["SLOPE_PATH"].replace("\\", "/") -# Legacy -#def get_dist_between_points() -> int: -# return data["DIST_BETWEEN_POINTS"] - - def get_size_constant() -> int: return data["SIZE_CONSTANT"] @@ -145,12 +115,6 @@ def get_player_pos() -> tuple: return data["PLAYER_POSITION"] -# IMPORTANT PATHING -parent_path: str = os.getcwd() -data_path: str = os.path.join(parent_path, 'Data') -images_path: str = os.path.join(data_path, 'Images') -app_files_path: str = os.path.join(parent_path, 'App Files') - # Creates directories and sets 'info.json' variables only if FileManager.py is running. # Otherwise, only helper methods are accessible. if __name__ == '__main__': diff --git a/earth_texture.jpg b/earth_texture.jpg new file mode 100644 index 0000000..1674c17 Binary files /dev/null and b/earth_texture.jpg differ diff --git a/testbench.py b/testbench.py index 090307a..7ac15c6 100644 --- a/testbench.py +++ b/testbench.py @@ -1,10 +1,13 @@ from PIL import Image import random -from utils import load_json, timeit +from utils import load_json, timeit, push_to_json, file2list import os import numpy as np import cv2 import FileManager as fm +import msgspec +import orjson as oj + # from mpl_toolkits.mplot3d import Axes3D # import matplotlib.pyplot as plt # import numpy as np @@ -36,6 +39,5 @@ # plt.colorbar(p) # plt.show() - if __name__ == '__main__': pass diff --git a/ui.py b/ui.py index fab0649..322e7aa 100644 --- a/ui.py +++ b/ui.py @@ -55,13 +55,14 @@ def get_pathfinding_endpoints(SIZE_CONSTANT, IMAGES_PATH): sg.Button("Set", key="-StartIN-", enable_events=True) ], [ - sg.Text("Current Start Position:"), + sg.Text("Current Goal Position:"), sg.Input(default_text="None", key="-GoalOUT-", disabled=True), sg.Button("Set", key="-GoalIN-", enable_events=True) ], [ sg.Combo(["Moon Texture", "Slopemap", "Heightkey"], default_value="Moon Texture", enable_events=True, key="-Map-"), + sg.Checkbox("Add comm checkpoints?", default=False, key="-CommIN-"), sg.OK("Submit", key="-Submit-") ] ] @@ -141,12 +142,12 @@ def get_pathfinding_endpoints(SIZE_CONSTANT, IMAGES_PATH): if are_you_sure("Endpoint Submission", "Are you sure these are the points you want?"): if values["-StartOUT-"] != "None" and values["-GoalOUT-"] != "None": window.close() - return eval(values["-StartOUT-"]), eval(values["-GoalOUT-"]) + return eval(values["-StartOUT-"]), eval(values["-GoalOUT-"]), bool(values["-CommIN-"]) else: show_error("Incomplete Data Error", "Please select a start and end point") if __name__ == "__main__": # path_fetcher() - # get_pathfinding_endpoints() + # print(get_pathfinding_endpoints(1277, "C:/Users/Owner/PycharmProjects/NASA-ADC-App/Data/Images")) pass diff --git a/utils.py b/utils.py index 13255f9..d5f72e2 100644 --- a/utils.py +++ b/utils.py @@ -6,8 +6,7 @@ from math import atan2, sin, cos, asin, sqrt, radians, degrees from typing import Callable, Any from time import time -from msgspec.json import decode -import json +import orjson as oj import numpy as np from PIL import Image @@ -23,16 +22,18 @@ def timed(*args, **kw) -> Any: return timed -def load_json(json_path: str) -> dict: - with open(json_path, "rb") as f: - json_data = decode(f.read()) - return json_data +@timeit +def load_json(json_path: str): + with open(json_path, 'rb') as f: + data = oj.loads(f.read()) + return data @timeit -def push_to_json(json_path, json_data, custom_indent=4): - with open(json_path, 'w') as f: - json.dump(json_data, f, ensure_ascii=False, indent=custom_indent) +def push_to_json(json_path, json_data): + json_data = oj.dumps(json_data) + with open(json_path, 'wb') as f: + f.write(json_data) def file2list(path): @@ -96,21 +97,21 @@ def slope_from_rect(x: float, y: float, data) -> float: return data[x][y][3] -def height_from_rect(x: float, y: float, data, info_data) -> float: +def height_from_rect(x: float, y: float, data) -> float: height = data[x][y][8] return height def get_x_coord(lat, long, rad): # takes in degrees latitude and longitude - return rad * cos(radians(lat)) * cos(radians(long)) + return rad * cos(lat) * cos(long) def get_y_coord(lat, long, rad): - return rad * cos(radians(lat)) * sin(radians(long)) + return rad * cos(lat) * sin(long) def get_z_coord(lat, rad): - return rad * sin(radians(lat)) + return rad * sin(lat) def get_specific_from_json(index, jsonpath): @@ -120,7 +121,7 @@ def get_specific_from_json(index, jsonpath): return arr -# ONLY FOR USE WITH DISPLAY.PY +# ONLY FOR USE WITH DISPLAY.PY AND ASTAR.PY def get_azi_elev(x, y, data): row = data[x][y] return round(row[4], 5), round(row[5], 5) # azimuth and elevation, respectively @@ -137,8 +138,8 @@ def get_azimuth(moon_lat, moon_long): # True Lunar South Pole lunar_south_pole_lat, lunar_south_pole_long = radians(-89.54), radians(0) - moon_lat_radian = radians(moon_lat) - moon_long_radian = radians(moon_long) + moon_lat_radian = moon_lat + moon_long_radian = moon_long # Azimuth Calculation c1 = sin(moon_long_radian - lunar_south_pole_long) * cos(moon_lat_radian) @@ -149,7 +150,8 @@ def get_azimuth(moon_lat, moon_long): return degrees(azi) -def get_elevation(moon_lat, moon_long, moon_height): +def get_elevation(moon_lat, moon_long, moon_x, moon_y, moon_z): + # Latitude and Longitude are already in radians. # Elevation Calculation for DataProcessor.py # Earth Cartesian Position with respect to Lunar Fixed Frame at a single time instant # [X, Y, Z] = [361000, 0, –42100] km. @@ -158,19 +160,11 @@ def get_elevation(moon_lat, moon_long, moon_height): earth_y = 0 earth_z = -42100 - moon_lat_rad = radians(float(moon_lat)) - moon_long_rad = radians(float(moon_long)) - moon_radius = 1737.4 * 1000 + float(moon_height) - - moon_x = get_x_coord(moon_lat, moon_long, moon_radius) - moon_y = get_y_coord(moon_lat, moon_long, moon_radius) - moon_z = get_z_coord(moon_lat, moon_long) - dists = [earth_x - moon_x, earth_y - moon_y, earth_z - moon_z] range_ = sqrt((dists[0] ** 2) + (dists[1] ** 2) + (dists[2] ** 2)) - rz = dists[0] * cos(moon_lat_rad) * cos(moon_long_rad) + dists[1] * cos(moon_lat_rad) * sin(moon_long_rad) + dists[ - 2] * sin(moon_lat_rad) + rz = dists[0] * cos(moon_lat) * cos(moon_long) + dists[1] * cos(moon_lat) * sin(moon_long) + dists[ + 2] * sin(moon_lat) elev = asin(rz / range_) @@ -198,6 +192,43 @@ def resize(image_path: str, new_name: str, scale: float, transpose=False) -> str return path +def find_point_on_segment(p1, p2, ratio): + return (p1[0] + (p2[0] - p1[0]) * ratio, p1[1] + (p2[1] - p1[1]) * ratio) + + +def euclidean_distance(p1, p2): + return np.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) + +# For A* Checkpoint Calculation +def subdivide_path(points, sections=10): + # Calculate total length of the path + total_length = sum(euclidean_distance(points[i], points[i+1]) for i in range(len(points) - 1)) + section_length = total_length / sections + + # Iterate through the path and find the points at each section + section_points = [] + current_length = 0 + section = 1 + + for i in range(len(points) - 1): + segment_length = euclidean_distance(points[i], points[i + 1]) + remaining_length = section_length * section - current_length + + while remaining_length <= segment_length: + ratio = remaining_length / segment_length + point_on_segment = find_point_on_segment(points[i], points[i + 1], ratio) + section_points.append(point_on_segment) + + section += 1 + remaining_length = section_length * section - current_length + + current_length += segment_length + + + intify = lambda arr: list(map(lambda x: (int(x[0]), int(x[1])), arr)) + return intify(section_points) + + if __name__ == "__main__": # Do Nothing pass