Skip to content

Add csv_write() to fix issue #45 - #52

Merged
marinlauber merged 4 commits into
marinlauber:masterfrom
G0rocks:master
Oct 17, 2026
Merged

marinlauber merged 4 commits into
marinlauber:masterfrom
G0rocks:master

Conversation

@G0rocks

@G0rocks G0rocks commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

Fixes #45

@G0rocks G0rocks mentioned this pull request Feb 19, 2026
@marinlauber

Copy link
Copy Markdown
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

G0rocks commented Feb 19, 2026

Copy link
Copy Markdown
Contributor Author

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
Copy Markdown
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
Copy Markdown
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

Comment thread src/UtilsMod.py Outdated
Comment thread src/UtilsMod.py Outdated
Comment thread src/UtilsMod.py Outdated
marinlauber and others added 2 commits October 17, 2026 12:09
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JN4y2NWa8kFMBZ5FV92jkj
Implements the review on marinlauber#52:
- csv_write(VPP, tws_range=None, twa_range=None, fname="polar.csv")
  writes the best boat speed over the sail sets (no averaging) with the
  analysis TWS/TWA as header and rows by default
- a custom tws_range (knots) and/or twa_range (degrees) linearly
  interpolates the results onto that grid, e.g. the 5 deg / 2 kn steps
  OpenCPN expects; points outside the analysis or not sailable are 0
- speeds are already in knots in the store, so no second unit conversion
- VPP.write_csv() convenience method; runVPP.py writes polar.csv
- .gitignore only ignores csv files at the repo root (dat/ holds csv data)
- tests for the default grid, the interpolation and VPP.write_csv

Fixes marinlauber#45

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JN4y2NWa8kFMBZ5FV92jkj
@marinlauber
marinlauber merged commit 83dcce2 into marinlauber:master Oct 17, 2026
marinlauber added a commit that referenced this pull request Oct 17, 2026
Resolve the import conflicts in src/python_vpp/VPPMod.py and tests/test_utils.py to the package imports, and document write_csv in the README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JN4y2NWa8kFMBZ5FV92jkj
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