Python code for Celestial Navigation in VSNG

Currently the newest simulator version. You can use this forum to share bugs or your ideas for the development.

Please support the development work, buy Vehicle Simulator or Virtual Sailor NG. Do not use or create piracy versions!
Pilot_76
Posts: 24
Joined: Thu Apr 29, 2021 11:42 am

Python code for Celestial Navigation in VSNG

Post by Pilot_76 »

ChatGPT and Bing AI helped me in creating a Python code to use Celestial Navigation in VSNG. The in-game "Telescope" showed me that any celestial body was off by at least 10°!! I compared it by using Stellarium which is a "sky simulator" both with the same exact position and of course I chose Stellarium's sky, but this might change in future updates in VSNG.

The script asks the user to type in the following:
-Retrieve a saved game's position or input a Custom position.
-System time or a custom time.
-Local pressure in mb.
-Local temperature in C°
-Loads Stellarium
Note: For some reason, the system time does not work, so just press "8" to set it manually once Stellarium loads. The "custom position" tells Stellarium to use the command lines "--latitude" and "--longitude" so it will load "....stellarium.exe --latitude -33.4543 --longitude 35.3422" for example by using decimal degrees format. Other formats are explained in Stellarium's user guide.

Once loaded, Stellarium will hide any coordinates displayed and the user just "shoots" a chosen celestial body then proceeds to the proper calculations manually or by any other means (electronic, app, calculator etc.).

The user must also edit config.ini in the [gui] section and must add the line "flag_show_location =false" in order to hide your coordinates (that is the main purpose!!) This is on page 318 of Stellarium's manual.

Below is the script to edit and save in Python, and just change the folder locations accordingly to your setup.

import os
import subprocess

# File locations
stellarium_exe = r'F:\Stellarium3\stellarium.exe'
config_file = r'C:\Users\Yugo\AppData\Roaming\Stellarium\config.ini'
saved_game_file = r'H:\SteamLibrary\steamapps\common\Virtual Sailor NG\situations\y.txt'

# Retrieve coordinates from saved game file
with open(saved_game_file, 'r') as f:
lines = f.readlines()
if len(lines) >= 6:
longitude_line = lines[4].split('\t')
latitude_line = lines[5].split('\t')
if len(longitude_line) >= 2 and len(latitude_line) >= 2:
longitude = longitude_line[0].strip()
latitude = latitude_line[0].strip()
# Hide the actual values from the user
hidden_longitude = "*" * len(longitude)
hidden_latitude = "*" * len(latitude)
print("Retrieved coordinates from y.txt:")
print("Longitude:", hidden_longitude)
print("Latitude:", hidden_latitude)
else:
longitude = None
latitude = None
else:
longitude = None
latitude = None

# Custom menu
print('Please select:')
print('Saved game position (1)')
print('Custom position (2)')
position_choice = input()

if position_choice == '1':
# Use saved game position
custom_longitude = longitude
custom_latitude = latitude
elif position_choice == '2':
# Use custom position
custom_longitude = input('Enter custom longitude: ')
custom_latitude = input('Enter custom latitude: ')

print('\nSelect date and time:')
print('System date-UTC (1)')
print('Custom date-UTC (2)')
date_time_choice = input()

if date_time_choice == '1':
# Use system date-UTC
sky_date = ''
sky_time = ''
elif date_time_choice == '2':
# Use custom date-UTC
sky_date = input('Enter custom date (yyyymmdd): ')
sky_time = input('Enter custom time (hh:mm:ss): ')

# Pressure, temperature, and altitude selection
pressure = input('\nType in for Pressure (mbar): ')
temperature = input('Type in for temperature (C°): ')
altitude = input("Type in Observer's Altitude (m): ")

# Update pressure and temperature in config.ini file
with open(config_file, 'r') as f:
lines = f.readlines()
lines[433] = f'pressure_mbar = {pressure}\n'
lines[434] = f'temperature_C = {temperature}\n'
with open(config_file, 'w') as f:
f.writelines(lines)

# Launch Stellarium with specified parameters
cmd = [stellarium_exe, f'--latitude={custom_latitude}', f'--longitude={custom_longitude}', f'--altitude={altitude}']
if sky_date:
cmd.append(f'--sky-date={sky_date}')
if sky_time:
cmd.append(f'--sky-time={sky_time}')
subprocess.run(cmd)
hjsmuc
Posts: 38
Joined: Wed Mar 10, 2021 1:21 pm

Re: Python code for Celestial Navigation in VSNG

Post by hjsmuc »

That sounds great, however it goes a bit beyond my technical comprehension. Would you mind explaining it by an in-game example? Also, any explanation of how you asked AI to write that script? I would like to try something similar with an offline dead reckoning script. Thanks.
Pilot_76
Posts: 24
Joined: Thu Apr 29, 2021 11:42 am

Re: Python code for Celestial Navigation in VSNG

Post by Pilot_76 »

