Skip to content

Add csv_write() to fix issue #45#52

Open
G0rocks wants to merge 2 commits intomarinlauber:masterfrom
G0rocks:master
Open

Add csv_write() to fix issue #45#52
G0rocks wants to merge 2 commits intomarinlauber:masterfrom
G0rocks:master

Conversation

@G0rocks
Copy link

@G0rocks G0rocks commented Feb 19, 2026

Fixes #45

@G0rocks G0rocks mentioned this pull request Feb 19, 2026
@marinlauber
Copy link
Owner

@G0rocks I can see the use of this if it is made general. By that, I mean it should write the result of the VPP to the CSV format, which is normally used in routing software. The current version is too specific for your use case, but it is a special case of the more general polar csv format.

I also don't understand why you average the boat speed. You get multiple boat speeds, but there is only one that is the best; you should just use the max boat speed at that TWA/TWS point.

@G0rocks
Copy link
Author

G0rocks commented Feb 19, 2026

The reason for averaging the boat speed is that I did not know which one was the correct one so I thought to myself that if I averaged them I'd probably get something close to the realistic one.

Regarding the generalization, can you tell me explicitly what needs to change so that this function fulfills your requirement of writing the result of the VPP to the CSV format? I don't know how you want this done even though I am probably up for fixing it as long as it still fulfills my requirement of being compatible with OpenCPN.

Note that currently OpenCPN polars only work in 5° increments (or multiples of 5°) and when it comes to the wind speed they work in increments of 2 (or multiples of 2) regardless of if it is in knots or meters/sec or another unit but the OpenCPN plugin always writes "knots".
This means that if, in order to fulfill your requirement, it needs to be possible to write the result in a csv file which would perhaps use angle increments of 3° or wind speed increments of 1 knot (or meter/sec or other unit) then it will no longer be compatible with OpenCPN until this issue (rgleason/polar_pi#34) is dealt with.

@marinlauber
Copy link
Owner

Okay, I can guide you on how to select the best boat speed later.

Ideally, we want the function to do something like this (pseudo-code)

def write_CSV(VPP, save, fname="SailChart.png"):
    tws = VPP.tws_range
    twa = VPP.twa_range
    # generate header for csv file
    writer = csv.writer(file, delimiter=";")
    # Write header with TWS in knots
    writer.writerow((hcat(["TWA\TWS", [str(ws) for ws in tws]]))
    # Loop through every degree
    for angle in twa:
         row = make_row_from_store(VPP.store)
         writer.writerow(row)

And then in your VPP setup, you need to set the correct analysis

vpp.set_analysis(
    tws_range=np.arange(2.0, 40.0, 2.0), twa_range=np.arrange(0.0, 181.0, 5.0)
)

which will result in the TWS and TWA you want.

But if a user decides he wants to write a CSV file with only tws_range=np.arange(2.0, 16.0, 4.0) it will also work.

@marinlauber
Copy link
Owner

@G0rocks I've written the function quickly.

If you don't specify the tws_range or twa_range you want it to write the output at, it uses the ones you defined in the VPP analysis. If you specify either of the two, it uses that to interpolate the VPP results onto the new TWA/TWS combination.

def csv_write(VPP, tws_range=None, twa_range=None, fname="polar.csv"):
    if not hasattr(VPP, "store"):
        raise ValueError("VPP object must have a 'store' attribute to use to export to csv.")
    import csv
    # if we specify tws_range and twa_range, we use them, otherwise we use the ones from VPP
    tws = VPP.tws_range if tws_range is None else tws_range
    twa = VPP.twa_range if twa_range is None else twa_range
    store = np.max(VPP.store[:, :, :, 0], axis=2)/KNOTS_TO_MPS
    # if we specify a custom TWA or TWS range, we make a custom structure and we interpolate the results in it
    if tws_range is not None or twa_range is not None:
        func = RectBivariateSpline(VPP.tws_range, VPP.twa_range, store)
        store = func(tws, twa)
    with open(fname, mode="w", newline="") as file:
        # generate header for csv file
        writer = csv.writer(file, delimiter=";")
        # Write header with TWS in knots
        writer.writerow(["TWA\TWS"] + [str(round(ws/KNOTS_TO_MPS, 2)) for ws in tws])
        # Loop through every degree
        for j, angle in enumerate(twa):
            row = [str(angle)] + [str(round(store[i, j], 2)) for i in range(len(tws))]
            writer.writerow(row)

If it roughly tests and it works.

# standard write of the results
csv_write(vpp, fname="polar.csv")

# custom twa range
 csv_write(vpp, twa_range=np.linspace(30.0, 180.0, 16), fname="polar.csv")

# custom tws  and tws range
csv_write(vpp, twa_range=np.linspace(30.0, 180.0, 10),
                 tws_range=np.linspace(4.0, 22.0, 10), fname="polar.csv")

I'll let you test it and update the PR if that works for you. I cannot checkout your PR and implement it myself

with open(fname, mode="w", newline="") as file:
writer = csv.writer(file, delimiter=";")
# Write header with TWS in knots
writer.writerow(["TWA\TWS", "2", "4", "6", "8", "10", "12", "14", "16", "18", "20", "22", "24", "26", "28", "30", "32", "34", "36", "38", "40"])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not general, it should use the tws_range array to generate the header of the CSV, then in your specific case, you can make sure you run these TWS.

# Write header with TWS in knots
writer.writerow(["TWA\TWS", "2", "4", "6", "8", "10", "12", "14", "16", "18", "20", "22", "24", "26", "28", "30", "32", "34", "36", "38", "40"])
# Loop through every degree from 0°to 180° in 5° increments
for angle in range(0, 181, 5):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, not general, use the twa_range

row = [None] * 21
# Set angle
row[0] = angle
# Go through each TWS and get the corresponding boat speed for the current angle
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you the tws_range and twa_range you don't need to do all this, You can just go through the store array and take the value there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add OpenCPN compatible results

2 participants