Survey Toolbox update

Survey Toolbox update
basic library

Chipping away at it in my downtime! I have managed to separate the code from one big script into a bunch of smaller scripts to allow for some code isolation - this will let bits and pieces be worked on without impacting the whole (hopefully!). I've also managed to put together a preliminary basic example of how some of this might be used and at the same time finding a bug in the bearing calculator - which I'm positive I'd already fixed once before, but in a different project (hence why it's important to put reusable code in a library). Let's take a look at one of the functions, and, the example code.

CBD - Coordinates from Bearing and Distance

This is one of my favourites as it let's you calculate the position of another point (or object) provided you know the start coordinates, and a bearing and distance to the new point.

#  cbd
from .commonCalculations import get_deltas_from_bearing_distance
from .commonCalculations import get_coordinates_from_deltas


def coordinates_from_bearing_distance(from_coordinates, bearing, distance_2d):
    # Return coordinates from bearing and distance
    deltas = get_deltas_from_bearing_distance(bearing, distance_2d)
    new_coords = get_coordinates_from_deltas(from_coordinates, deltas)
    return new_coords
We'll look at commonCalculations later

Above is the entire code for calculating these coordinates. As you can see it does rely on two other functions from the .commonCalculations script, which is really the guts of the module, containing the reusable code that powers these smaller functions. Ie; using the bearing and distance to calculate the differences in Easting, Northings, and Elevation and then using these coordinates to produce new coordinates.

Simple Artillery game, place guns at two random sets of coordinates.
player1 enters a bearing and distance to fire at player2
coordinates from bearing and distance are used to calculate where the round falls.
bearing and distance from coordinates are used to plot how far away the round has fallen from player two.
if the round is within [x] metres, it's a kill.
otherwise some approximate corrections are sent to player1 to aid in adjusting for the next round (if they're still alive!)

Demo Code

I won't break the demo code down, it's already commented throughout so there i no point.

# Declare some constants.
from surveytoolbox.config import EASTING, NORTHING, ELEVATION

# Import functions.
from surveytoolbox.newPointStore import NewPointStore
from surveytoolbox.newSurveyPoint import NewSurveyPoint

from surveytoolbox.bdc import bearing_distance_from_coordinates
from surveytoolbox.cbd import coordinates_from_bearing_distance
from surveytoolbox.fmt_dms import format_as_dms

# Start a point store so you can track your points.
pointStore = NewPointStore()

# Create some points and add to point store.
point_1 = NewSurveyPoint("JRR")
pointStore.set_new_point(point_1)

point_2 = NewSurveyPoint("JayArghArgh")
pointStore.set_new_point(point_2)

# Start playing
point_1.set_vertex(
    {
        EASTING: 100,
        NORTHING: 100,
        ELEVATION: 30
    }
)

point_2.set_vertex(
    {
        EASTING: 200,
        NORTHING: 100,
        ELEVATION: 30
    }
)

# Calculate and print the bearing and distance between two points.
target_loc = bearing_distance_from_coordinates(point_1.get_vertex(), point_2.get_vertex())
print(
    f"Bearing: {format_as_dms(target_loc[0])}"
    f"\nDistance (2d): {target_loc[1]}"
    f"\nDistance (3d): {target_loc[2]}"
)

# Create a new point using the provided bearing and distance (it shoudl duplicate point 2)
point_3 = NewSurveyPoint("JRR2110141000")
pointStore.set_new_point(point_3)

point_3.set_vertex(
    coordinates_from_bearing_distance(
        point_1.get_vertex(),
        target_loc[0],
        target_loc[1]
    )
)

# Uncomment this for an example of listing specific information for all points.
current_points = pointStore.get_point_store()
for k, v in current_points.items():
    print(
        f"{current_points[k].get_point_name()}: {current_points[k].get_vertex()}"
    )

# TODO returning back-bearing for some reason.
The above code when run, outputs below on the console.
PS C:\Code\jrr-code\surveytoolbox> python .\tester.py
Bearing: 270° 0' 0.0"
Distance (2d): 100.0
Distance (3d): 100.0
JRR: {'e': 100, 'n': 100, 'el': 30}
JayArghArgh: {'e': 200, 'n': 100, 'el': 30}
JRR2110141000: {'e': 0.0, 'n': 99.99999999999999, 'el': 30.0}
There is an error in the bearing calculations here :/

Code Consistency

When I originally created my coordinates script, it was never intended to be used more than once let alone turned into a library - there is some tidy up work that needs to be done to make the code consistent which I'm addressing along the way. The biggest inconsistency however, is the one I introduced yesterday :P To begin with, the returns were all arrays with not a lot of information other than the expected order they were returned

east_north_elevation = [100.00, 200.00, 201.00]
bearing_distance2d_distance3d = [100.00, 200.00 201.00]
example of the original returned arrays.

In the above code block, you can see how this would not have been very helpful at all. Wanting to be able to program this for Python, and have outputs that could be exported as JSON and then utilized in other languages / platforms, I have started to switch the returned output to Python Dictionaries. Of course playing around now with what is best, what is consistent, what would the user expect, then conducting the refactor - has been a bit fiddly. I have settled on the below, with the dictionary keys being held in a config file so should I need to change these later, it won't be a problem (hopefully!)

EASTING = "e"
NORTHING = "n"
ELEVATION = "el"

vertex = {EASTING: 100.00, NORTHING: 100.00, ELEVATION: 100.00}
print(vertex)

# Returns eg {e: 100.00, n: 100.00, el: 100.00}

Next up!

The next job to tackle shouldn't take too long, I'll be finding and fixing the bug in the BFD - bearings from deltas code. I think the last time I did this it was jsut a complete re-write (see if you can beat me to it!). And I'll be doing some more work on the overall structure of the module in preparation for uploading Rev01.

# bfd
import math


def get_bearing_from_deltas(deltas):
    # A great big switch statement to determine the correct direction.
    delta_e = deltas[0]
    delta_n = deltas[1]
    delta_el = deltas[2]
    bearing = 0.0

    if delta_e == 0 and delta_n > 0:
        bearing = 0.0
    elif delta_e == 0 and delta_n < 0:
        bearing = 180.0
    elif delta_e > 0 and delta_n == 0:
        bearing = 90.0
    elif delta_e < 0 and delta_n == 0:
        bearing = 270.0

    # Check for 45 degree variations.
    elif abs(delta_e) == abs(delta_n):
        if delta_e > 0 and delta_n > 0:
            bearing = 45.0
        elif delta_e > 0 > delta_n:
            bearing = 135.0
        elif delta_e < 0 and delta_n < 0:
            bearing = 225.0
        elif delta_n > 0 > delta_e:
            bearing = 315.0

    # Compute it out.
    elif delta_e > 0:
        bearing = math.degrees(math.atan(delta_e / delta_n))
    elif delta_e < 0 and delta_n < 0:
        bearing = math.degrees(math.atan(delta_e / delta_n)) + 180
    elif delta_n > 0 > delta_e:
        bearing = math.degrees(math.atan(delta_e / delta_n)) + 360

    # The final piece.
    if bearing < 0:
        bearing += 180
    return bearing
Where is the bug?