I saved a situation called "y" (I'm just that lazy) and a file called "y.txt" is created in the "situations" subfolder. Lines 5 and 6 are the cameras' coordinates, irrespective of the Vehicle you are using. That data is what the script imports into Stellarium.

Below is the instruction text I typed in and pasted it into ChatGPT and Bing AI. Both corrected each other's code until I got it right except the "System's date and time" but I don't mind about it anymore. Usually I see these AI engines as kids that know how to code but lack social skills, so I try to be very specific about the details and what I want the code to do.

"This is a written instruction into writing a script in Python.

The objective is to retrieve a set of coordinates from a saved game file from the game “Virtual Sailor NG” and to load Stellarium app with those coordinates by using a custom menu and command lines that Stellarium.exe will load. Also, there is a minor change in one of Stellarium’s file (config.ini).

Data:

Stellarium.exe location: F:\Stellarium3\stellarium.exe

Stellarium’s configuration file is at: C:\Users\Yugo\AppData\Roaming\Stellarium\config.ini

Saved game location: H:\SteamLibrary\steamapps\common\Virtual Sailor NG\situations\y.txt

Below is a part of the y.txt file:


// VS_NG Situation File

W060S10 [scenery_name]
-46.651440 [camera_east]
-27.767967 [camera_north]

Please note that the coordinates (in this case -46.651440 and -27.767967) are respectively lines 5 and 6. These are the coordinates to be retrieved. Line 5 is longitude and line 6 is latitude. These coordinates will change each time the game is saved.

Stellarium.exe command lines: These are typed after stellarium.exe.

--latitude (loads a custom latitude).
--longitude (loads a custom longitude).
--altitude (sets the altitude of the observer in meters).
--sky-date (The initial date in yyymmdd format).
--sky-time (The initial time in hh:mm:ss format).

For example:
stellarium.exe –latitude -46 –longitude -26 –sky-date 20001010

This will load stellarium at coordinates 46°S 26°W on October 10th, 2000.
Since the time was not set, the default time will be system’s time.

Now, below are the custom menu I need once the script is loaded.

“Please select:
Saved game position (1)
Custom position (2)”

Now the date and time section below:

“Select date and time:
System date-UTC (1)
Custom date-UTC (2)”

Next is the Pressure and temperature selection:

“Type in for Pressure:”
“Type in for temperature (C°):”

To change the pressure and temperature parameters, the config.ini has to be altered.
Below is part of the config.ini file in quotes:

“[landscape]
atmosphere_fade_duration = 0.5
atmosphere_model_path = .\atmosphere\default
atmospheric_extinction_coefficient = 0.12999999523162842
draw_sun_after_atmosphere = false
early_solar_halo = false
flag_atmosphere = true
flag_enable_illumination_layer = true
flag_enable_labels = false
flag_fog = true
flag_landscape = true
flag_landscape_sets_location = false
flag_landscape_sets_minimal_brightness = true
flag_minimal_brightness = true
flag_polyline_only = false
label_color = 0.2,0.8,0.2
label_font_size = 18
minimal_brightness = 0.10000000149011612
polyline_thickness = 1
pressure_mbar = 1025
temperature_C = 30
turbidity = 5
use_T_from_k = false”


Lines 434 and 435 in the config.ini file will give:

pressure_mbar = 1025
temperature_C = 30

Instruct the script to change these values accordingly per user typed input. The location of config.ini was already given.

There is also the command line “--altitude” that sets the observer’s altitude in meters.

Once this has been set, the script loads stellarium.exe with all those parameters."

Once the code is written, I transfer that into Python's IDLE app and save it, then hit F5 to run it. Here: https://youtu.be/RS25FMLvPwk
hjsmuc
Posts: 38
Joined: Wed Mar 10, 2021 1:21 pm

Re: Python code for Celestial Navigation in VSNG

Post by hjsmuc »

Wow, thanks a lot for taking the time to explain in that detail. Great job.
Pilot_76
Posts: 24
Joined: Thu Apr 29, 2021 11:42 am

Re: Python code for Celestial Navigation in VSNG

Post by Pilot_76 »

Could you tell me more about the DR script you previously mentioned? Sounds interesting. I do the navigation in Opencpn and one of its plugins generates DR waypoints based either on speed or time.
hjsmuc
Posts: 38
Joined: Wed Mar 10, 2021 1:21 pm

Re: Python code for Celestial Navigation in VSNG

Post by hjsmuc »

I am at a very early state of tinkering with that. The idea is to read the VS situation file, take the necessary data to start a dead reckoning for a future time and then rewrite the position information back into the situation file. This would allow to run the ship offline during the night or other extended periods and be able to resume the simulation at any given time. It would obviously not be precise, but a start to do better long distance journeys. Currently, I am trying to find out where in the situation file the vessel speed and course is saved, the data in the file does not to seem to match the ship's movements.
Pilot_76
Posts: 24
Joined: Thu Apr 29, 2021 11:42 am

Re: Python code for Celestial Navigation in VSNG

Post by Pilot_76 »

Below is an extract from my y.txt file:

Trimaran Jungle [vehicle_name]
-49.009941 [vehicle_east] <= Current Longitude
-38.508617 [vehicle_north] <= Current Latitude
-0.86 [vehicle_alt]
2.57 [vehicle_beta]
9.02 [vehicle_speed] <= SPEED
0.81 [vehicle_rpm]
33469.62 [vehicle_fuel]
1.00 [vehicle_reefb]
1.00 [vehicle_reefs]
0.21 [vehicle_beta_sb]
0.21 [vehicle_beta_ss]
10.00 [vehicle_apspeed]
-3.72 [vehicle_apheading] <= Autopilot HEADING
Pilot_76
Posts: 24
Joined: Thu Apr 29, 2021 11:42 am

Re: Python code for Celestial Navigation in VSNG

Post by Pilot_76 »

Just engage the autopilot then save the situation. The current heading will show up in the file.
hjsmuc
Posts: 38
Joined: Wed Mar 10, 2021 1:21 pm

Re: Python code for Celestial Navigation in VSNG

Post by hjsmuc »

In my situation file the speed is given as 3.88 while the real speed is 7.5, and the heading is given as -2.97 while the heading in game is 190. How are these values to be translated? It seems that the speed in the file is 50% of the real speed and that one degree of heading is multiplied with 0,017444 until 180 degrees and thereafter it is counted backwards until 0 with a minus sign before the value. I have no clue why this is done and I suppose it will make it more complicated to write a script using these formulas. But maybe I am missing something.
Pilot_76
Posts: 24
Joined: Thu Apr 29, 2021 11:42 am

Re: Python code for Celestial Navigation in VSNG

Post by Pilot_76 »

You are correct. The Speed and heading is a bit confusing, so I gave up in trying to understand why it is written in that way.

The thing is that I got creative and ChatGPT wrote this script that simulates DR movement.

You may try it yourself:

import os
import random
import math
from datetime import datetime

def get_ship_coordinates(file_path, ship_name):
with open(file_path, 'r') as file:
lines = file.readlines()
for i in range(len(lines)):
if lines.startswith(ship_name):
longitude = float(lines[i+1].split('\t')[0])
latitude = float(lines[i+2].split('\t')[0])
return longitude, latitude
return None, None

def calculate_dr_position(longitude, latitude, heading, speed, days, hours, uncertainty):
# Convert heading from degrees to radians
heading_rad = math.radians(heading)

# Calculate the distance traveled in nautical miles
distance = speed * (days * 24 + hours)

# Apply uncertainty
if uncertainty:
heading_rad += random.uniform(math.radians(1), math.radians(2.5))
speed *= random.uniform(1.01, 1.10)

# Calculate the new latitude and longitude using the dead reckoning formulas
new_latitude = latitude + (distance / 60) * math.cos(heading_rad)
new_longitude = longitude + (distance / 60) * math.sin(heading_rad)

return new_longitude, new_latitude

def update_saved_game(file_path, ship_name, new_longitude, new_latitude):
with open(file_path, 'r+') as file:
lines = file.readlines()
for i in range(len(lines)):
if lines.startswith(ship_name):
lines[i+1] = f'{new_longitude}\t[vehicle_east]\n'
lines[i+2] = f'{new_latitude}\t[vehicle_north]\n'
file.seek(0)
file.writelines(lines)
file.truncate()
break

# Prompt the user for inputs
print("Welcome to DR Script by Python. This will change data in a saved game file with updated DR coordinates.")

file_name = input("\nChoose the saved '.txt' file: ")
ship_name = input("\nYour ship's name? ")
heading = int(input("\nLast known ship's heading (0 to 359): "))
speed = float(input("\nLast known ship's speed in knots: "))
date = input("\nLast known ship's date in UTC (yyyymmdd format): ")
time = input("\nLast known ship's time in UTC (hhmm 24-hour format): ")
uncertainty_input = input("\nAdd a random DR uncertainty (Y/N)? ")

# Prepare file path
file_path = os.path.join(r'H:\SteamLibrary\steamapps\common\Virtual Sailor NG\situations', f'{file_name}.txt')

# Retrieve ship's coordinates from the saved game file
longitude, latitude = get_ship_coordinates(file_path, ship_name)
if longitude is None or latitude is None:
print("\nShip's name not found in the specified file.")
exit()

# Get the current system time
current_datetime = datetime.utcnow()
current_date = current_datetime.strftime('%Y%m%d')
current_time = current_datetime.strftime('%H%M')

# Convert the user input date and time to integers
user_date = int(date)
user_time = int(time)

# Calculate the time duration between the user input time and the current system time
date_difference = int(current_date) - user_date
time_difference = int(current_time) - user_time

# Handle the case where the time difference might cross the midnight boundary
if time_difference < 0:
date_difference -= 1
time_difference += 2400

# Calculate the total time difference in days and hours
days = date_difference
hours = time_difference // 100

# Print the duration for which the ship has been traveling in DR mode
print(f"\nThe ship has been traveling in DR mode for {days} days and {hours} hours.")

# Perform DR position calculation
uncertainty = uncertainty_input.upper() == 'Y'
new_longitude, new_latitude = calculate_dr_position(longitude, latitude, heading, speed, days, hours, uncertainty)

# Update the saved game file with the new coordinates
if new_longitude is not None and new_latitude is not None:
update_saved_game(file_path, ship_name, new_longitude, new_latitude)
print("\nDR position has been updated. Enjoy!")
else:
print("\nFailed to calculate the new DR position.")

# Wait for user input before quitting
input("\nPress any key to quit.")
Last edited by Pilot_76 on Wed Jul 05, 2023 5:37 pm, edited 1 time in total.
Pilot_76
Posts: 24
Joined: Thu Apr 29, 2021 11:42 am

Re: Python code for Celestial Navigation in VSNG

Post by Pilot_76 »

Here's a description of what the updated code does:

1. The script begins by importing necessary modules such as `os`, `random`, `math`, and `datetime`.

2. It defines three functions:
- `get_ship_coordinates(file_path, ship_name)`: This function reads a file and retrieves the longitude and latitude coordinates of a ship based on the ship's name.
- `calculate_dr_position(longitude, latitude, heading, speed, days, hours, uncertainty)`: This function calculates the new longitude and latitude coordinates for the dead reckoning (DR) position based on the given inputs.
- `update_saved_game(file_path, ship_name, new_longitude, new_latitude)`: This function updates the saved game file with the new coordinates of the ship.

3. The script prompts the user for various inputs, including the saved file name, ship name, ship's heading, speed, date, time, and whether to add uncertainty to the DR calculations.

4. It constructs the file path based on the provided file name.

5. The script calls the `get_ship_coordinates` function to retrieve the current ship's coordinates from the saved game file.

6. It retrieves the current system's date and time using the `datetime.utcnow()` function and compares it with the user's input date and time. It calculates the time difference in days and hours.

7. The script prints a message indicating the duration for which the ship has been traveling in DR mode.

8. It calls the `calculate_dr_position` function with the retrieved ship's coordinates, heading, speed, time duration, and uncertainty parameters to calculate the new DR position.

9. If the new DR position is successfully calculated, the script calls the `update_saved_game` function to update the saved game file with the new coordinates.

10. Finally, the script prints a message indicating whether the update was successful or not, and waits for user input before quitting.

The updated code enables users to provide their ship's information, calculates the time duration for the DR position based on the user's input and the current system time, and updates the saved game file with the new coordinates.
hjsmuc
Posts: 38
Joined: Wed Mar 10, 2021 1:21 pm

Re: Python code for Celestial Navigation in VSNG

Post by hjsmuc »

That's amazing, I will try it out later. I spent some time today to try my own script, but compared to yours mine is quite crappy. I didn't manage to tell it to properly consider longitude and latitude when calculating the new position. Please don't laugh too loud.

import os
import math
from geopy import Point
from geopy.distance import geodesic

file_path = r"G:\Virtual Sailor\situations\test.txt"
line_number_heading = 48
line_number_longitude = 36
line_number_latitude = 37
line_number_speed = 40

with open(file_path) as f:
lines = f.readlines()
y_str = lines[line_number_heading - 1].strip().split("\t")[0]
y = float(y_str)
if y >= 0:
HDG = y / 0.01744444
else:
HDG = y / 0.01744444 + 360

longitude_str = lines[line_number_longitude - 1].strip().split("\t")[0].split(" ")[0]
longitude = float(longitude_str)
if longitude < 0:
longitude_direction = "West"
longitude *= -1
longitude_sign = "-"
else:
longitude_direction = "East"
longitude_sign = ""

latitude_str = lines[line_number_latitude - 1].strip().split("\t")[0]
latitude = float(latitude_str)
if latitude < 0:
latitude_direction = "South"
latitude *= -1
latitude_sign = "-"
else:
latitude_direction = "North"
latitude_sign = ""

speed_str = lines[line_number_speed - 1].strip().split("\t")[0]
speed_knots = float(speed_str) * 2
speed_nautical_miles_per_hour = speed_knots * 1

HDG_rounded = round(HDG)

print(f"The current position of the ship is {latitude_sign}{latitude:.6f} {latitude_direction}, {longitude_sign}{longitude:.6f} {longitude_direction}.")
print(f"The current heading of the ship is {HDG_rounded} degrees.")
print(f"The current speed of the ship is {speed_nautical_miles_per_hour:.2f} nautical miles per hour.")

projected_speed_knots = float(input("Enter the projected speed of the ship in knots: "))
projected_course_degrees_true_north = float(input("Enter the projected course of the ship in degrees true north: "))
wind_speed_knots = float(input("Enter the current wind speed in knots: "))
wind_direction_degrees_true_north = float(input("Enter the current wind direction in degrees true north: "))
travel_time_hours = float(input("Enter the travel time in hours: "))

point = Point(latitude, longitude)
projected_distance_latitude_decimal_degrees_per_hour = projected_speed_knots * math.sin(math.radians(projected_course_degrees_true_north)) / 60
projected_distance_longitude_decimal_degrees_per_hour_at_equator = projected_speed_knots * math.cos(math.radians(projected_course_degrees_true_north)) / (60 * math.cos(math.radians(latitude)))
projected_latitude_decimal_degrees_new_position, projected_longitude_decimal_degrees_new_position, _= geodesic(point, (latitude, longitude)).destination(point=(latitude + projected_distance_latitude_decimal_degrees_per_hour * travel_time_hours, longitude + projected_distance_longitude_decimal_degrees_per_hour_at_equator * travel_time_hours), bearing=projected_course_degrees_true_north)

if projected_longitude_decimal_degrees_new_position < 0:
projected_longitude_direction = "West"
projected_longitude_decimal_degrees_new_position *= -1
projected_longitude_sign = "-"
else:
projected_longitude_direction = "East"
projected_longitude_sign = ""

if projected_latitude_decimal_degrees_new_position < 0:
projected_latitude_direction = "South"
projected_latitude_decimal_degrees_new_position *= -1
projected_latitude_sign = "-"
else:
projected_latitude_direction = "North"
projected_latitude_sign = ""

print(f"The projected position of the ship after {travel_time_hours} hours is {projected_latitude_sign}{abs(projected_latitude_decimal_degrees_new_position):.6f} {projected_latitude_direction}, {projected_longitude_sign}{abs(projected_longitude_decimal_degrees_new_position):.6f} {projected_longitude_direction}.")
Pilot_76
Posts: 24
Joined: Thu Apr 29, 2021 11:42 am

Re: Python code for Celestial Navigation in VSNG

Post by Pilot_76 »

This is how it looks like:

https://youtu.be/AdPKpVPy98g
Pilot_76
Posts: 24
Joined: Thu Apr 29, 2021 11:42 am

Re: Python code for Celestial Navigation in VSNG

Post by Pilot_76 »

BTW I didn't do anything at all. ChatGPT wrote the entire code. I just kept prompting it until it got things right. :lol:
hjsmuc
Posts: 38
Joined: Wed Mar 10, 2021 1:21 pm

Re: Python code for Celestial Navigation in VSNG

Post by hjsmuc »

I guess you have a better understanding of ChatGPT's character. Do I understand correctly that VS has to run to use your script? Mine was intended to work without VS.
Pilot_76
Posts: 24
Joined: Thu Apr 29, 2021 11:42 am

Re: Python code for Celestial Navigation in VSNG

Post by Pilot_76 »

The script retrieves a position from a specific ship from a specific situation. My saved file has the maximum of 21 sailboats (or 22?) and I am in control of one of them. I just used VSNG's script to order the other vessels to sail to a specific coordinates and that created the problem that Python's script must now identify the correct ship to edit its position, otherwise it will add or mess up with the other sailboats. In order for the script to work properly, the ship's name must be addressed, hence the ship's name prompt.

Regarding your question, no, VS does not need to be running in order to be in "DR mode". Just keep running the script and every time the position will be updated. For example, you just saved the situation. Then you turn off the pc. Next time you turn it on and fire the script, it will update the position again. In fact, the script does not even know if VS is running or not. But I don't see the fun in that. That I will personally use when in command of a large cargo ship or cruiser, where the ship does not stop if the Skipper goes to sleep. When on a sailboat, which is my case now, I simply lower the sails, turn off the cabin lights and save the situation. Unless I have an "autopilot virtual buddy" that will take the helm until early morning, then yes, I will use this script. Until this script idea, I used to leave the pc on the whole night. The script does not inform you the current position, though. You must either load the game and check the gps or load the situation file and read the coordinates there.
hjsmuc
Posts: 38
Joined: Wed Mar 10, 2021 1:21 pm

Re: Python code for Celestial Navigation in VSNG

Post by hjsmuc »

SOLVED: Not sure I get it. When I run your script I get the error IndentationError: expected an indented block after function definition on line 6
I modified the location of the VS situations and also the name of my last situation. Also tried with just entering the path to the VS situations.

I ran your script another time through ChatGPT and it changed a few indentations and now it works fine. I will keep playing with both scripts, trying to get some more automation into it.
Pilot_76
Posts: 24
Joined: Thu Apr 29, 2021 11:42 am

Re: Python code for Celestial Navigation in VSNG

Post by Pilot_76 »

Sorry for the late reply. Life got in the way.

Post here the updated script from chatgpt I will compare them both.
hjsmuc
Posts: 38
Joined: Wed Mar 10, 2021 1:21 pm

Re: Python code for Celestial Navigation in VSNG

Post by hjsmuc »

Here it it is, that's yours after one more run with ChatGPT. I'll send mine later.

import math
import random
import os
from datetime import datetime

def get_ship_coordinates(file_path, ship_name):
with open(file_path, 'r') as file:
lines = file.readlines()
for i in range(len(lines)):
if lines.startswith(ship_name):
longitude = float(lines[i+1].split('\t')[0])
latitude = float(lines[i+2].split('\t')[0])
return longitude, latitude
return None, None

def calculate_dr_position(longitude, latitude, heading, speed, days, hours, uncertainty):
# Convert heading from degrees to radians
heading_rad = math.radians(heading)

# Calculate the distance traveled in nautical miles
distance = speed * (days * 24 + hours)

# Apply uncertainty
if uncertainty:
heading_rad += random.uniform(math.radians(1), math.radians(2.5))
speed *= random.uniform(1.01, 1.10)

# Calculate the new latitude and longitude using the dead reckoning formulas
new_latitude = latitude + (distance / 60) * math.cos(heading_rad)
new_longitude = longitude + (distance / 60) * math.sin(heading_rad)

return new_longitude, new_latitude

def update_saved_game(file_path, ship_name, new_longitude, new_latitude):
with open(file_path, 'r+') as file:
lines = file.readlines()
for i in range(len(lines)):
if lines.startswith(ship_name):
# Round the new longitude to six decimal places
new_longitude_rounded = round(new_longitude, 6)
lines[i+1] = f'{new_longitude_rounded}\t[vehicle_east]\n'
lines[i+2] = f'{new_latitude}\t[vehicle_north]\n'
file.seek(0)
file.writelines(lines)
file.truncate()
break


# Prompt the user for inputs
print("Welcome to DR Script by Python. This will change data in a saved game file with updated DR coordinates.")

file_name = input("\nChoose the saved '.txt' file: ")
ship_name = input("\nYour ship's name? ")
heading = int(input("\nLast known ship's heading (0 to 359): "))
speed = float(input("\nLast known ship's speed in knots: "))
date = input("\nLast known ship's date in UTC (yyyymmdd format): ")
time = input("\nLast known ship's time in UTC (hhmm 24-hour format): ")
uncertainty_input = input("\nAdd a random DR uncertainty (Y/N)? ")

# Prepare file path
file_path = os.path.join(r'G:\Virtual Sailor\situations', f'{file_name}.txt')

# Retrieve ship's coordinates from the saved game file
longitude, latitude = get_ship_coordinates(file_path, ship_name)
if longitude is None or latitude is None:
print("\nShip's name not found in the specified file.")
exit()

# Get the current system time
current_datetime = datetime.utcnow()
current_date = current_datetime.strftime('%Y%m%d')
current_time = current_datetime.strftime('%H%M')

# Convert the user input date and time to integers
user_date = int(date)
user_time = int(time)

# Calculate the time duration between the user input time and the current system time
date_difference = int(current_date) - user_date
time_difference = int(current_time) - user_time

# Handle the case where the time difference might cross the midnight boundary
if time_difference < 0:
date_difference -= 1
time_difference += 2400

# Calculate the total time difference in daysand hours
days = date_difference
hours = time_difference // 100

# Print the duration for which the ship has been traveling in DR mode
print(f"\nThe ship has been traveling in DR mode for {days} days and {hours} hours.")


# Perform DR position calculation
uncertainty = uncertainty_input.upper() == 'Y'
new_longitude, new_latitude = calculate_dr_position(longitude, latitude, heading, speed, days, hours, uncertainty)

# Update the saved game file with the new coordinates
if new_longitude is not None and new_latitude is not None:
update_saved_game(file_path, ship_name, new_longitude, new_latitude)
print("\nDR position has been updated. Enjoy!")
else:
print("\nFailed to calculate the new DR position.")

# Wait for user input before quitting
input("\nPress any key to quit.")
hjsmuc
Posts: 38
Joined: Wed Mar 10, 2021 1:21 pm

Re: Python code for Celestial Navigation in VSNG

Post by hjsmuc »

Here is what ChatGPT managed to do after some heated discussion, give it a try. Happy to hear what you think and please point out bugs or omissions.

import os
import math
from math import radians, degrees, sin, cos, atan2, sqrt


def dead_reckoning(lat, lon, course, speed, hours, wind_speed, wind_direction, current_speed, current_direction):
# Convert course, wind direction, and current direction to radians
course_rad = math.radians(course)
wind_direction_rad = math.radians(wind_direction)
current_direction_rad = math.radians(current_direction)

# Convert speed from knots to nautical miles per hour
speed_nm = speed * 1

# Calculate distance traveled in nautical miles
distance_nm = speed_nm * hours

# Calculate the components of the wind vector
wind_speed_x = wind_speed * math.cos(wind_direction_rad)
wind_speed_y = wind_speed * math.sin(wind_direction_rad)

# Calculate the components of the current vector
current_speed_x = current_speed * math.cos(current_direction_rad)
current_speed_y = current_speed * math.sin(current_direction_rad)

# Calculate the components of the resulting vector
result_speed_x = speed_nm * math.cos(course_rad) + wind_speed_x + current_speed_x
result_speed_y = speed_nm * math.sin(course_rad) + wind_speed_y + current_speed_y

# Calculate the resulting speed and course
result_speed = math.sqrt(result_speed_x ** 2 + result_speed_y ** 2)
result_course = math.degrees(math.atan2(result_speed_y, result_speed_x))

# Convert the resulting course to the range of 0-360 degrees
result_course = (result_course + 360) % 360

# Calculate change in latitude and longitude
delta_lat = (distance_nm / 60.0) * math.cos(math.radians(result_course))
delta_lon = (distance_nm / 60.0) * math.sin(math.radians(result_course))

# Calculate new latitude and longitude
new_lat = lat + delta_lat
new_lon = lon + delta_lon

return new_lat, new_lon


def format_coordinate(coordinate, is_longitude):
if is_longitude:
direction = "W" if coordinate < 0 else "E"
else:
direction = "S" if coordinate < 0 else "N"
return abs(coordinate), direction


def read_waypoints(situation_file_path):
waypoints = []
found_start = False

with open(situation_file_path, "r") as f:
lines = f.readlines()
waypoint_counter = 0

for line in lines:
line = line.strip()

if "1" in line and "[vehicle_own]" in line:
found_start = True
continue

if "1" in line and "[vehicle_next_position]" in line:
break

if found_start and line.endswith("[vehicle_waypoint]"):
waypoint_parts = line.split("\t")
if len(waypoint_parts) >= 2:
waypoint_counter += 1
longitude = -float(waypoint_parts[0]) # Negative values indicate West
latitude = float(waypoint_parts[1])
formatted_longitude, longitude_direction = format_coordinate(longitude, is_longitude=True)
formatted_latitude, latitude_direction = format_coordinate(latitude, is_longitude=False)
waypoint = (
chr(ord('A') + waypoint_counter - 1), formatted_longitude, longitude_direction,
formatted_latitude, latitude_direction
)
waypoints.append(waypoint)

return waypoints





def calculate_course_to_waypoint(ship_longitude, ship_latitude, ship_speed, waypoints):
waypoint_name = input("Enter the name of the waypoint: ")

waypoint = None
for wp in waypoints:
if wp[0] == waypoint_name:
waypoint = wp
break

if waypoint is None:
print("Waypoint not found.")
return

waypoint_longitude = waypoint[1]
waypoint_latitude = waypoint[3]

# Confirm the speed from the file
confirm_speed = input(f"Current speed from file: {ship_speed} nm. Is this speed correct? (yes/no): ")
if confirm_speed.lower() in ["no", "n"]:
new_speed = float(input("Enter the new speed in nm: "))
ship_speed = new_speed

dlon = radians(waypoint_longitude) - radians(ship_longitude)
lat1 = radians(ship_latitude)
lat2 = radians(waypoint_latitude)

y = sin(dlon) * cos(lat2)
x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dlon)
initial_bearing = atan2(y, x)

initial_bearing = (degrees(initial_bearing) + 360) % 360

distance = calculate_distance(ship_latitude, ship_longitude, waypoint_latitude, waypoint_longitude)
time = distance / ship_speed

print(f"Course to waypoint {waypoint_name}: {initial_bearing:.2f}\u00b0")
print(f"Distance to waypoint {waypoint_name}: {distance:.2f} nm")
print(f"Time to reach waypoint {waypoint_name}: {time:.2f} hours")


def calculate_course_between_waypoints(ship_longitude, ship_latitude, ship_speed, waypoints):
waypoint1_name = input("Enter the name of the first waypoint: ")
waypoint2_name = input("Enter the name of the second waypoint: ")

waypoint1 = None
waypoint2 = None

for wp in waypoints:
if wp[0] == waypoint1_name:
waypoint1 = wp
if wp[0] == waypoint2_name:
waypoint2 = wp

if waypoint1 is None or waypoint2 is None:
print("One or both waypoints not found.")
return

waypoint1_longitude = waypoint1[1]
waypoint1_latitude = waypoint1[3]

waypoint2_longitude = waypoint2[1]
waypoint2_latitude = waypoint2[3]

# Confirm the speed from the file
confirm_speed = input(f"Current speed from file: {ship_speed} nm. Is this speed correct? (yes/no): ")
if confirm_speed.lower() in ["no", "n"]:
new_speed = float(input("Enter the new speed in nm: "))
ship_speed = new_speed

dlon = radians(waypoint2_longitude) - radians(waypoint1_longitude)
lat1 = radians(waypoint1_latitude)
lat2 = radians(waypoint2_latitude)

y = sin(dlon) * cos(lat2)
x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dlon)
initial_bearing = atan2(y, x)

initial_bearing = (degrees(initial_bearing) + 360) % 360

distance = calculate_distance(waypoint1_latitude, waypoint1_longitude, waypoint2_latitude, waypoint2_longitude)
time = distance / ship_speed

print(f"Course between waypoints {waypoint1_name} and {waypoint2_name}: {initial_bearing:.2f}\u00b0")
print(f"Distance between waypoints {waypoint1_name} and {waypoint2_name}: {distance:.2f} nm")
print(f"Time to travel between waypoints {waypoint1_name} and {waypoint2_name}: {time:.2f} hours")


def calculate_distance(lat1, lon1, lat2, lon2):
R = 6371 # Radius of the Earth in km

dlat = radians(lat2 - lat1)
dlon = radians(lon2 - lon1)
a = sin(dlat / 2) * sin(dlat / 2) + cos(radians(lat1)) * cos(radians(lat2)) * sin(dlon / 2) * sin(dlon / 2)
c = 2 * atan2(sqrt(a), sqrt(1 - a))
distance = R * c

# Convert distance from kilometers to nautical miles
distance_nm = distance / 1.852

return distance_nm


def calculate_course_to_all_waypoints(ship_longitude, ship_latitude, ship_speed, waypoints):
start_waypoint = input("Enter the name of the first waypoint: ")

start_index = None
for i, waypoint in enumerate(waypoints):
if waypoint[0] == start_waypoint:
start_index = i
break

if start_index is None:
print("Start waypoint not found.")
return

# Confirm the speed from the file
confirm_speed = input(f"Current speed from file: {ship_speed} nm. Is this speed correct? (yes/no): ")
if confirm_speed.lower() in ["no", "n"]:
new_speed = float(input("Enter the new speed in nm: "))
ship_speed = new_speed

total_distance = 0
total_time = 0

for i in range(start_index, len(waypoints) - 1):
current_waypoint = waypoints
next_waypoint = waypoints[i + 1]

current_longitude = current_waypoint[1]
current_latitude = current_waypoint[3]

next_longitude = next_waypoint[1]
next_latitude = next_waypoint[3]

dlon = radians(next_longitude) - radians(current_longitude)
lat1 = radians(current_latitude)
lat2 = radians(next_latitude)

y = sin(dlon) * cos(lat2)
x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dlon)
initial_bearing = atan2(y, x)

initial_bearing = (degrees(initial_bearing) + 360) % 360

distance = calculate_distance(current_latitude, current_longitude, next_latitude, next_longitude)
time = distance / ship_speed

total_distance += distance
total_time += time

print(f"Course from {current_waypoint[0]} to {next_waypoint[0]}: {initial_bearing:.2f}\u00b0")
print(f"Distance from {current_waypoint[0]} to {next_waypoint[0]}: {distance:.2f} nm")
print(f"Time to travel from {current_waypoint[0]} to {next_waypoint[0]}: {time:.2f} hours")

print(f"Total Distance: {total_distance:.2f} nm")
print(f"Total Time: {total_time:.2f} hours")


def calculate_new_position(ship_longitude, ship_latitude, ship_speed, data, situation_file_path):
print("Option 4: Calculate a new position")

# Prompt user for speed confirmation
confirm_speed = input(f"Current speed from file: {ship_speed} nm. Is this speed correct? (yes/no): ")
if confirm_speed.lower() in ["no", "n"]:
ship_speed = float(input("Enter the new speed in nm: "))

# Prompt user for course confirmation
course_from_file = float(data["vehicle_beta"])
if course_from_file >= 0:
calculated_course = round(course_from_file / 0.0174444, 2)
else:
calculated_course = round((course_from_file / 0.0174444) + 360, 2)
confirm_course = input(f"Current course from file: {calculated_course}°. Is this course correct? (yes/no): ")
if confirm_course.lower() in ["no", "n"]:
ship_course = float(input("Enter the new course in degrees: "))
else:
ship_course = calculated_course

# Prompt user for wind speed, wind direction, current speed, current direction, and travel time
wind_speed = float(input("Enter the wind speed in nm: "))
wind_direction = float(input("Enter the wind direction in degrees: "))
current_speed = float(input("Enter the current speed in nm: "))
current_direction = float(input("Enter the current direction in degrees: "))
travel_time = float(input("Enter the travel time in hours: "))

# Calculate new position using dead reckoning
new_lat, new_lon = dead_reckoning(ship_latitude, ship_longitude, ship_course, ship_speed, travel_time,
wind_speed, wind_direction, current_speed, current_direction)

# Format longitude and latitude with directions
lon_direction = "E" if new_lon >= 0 else "W"
lat_direction = "N" if new_lat >= 0 else "S"

print("New Position Calculation:")
print(f"Initial Position: Longitude: {abs(ship_longitude):.6f}\u00b0{lon_direction}, Latitude: {abs(ship_latitude):.6f}\u00b0{lat_direction}")
print(f"Final Position: Longitude: {abs(new_lon):.6f}\u00b0{lon_direction}, Latitude: {abs(new_lat):.6f}\u00b0{lat_direction}")

# Update the latitude and longitude in the data dictionary
data["vehicle_east"] = str(new_lon)
data["vehicle_north"] = str(new_lat)

# Prompt user to write new position to situation file
write_to_file = input("Do you want to write the new position to the situation file? (yes/no): ")
if write_to_file.lower() in ["yes", "y"]:
# Write the updated data back to the situation file
with open(situation_file_path, "r") as f:
lines = f.readlines()

with open(situation_file_path, "w") as f:
for line in lines:
if line.strip().endswith("[vehicle_east]"):
f.write(f"{new_lon:.6f}\t[vehicle_east]\n")
elif line.strip().endswith("[vehicle_north]"):
f.write(f"{new_lat:.6f}\t[vehicle_north]\n")
else:
f.write(line)

print("New position written to the situation file.")
else:
print("New position not written to the situation file.")

# Prompt user to delete waypoints from situation file
delete_waypoints = input("Do you want to delete the waypoints from the situation file? (yes/no): ")
if delete_waypoints.lower() in ["yes", "y"]:
with open(situation_file_path, "r") as f:
lines = f.readlines()

start_deleting = False
with open(situation_file_path, "w") as f:
for line in lines:
if line.strip().endswith("[vehicle_own]"):
start_deleting = True
elif line.strip().endswith("[vehicle_next_waypoint]"):
start_deleting = False

if not start_deleting or line.strip().endswith("[vehicle_own]"):
f.write(line)

print("Waypoints deleted from the situation file if requested.")



def main():
situation_file = input("Enter the name of the situation file (without .txt extension): ")
situation_file += ".txt"
situation_file_path = os.path.join("G:\\", "Virtual Sailor", "situations", situation_file)

data = {}
with open(situation_file_path, "r") as f:
for line in f:
line = line.strip()
if not line or line.startswith("//") or line.startswith("@"):
continue
parts = line.split("\t")
key = parts[-1].strip("[]")
value = parts[:-1]
if len(value) == 1:
value = value[0]
data[key] = value

ship_longitude = float(data["vehicle_east"])
lon_direction = "E" if ship_longitude >= 0 else "W"
print(f"Longitude: {abs(ship_longitude):.6f}\u00b0{lon_direction}")

ship_latitude = float(data["vehicle_north"])
lat_direction = "N" if ship_latitude >= 0 else "S"
print(f"Latitude: {abs(ship_latitude):.6f}\u00b0{lat_direction}")

ship_speed = float(data["vehicle_speed"]) * 2
print(f"Speed: {ship_speed} nm")

course = float(data["vehicle_beta"])
if course >= 0:
course = course / 0.0174444
else:
course = (course / 0.0174444) + 360
print(f"Course: {course:.2f}\u00b0")

waypoints = read_waypoints(situation_file_path)
if waypoints:
print("Waypoint positions:")
for waypoint in waypoints:
name, longitude, longitude_direction, latitude, latitude_direction = waypoint
print(
f"Waypoint {name}: Longitude: {longitude:.6f}\u00b0{longitude_direction}, Latitude: {latitude:.6f}\u00b0{latitude_direction}"
)
else:
print("No waypoints found.")

while True:
calculate_navigation = input("Do you want to perform navigation calculations? (yes/no): ")
if calculate_navigation.lower() in ["no", "n"]:
break
elif calculate_navigation.lower() in ["yes", "y"]:
print("Navigation Calculations Menu:")
print("1. Calculate course and time to a waypoint")
print("2. Calculate course and distance between two waypoints")
print("3. Calculate course and speed for all waypoints")
print("4. Calculate a new position")

option = input("Enter the option number (1-4): ")

if option == "1":
calculate_course_to_waypoint(ship_longitude, ship_latitude, ship_speed, waypoints)
elif option == "2":
calculate_course_between_waypoints(ship_longitude, ship_latitude, ship_speed, waypoints)
elif option == "3":
calculate_course_to_all_waypoints(ship_longitude, ship_latitude, ship_speed, waypoints)
elif option == "4":
calculate_new_position(ship_longitude, ship_latitude, ship_speed, data, situation_file_path)
else:
print("Invalid option.")
else:
print("Invalid input. Please enter 'yes', 'no', 'y', or 'n'.")


if __name__ == "__main__":
main()
Post Reply