feat: initial commit from Primus

This commit is contained in:
Jonas Juselius
2022-04-22 13:30:12 +02:00
commit 28ed5ab459
30 changed files with 12244 additions and 0 deletions

69
.build/Build.fs Normal file
View File

@@ -0,0 +1,69 @@
open Fake.Core
open Fake.IO
open Farmer
open Farmer.Builders
open Helpers
initializeContext()
let srcPath = Path.getFullName "src"
let testPath = Path.getFullName "test"
let libPath = Some srcPath
let deployPath = Path.getFullName "deploy"
let packPath = Path.getFullName "packages"
let versionFile = Path.getFullName ".version"
Target.create "Clean" (fun _ -> Shell.cleanDir deployPath)
Target.create "InstallClient" (fun _ ->
run npm "install" "."
run dotnet "tool restore" "."
)
Target.create "Bundle" (fun _ ->
run dotnet $"publish -c Release -o \"{deployPath}\"" srcPath
)
Target.create "BundleDebug" (fun _ ->
run dotnet $"publish -c Debug -o \"{deployPath}\"" srcPath
)
Target.create "Pack" (fun _ ->
match libPath with
| Some p -> run dotnet $"pack -c Release -o \"{packPath}\"" p
| None -> ()
)
Target.create "Format" (fun _ ->
run dotnet "fantomas . -r" "src"
)
Target.create "Test" (fun _ ->
if System.IO.Directory.Exists testPath then
run dotnet "run" testPath
else ()
)
open Fake.Core.TargetOperators
let dependencies = [
"Clean"
==> "InstallClient"
==> "Bundle"
"Clean"
==> "BundleDebug"
"Clean"
==> "Test"
"Clean"
==> "Pack"
]
[<EntryPoint>]
let main args = runOrDefault args

105
.build/Helpers.fs Normal file
View File

@@ -0,0 +1,105 @@
module Helpers
open Fake.Core
let initializeContext () =
let execContext = Context.FakeExecutionContext.Create false "build.fsx" [ ]
Context.setExecutionContext (Context.RuntimeContext.Fake execContext)
module Proc =
module Parallel =
open System
let locker = obj()
let colors =
[| ConsoleColor.Blue
ConsoleColor.Yellow
ConsoleColor.Magenta
ConsoleColor.Cyan
ConsoleColor.DarkBlue
ConsoleColor.DarkYellow
ConsoleColor.DarkMagenta
ConsoleColor.DarkCyan |]
let print color (colored: string) (line: string) =
lock locker
(fun () ->
let currentColor = Console.ForegroundColor
Console.ForegroundColor <- color
Console.Write colored
Console.ForegroundColor <- currentColor
Console.WriteLine line)
let onStdout index name (line: string) =
let color = colors.[index % colors.Length]
if isNull line then
print color $"{name}: --- END ---" ""
else if String.isNotNullOrEmpty line then
print color $"{name}: " line
let onStderr name (line: string) =
let color = ConsoleColor.Red
if isNull line |> not then
print color $"{name}: " line
let redirect (index, (name, createProcess)) =
createProcess
|> CreateProcess.redirectOutputIfNotRedirected
|> CreateProcess.withOutputEvents (onStdout index name) (onStderr name)
let printStarting indexed =
for (index, (name, c: CreateProcess<_>)) in indexed do
let color = colors.[index % colors.Length]
let wd =
c.WorkingDirectory
|> Option.defaultValue ""
let exe = c.Command.Executable
let args = c.Command.Arguments.ToStartInfo
print color $"{name}: {wd}> {exe} {args}" ""
let run cs =
cs
|> Seq.toArray
|> Array.indexed
|> fun x -> printStarting x; x
|> Array.map redirect
|> Array.Parallel.map Proc.run
let createProcess exe arg dir =
CreateProcess.fromRawCommandLine exe arg
|> CreateProcess.withWorkingDirectory dir
|> CreateProcess.ensureExitCode
let dotnet = createProcess "dotnet"
let npm =
let npmPath =
match ProcessUtils.tryFindFileOnPath "npm" with
| Some path -> path
| None ->
"npm was not found in path. Please install it and make sure it's available from your path. " +
"See https://safe-stack.github.io/docs/quickstart/#install-pre-requisites for more info"
|> failwith
createProcess npmPath
let run proc arg dir =
proc arg dir
|> Proc.run
|> ignore
let runParallel processes =
processes
|> Proc.Parallel.run
|> ignore
let runOrDefault args =
try
match args with
| [| target |] -> Target.runOrDefault target
| _ ->
Target.runOrDefault "Pack"
0
with e ->
printfn "%A" e
1

18
.config/dotnet-tools.json Normal file
View File

@@ -0,0 +1,18 @@
{
"version": 1,
"isRoot": true,
"tools": {
"fable": {
"version": "3.7.0",
"commands": [
"fable"
]
},
"fantomas-tool": {
"version": "4.6.4",
"commands": [
"fantomas"
]
}
}
}

29
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,29 @@
FROM mcr.microsoft.com/dotnet/sdk:6.0
# Add keys and sources lists
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" \
| tee /etc/apt/sources.list.d/yarn.list
# Install node, 7zip, yarn, git, process tools
RUN apt-get update \
&& apt-get install -y nodejs p7zip-full git procps ssh-client
# Clean up
RUN apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
# Install dotnet tools
RUN dotnet tool install fable -g
# Trouble brewing
RUN rm /etc/ssl/openssl.cnf
# add dotnet tools to path to pick up fake and paket installation
ENV PATH="/root/.dotnet/tools:${PATH}"
# Copy endpoint specific user settings into container to specify
# .NET Core should be used as the runtime.
COPY settings.vscode.json /root/.vscode-remote/data/Machine/settings.json

View File

@@ -0,0 +1,11 @@
{
"name": "SAFE",
"dockerFile": "Dockerfile",
"appPort": [8080, 8085],
"extensions": [
"ionide.ionide-fsharp",
"ms-dotnettools.csharp",
"editorconfig.editorconfig",
"msjsdiag.debugger-for-chrome"
]
}

View File

@@ -0,0 +1,3 @@
{
"FSharp.fsacRuntime":"netcore"
}

32
.editorconfig Normal file
View File

@@ -0,0 +1,32 @@
root = true
[*]
indent_style = space
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = false
[*.fs]
max_line_length=120
# Feliz style
fsharp_single_argument_web_mode=true
fsharp_space_before_colon=false
fsharp_max_if_then_else_short_width=60
fsharp_max_infix_operator_expression=50
fsharp_max_record_width=70
fsharp_max_record_number_of_items=1
fsharp_max_array_or_list_width=70
fsharp_max_array_or_list_number_of_items=1
fsharp_max_value_binding_width=70
fsharp_max_function_binding_width=40
fsharp_max_dot_get_expression_width=50
fsharp_multiline_block_brackets_on_same_column=true
fsharp_newline_between_type_definition_and_members=false
fsharp_max_elmish_width=40
fsharp_align_function_signature_to_indentation=false
fsharp_alternative_long_member_definitions=false
fsharp_multi_line_lambda_closing_newline=false
fsharp_disable_elmish_syntax=false
fsharp_keep_indent_in_branch=false
fsharp_blank_lines_around_nested_multiline_expressions=false

17
.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
.fable/
.fake/
.vs/
obj/
bin/
packages/
node_modules/
src/Client/public/js/
release.cmd
release.sh
.idea/
*.orig
*.DotSettings.user
deploy
.ionide/
*.db
build.fsx.lock

9
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,9 @@
variables:
DEPLOY_NAME: default
DEPLOY_NAMESPACE: default
include:
- project: oceanbox/gitlab-ci
ref: main
file: DotnetPackage.gitlab-ci.yml

41
.releaserc.yaml Normal file
View File

@@ -0,0 +1,41 @@
branches:
- main
- master
- name: develop
prerelease: true
plugins:
- '@semantic-release/commit-analyzer'
- '@semantic-release/release-notes-generator'
- - '@semantic-release/changelog'
- changelogFile: RELEASE_NOTES.md
changelogTitle: "# Changelog"
- - 'semantic-release-dotnet'
- paths: [ "src/*.fsproj", "src/*/*.fsproj" ]
- - '@semantic-release/exec'
- generateNotesCmd: "echo ${nextRelease.version} > .version"
- - '@semantic-release/git'
- message: "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
assets: [ "RELEASE_NOTES.md", ".version", "src/*.fsproj", "src/*/*.fsproj" ]
- - '@semantic-release/gitlab'
- assets: []
analyzeCommits:
- path: "@semantic-release/commit-analyzer"
releaseRules:
- type: "fix"
release: "patch"
- type: "patch"
release: "patch"
- type: "feat"
release: "minor"
- type: "feature"
release: "minor"
- type: "minor"
release: "minor"
- type: "breaking"
release: "major"
- type: "major"
release: "major"

16
Build.fsproj Normal file
View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include=".build/Helpers.fs" />
<Compile Include=".build/Build.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fake.Core.Target" Version="5.20.4" />
<PackageReference Include="Fake.DotNet.Cli" Version="5.20.4" />
<PackageReference Include="Fake.IO.FileSystem" Version="5.20.4" />
<PackageReference Include="Farmer" Version="1.6.26" />
</ItemGroup>
</Project>

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Serit Tromsø AS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

77
Oceanbox.FvcomKit.sln Normal file
View File

@@ -0,0 +1,77 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2005
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C29C6F32-3A30-4071-9B4A-8FBCAAA5993A}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
LICENSE = LICENSE
Dockerfile = Dockerfile
EndProjectSection
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Build", "Build.fsproj", "{C6824583-FB68-4F69-8117-6B29637A3B96}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "src", "src/Oceanbox.FvcomKit.fsproj", "{662A0CDC-7E82-4157-AD25-469DD7ABAA69}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "test", "test\Tests.fsproj", "{BCBD73E2-7170-4A85-BFD8-B76F056D4D49}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C6824583-FB68-4F69-8117-6B29637A3B96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C6824583-FB68-4F69-8117-6B29637A3B96}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C6824583-FB68-4F69-8117-6B29637A3B96}.Debug|x64.ActiveCfg = Debug|Any CPU
{C6824583-FB68-4F69-8117-6B29637A3B96}.Debug|x64.Build.0 = Debug|Any CPU
{C6824583-FB68-4F69-8117-6B29637A3B96}.Debug|x86.ActiveCfg = Debug|Any CPU
{C6824583-FB68-4F69-8117-6B29637A3B96}.Debug|x86.Build.0 = Debug|Any CPU
{C6824583-FB68-4F69-8117-6B29637A3B96}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C6824583-FB68-4F69-8117-6B29637A3B96}.Release|Any CPU.Build.0 = Release|Any CPU
{C6824583-FB68-4F69-8117-6B29637A3B96}.Release|x64.ActiveCfg = Release|Any CPU
{C6824583-FB68-4F69-8117-6B29637A3B96}.Release|x64.Build.0 = Release|Any CPU
{C6824583-FB68-4F69-8117-6B29637A3B96}.Release|x86.ActiveCfg = Release|Any CPU
{C6824583-FB68-4F69-8117-6B29637A3B96}.Release|x86.Build.0 = Release|Any CPU
{662A0CDC-7E82-4157-AD25-469DD7ABAA69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{662A0CDC-7E82-4157-AD25-469DD7ABAA69}.Debug|Any CPU.Build.0 = Debug|Any CPU
{662A0CDC-7E82-4157-AD25-469DD7ABAA69}.Debug|x64.ActiveCfg = Debug|Any CPU
{662A0CDC-7E82-4157-AD25-469DD7ABAA69}.Debug|x64.Build.0 = Debug|Any CPU
{662A0CDC-7E82-4157-AD25-469DD7ABAA69}.Debug|x86.ActiveCfg = Debug|Any CPU
{662A0CDC-7E82-4157-AD25-469DD7ABAA69}.Debug|x86.Build.0 = Debug|Any CPU
{662A0CDC-7E82-4157-AD25-469DD7ABAA69}.Release|Any CPU.ActiveCfg = Release|Any CPU
{662A0CDC-7E82-4157-AD25-469DD7ABAA69}.Release|Any CPU.Build.0 = Release|Any CPU
{662A0CDC-7E82-4157-AD25-469DD7ABAA69}.Release|x64.ActiveCfg = Release|Any CPU
{662A0CDC-7E82-4157-AD25-469DD7ABAA69}.Release|x64.Build.0 = Release|Any CPU
{662A0CDC-7E82-4157-AD25-469DD7ABAA69}.Release|x86.ActiveCfg = Release|Any CPU
{662A0CDC-7E82-4157-AD25-469DD7ABAA69}.Release|x86.Build.0 = Release|Any CPU
{BCBD73E2-7170-4A85-BFD8-B76F056D4D49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BCBD73E2-7170-4A85-BFD8-B76F056D4D49}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BCBD73E2-7170-4A85-BFD8-B76F056D4D49}.Debug|x64.ActiveCfg = Debug|Any CPU
{BCBD73E2-7170-4A85-BFD8-B76F056D4D49}.Debug|x64.Build.0 = Debug|Any CPU
{BCBD73E2-7170-4A85-BFD8-B76F056D4D49}.Debug|x86.ActiveCfg = Debug|Any CPU
{BCBD73E2-7170-4A85-BFD8-B76F056D4D49}.Debug|x86.Build.0 = Debug|Any CPU
{BCBD73E2-7170-4A85-BFD8-B76F056D4D49}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BCBD73E2-7170-4A85-BFD8-B76F056D4D49}.Release|Any CPU.Build.0 = Release|Any CPU
{BCBD73E2-7170-4A85-BFD8-B76F056D4D49}.Release|x64.ActiveCfg = Release|Any CPU
{BCBD73E2-7170-4A85-BFD8-B76F056D4D49}.Release|x64.Build.0 = Release|Any CPU
{BCBD73E2-7170-4A85-BFD8-B76F056D4D49}.Release|x86.ActiveCfg = Release|Any CPU
{BCBD73E2-7170-4A85-BFD8-B76F056D4D49}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {79A6998D-BCE6-4EC5-ADBC-69234C0D2EC5}
EndGlobalSection
EndGlobal

9
README.md Normal file
View File

@@ -0,0 +1,9 @@
# Oceanbox.FvcomKit
## Run
`dotnet run`
## Build
`dotnet run Bundle`

1
RELEASE_NOTES.md Normal file
View File

@@ -0,0 +1 @@
# Changelog

10009
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

11
package.json Normal file
View File

@@ -0,0 +1,11 @@
{
"private": true,
"devDependencies": {
"@semantic-release/changelog": "^6.0.1",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@semantic-release/gitlab": "^7.0.4",
"semantic-release-dotnet": "^1.0.0"
},
"dependencies": {}
}

262
src/Fvcom.fs Normal file
View File

@@ -0,0 +1,262 @@
module Oceanbox.FvcomKit.Fvcom
#nowarn "57"
open Microsoft.Research.Science.Data
open FSharpPlus
open Serilog
open Grid
type FvcomRestart =
{
salinity: single [,,]
temp: single [,,]
zeta: single [,]
u: single [,,]
v: single [,,]
ua: single [,]
va: single [,]
}
let readUV (ds: DataSet) t l =
try
let n = ds.Dimensions["nele"].Length
let u = ds[ "u" ].GetData([| t; l; 0 |], [| 1; 1; n |]) :?> single [,,]
let v = ds[ "v" ].GetData([| t; l; 0 |], [| 1; 1; n |]) :?> single [,,]
Array.zip u[0, 0, *] v[0, 0, *]
with
| e ->
Log.Error $"{e}"
Array.empty
let readUV' (ds: DataSet) t l =
readUV ds t l
|> Array.collect (fun (x, y) -> [| x; y |])
let readTemp (ds: DataSet) t l =
try
let n = ds.Dimensions["node"].Length
let temp =
ds[ "temp" ].GetData([| t; l; 0 |], [| 1; 1; n |]) :?> single [,,]
temp[0, 0, *]
with
| e ->
Log.Error $"{e}"
Array.empty
let readSalinity (ds: DataSet) t l =
try
let n = ds.Dimensions["node"].Length
let sal =
ds["salinity"]
.GetData([| t; l; 0 |], [| 1; 1; n |])
:?> single [,,]
sal[0, 0, *]
with
| e ->
Log.Error $"{e}"
Array.empty
let readArt1 (ds: DataSet) =
try
ds[ "art1" ].GetData() :?> single []
with
| e ->
Log.Error $"{e}"
Array.empty
let readBathymetry (ds: DataSet) =
try
ds[ "h" ].GetData() :?> single []
with
| e ->
Log.Error e.Message
Array.empty
let readBathymetryAtCenter (ds: DataSet) e =
try
let h = ds[ "h_center" ].GetData() :?> single []
h[e]
with
| e ->
Log.Error e.Message
-1f
let readSiglev (ds: DataSet) n =
try
let siglev = ds[ "siglev" ].GetData() :?> single [,]
siglev[*, n]
with
| err ->
Log.Error $"{err}"
Array.empty
let readSiglay (ds: DataSet) n =
try
let siglay = ds[ "siglay" ].GetData() :?> single [,]
siglay[*, n]
with
| err ->
Log.Error $"{err}"
Array.empty
let readSiglayCenter (ds: DataSet) n =
try
let siglay = ds[ "siglay_center" ].GetData() :?> single [,]
siglay[*, n]
with
| err ->
Log.Error $"{err}"
Array.empty
let readSiglevAtCenter (ds: DataSet) e =
try
let siglev = ds[ "siglev_center" ].GetData() :?> single [,]
siglev[*, e]
with
| e ->
Log.Error $"{e}"
Array.empty
let readSiglayAtCenter (ds: DataSet) e =
try
let siglay = ds[ "siglay_center" ].GetData() :?> single [,]
siglay[*, e]
with
| e ->
Log.Error $"{e}"
Array.empty
module Siglay =
let readSiglay (ds: DataSet) =
try
ds[ "siglay" ].GetData() :?> single [,]
with
| err ->
Log.Error $"{err}"
Array2D.zeroCreate 0 0
let readSiglayAtCenter (ds: DataSet) =
try
ds[ "siglay_center" ].GetData() :?> single [,]
with
| e ->
Log.Error $"{e}"
Array2D.zeroCreate 0 0
let readUv (ds: DataSet) e t =
try
let l = ds.Dimensions["siglay"].Length
let u = ds[ "u" ].GetData([| t; 0; e |], [| 1; l; 1 |]) :?> single [,,]
let v = ds[ "v" ].GetData([| t; 0; e |], [| 1; l; 1 |]) :?> single [,,]
Array.zip u[0, *, 0] v[0, *, 0]
with
| err ->
Log.Warning $"readUv {e} {t}"
Log.Error $"{err}"
Array.empty
let readUv' (ds: DataSet) e t =
try
let l = ds.Dimensions["siglay"].Length
let u = ds[ "u" ].GetData([| t; 0; e |], [| 1; l; 1 |]) :?> single [,,]
let v = ds[ "v" ].GetData([| t; 0; e |], [| 1; l; 1 |]) :?> single [,,]
Array.zip u[0, *, 0] v[0, *, 0]
|> Array.collect (fun (x, y) -> [| x; y |])
with
| err ->
Log.Warning $"readUv' {e} {t}"
Log.Error $"{err}"
Array.empty
let readTemp (ds: DataSet) n t =
try
let l = ds.Dimensions["siglay"].Length
let t = ds[ "temp" ].GetData([| t; 0; n |], [| 1; l; 1 |]) :?> single [,,]
t[0, *, 0]
with
| err ->
Log.Warning $"readTemp {n} {t}"
Log.Error $"{err}"
Array.empty
let readSalinity (ds: DataSet) n t =
try
let l = ds.Dimensions["siglay"].Length
let t =
ds["salinity"]
.GetData([| t; 0; n |], [| 1; l; 1 |])
:?> single [,,]
t[0, *, 0]
with
| err ->
Log.Warning $"readS {n} {t}"
Log.Error $"{err}"
Array.empty
let getBBox (ds: DataSet) : BBox =
try
let x = ds[ "x" ].GetData() :?> single []
let y = ds[ "y" ].GetData() :?> single []
let minX = Array.min x
let maxX = Array.max x
let minY = Array.min y
let maxY = Array.max y
let center = minX + (maxX - minX) / 2f, minY + (maxY - minY) / 2f
{
minX = minX
maxX = maxX
minY = minY
maxY = maxY
center = center
}
with
| e ->
Log.Error $"{e}"
BBox.Default
let getGrid (ds: DataSet) : Grid =
try
let x = ds[ "x" ].GetData() :?> single []
let y = ds[ "y" ].GetData() :?> single []
let xc = ds[ "xc" ].GetData() :?> single []
let yc = ds[ "yc" ].GetData() :?> single []
let nv = ds[ "nv" ].GetData() :?> int [,]
let h = readBathymetry ds
let siglay = Siglay.readSiglay ds
let siglay_c = Siglay.readSiglayAtCenter ds
let siglev = ds[ "siglev" ].GetData() :?> single [,]
let elem =
Array.zip3 nv[0, *] nv[1, *] nv[2, *]
|> Array.map (fun (a, b, c) -> a - 1, b - 1, c - 1)
let nds = Array.zip x y
let cells = Array.zip xc yc
{
Elem = elem
Nodes = nds
BBox = getBBox ds
Cells = cells
Bathymetry = h
Siglay = siglay
SiglayCenter = siglay_c
Siglev = siglev
}
with
| e ->
Log.Error $"{e}"
Grid.Default
let writeFvcomRestart (fvcom: DataSet) (data: FvcomRestart) =
try
fvcom
.Variables[ "salinity" ]
.PutData data.salinity
fvcom.Variables[ "temp" ].PutData data.temp
fvcom.Variables[ "zeta" ].PutData data.zeta
fvcom.Variables[ "u" ].PutData(data.u)
fvcom.Variables[ "v" ].PutData(data.v)
fvcom.Variables[ "ua" ].PutData(data.ua)
fvcom.Variables[ "va" ].PutData(data.va)
with
| e -> Log.Fatal e.Message

204
src/Grid.fs Normal file
View File

@@ -0,0 +1,204 @@
module Oceanbox.FvcomKit.Grid
open System
open FSharpPlus
open FSharpPlus.Control
open Serilog
type NodeIdx = int
type ElemIdx = int
type Elem = NodeIdx * NodeIdx * NodeIdx
type Node = single * single
type BBox =
{
minX: single
maxX: single
minY: single
maxY: single
center: single * single
}
static member Default =
{
minX = Single.MaxValue
maxX = Single.MinValue
minY = Single.MaxValue
maxY = Single.MinValue
center = 0f, 0f
}
type Grid =
{
Elem: Elem array
Nodes: Node array
BBox: BBox
Cells: Node array
Bathymetry: single []
Siglay: single [,]
SiglayCenter: single [,]
Siglev: single [,]
}
static member Default =
{
Elem = Array.empty
Nodes = Array.empty
BBox = BBox.Default
Cells = Array.empty
Bathymetry = Array.empty
Siglay = Array2D.zeroCreate 0 0
SiglayCenter = Array2D.zeroCreate 0 0
Siglev = Array2D.zeroCreate 0 0
}
type ElemsAroundNode = Map<NodeIdx, ElemIdx []>
type NodesAroundNode = Map<NodeIdx, NodeIdx []>
type IndexedFvcomGrid =
{
Grid: Grid
ElemsAroundNode: ElemsAroundNode
NodesAroundNode: NodesAroundNode
}
static member Default =
{
Grid = Grid.Default
ElemsAroundNode = Map.empty
NodesAroundNode = Map.empty
}
let private makeElemsSurroundingNodeMap (elem: Elem array) : ElemsAroundNode =
let addElemIdx k v nodes =
match Map.tryFind k nodes with
| Some xs -> Set.add v xs
| None -> v |> Set.singleton
|> fun el -> Map.add k el nodes
elem
|> Array.fold
(fun (n, a) elm ->
let x, y, z = elm
n + 1,
addElemIdx x n a
|> addElemIdx y n
|> addElemIdx z n)
(0, Map.empty)
|> snd
|> Map.mapValues toArray
let private makeNodesSurroudingNodeMap (n2e: ElemsAroundNode) (elem: Elem array) =
n2e
|> Map.mapValues (fun n ->
n
|> Array.collect (fun x ->
let n1, n2, n3 = elem[x]
[| n1; n2; n3 |])
|> Array.distinct)
let private getSurrounding (idx: Map<int, int []>) (a, b, c) =
[| idx[a]; idx[b]; idx[c] |]
|> Array.concat
|> Array.distinct
let makeIndexedGrid grid =
Log.Information "Indexing grids."
let ean = makeElemsSurroundingNodeMap grid.Elem
{
Grid = grid
ElemsAroundNode = ean
NodesAroundNode = makeNodesSurroudingNodeMap ean grid.Elem
}
|> fun x ->
Log.Debug "...done indexinf grids."
x
let getElemsSurroundingNode (iGrid: IndexedFvcomGrid) n = iGrid.ElemsAroundNode[n]
let getNodesSurroundingNode (iGrid: IndexedFvcomGrid) n = iGrid.NodesAroundNode[n]
let getNodesSurroundingElem (iGrid: IndexedFvcomGrid) e =
getSurrounding iGrid.NodesAroundNode (iGrid.Grid.Elem[e])
let getElemsSurroundingElem (iGrid: IndexedFvcomGrid) e =
getSurrounding iGrid.ElemsAroundNode (iGrid.Grid.Elem[e])
let calcBBoxCenter (x: BBox) =
x.minX + (x.maxX - x.minX) / 2.0f, x.minY + (x.maxY - x.minY) / 2.0f
let calcBBox grid =
let x, y = Array.unzip grid.Nodes
{
minX = Array.min x
maxX = Array.max x
minY = Array.min y
maxY = Array.max y
center = 0f, 0f
}
|> fun x -> { x with center = calcBBoxCenter x }
let printBBox grid = calcBBox grid |> printfn "%A"
let bboxToLngLat b =
let toLatLon = ProjNet.FSharp.UTM_TO_WGS84 33
let x0, y0 = toLatLon.project ((b.minX, b.minY))
let x1, y1 = toLatLon.project ((b.maxX, b.maxY))
// Log.Error (sprintf "(%f, %f) (%f, %f)" x0 y0 x1 y1)
{
minX = x0
maxX = x1
minY = y0
maxY = y1
center = toLatLon.project b.center
}
let projectBBox proj b =
let x0, y0 = proj (b.minX, b.minY)
let x1, y1 = proj (b.maxX, b.maxY)
{ minX = x0; maxX = x1; minY = y0; maxY = y1; center = proj b.center }
let projectGrid proj (grid: Grid) : Grid =
{ grid with
Nodes = grid.Nodes |> Array.Parallel.map proj
BBox = projectBBox proj grid.BBox
Cells = grid.Cells |> Array.Parallel.map proj
}
let toWebMercator (grid: Grid) =
let toWebMercator = ProjNet.FSharp.UTM_TO_EPSG3857 33
let s = System.Diagnostics.Stopwatch.StartNew()
let g = grid |> projectGrid toWebMercator.project
s.Stop()
Log.Debug $"Reprojected grid: {s.ElapsedMilliseconds} ms"
g
let private chompLine (l: string) = l.Split ' '
let private readHeader (h: string) =
chompLine h
|> function
| [| nele; nnode |] -> Some(int nele, int nnode)
| _ -> None
let private readElem (f: string array) =
let next l =
chompLine l
|> function
| [| a; b; c |] -> int a - 1, int b - 1, int c - 1
| _ -> failwith "readElem failed"
try
f |> Array.map next |> Some
with
| e ->
Log.Error e.Message
None
let private readNodes (f: string array) =
let next l =
chompLine l
|> function
| [| x; y |] -> single x, single y
| _ -> failwith "readNodes failed"
try
f |> Array.map next |> Some
with
| e ->
Log.Error e.Message
None

142
src/Interpol.fs Normal file
View File

@@ -0,0 +1,142 @@
module Oceanbox.FvcomKit.Interpol
open MathNet.Numerics.LinearAlgebra
open Serilog
open Types
open ROMS
open Grid
type InterpolCoefs = ((int * int) * (float * float)) array array
type DepthInterpolCoefs = { iRho: InterpolCoefs; iU: InterpolCoefs; iV: InterpolCoefs }
let private findNearestZ z0 (h: float []) =
let rec findNerest' z0 (h: float []) d n =
if n < h.Length - 1 then
let d' = abs (h[n] - z0)
if d' < d then findNerest' z0 h d' (n + 1) else n
else
n - 1
findNerest' z0 h 10000.0 0
let private calcInterpolationWeightedIdx (rn, fn) =
let nearestIdx = Array.map (fun fzi -> findNearestZ fzi rn) fn
nearestIdx
|> Array.mapi (fun i j ->
if fn[i] <= rn[0] then
(0, 0), (1.0, 0.0)
elif fn[i] >= rn[^0] then
(rn.Length - 1, 0), (1.0, 0.0)
elif abs (fn[i] - rn[j]) < 9.999999975e-07 then
(j, 0), (1.0, 0.0)
elif fn[i] < rn[j] then
let dz = rn[j] - rn[j - 1]
let w1 = (fn[i] - rn[j - 1]) / dz
let w2 = (rn[j] - fn[i]) / dz
(j, j - 1), (w1, w2)
elif fn[i] > rn[j] then
let dz = rn[j + 1] - rn[j]
let w1 = (rn[j + 1] - fn[i]) / dz
let w2 = (fn[i] - rn[j]) / dz
(j, j + 1), (w1, w2)
else
failwith "not reachable")
let private calcInterpMatrices (rz: float [] []) (fz: float [] []) =
Array.zip rz fz
|> Array.Parallel.map calcInterpolationWeightedIdx
let mkDepthInterpolCoefs (siglay: single [,]) (h: float []) (roms: float [] []) =
let s =
siglay
|> Array2D.map float
|> Matrix.Build.DenseOfArray
let rescale (sigma: float Matrix) h =
let sh = h |> Array.map float
sigma.MapIndexed(fun _ j x -> -x * sh[j])
|> fun x -> x.ToColumnArrays()
let conv m =
m
|> Array.map (Array.map ((*) -1.0))
|> matrix
|> fun x -> x.ToColumnArrays()
let romz = conv roms
let z = rescale s h
calcInterpMatrices romz z
// Linear interpolation to nearest vertical neighbor
let mkAllDepthInterpolCoefs (fvcom: Grid) (roms: AdjoinedGrid) =
Log.Information "Computing interpolation coefficients."
let uv =
Array.zip roms.u roms.v
|> Array.map (fun (u, v) -> (u + v) * 0.5)
{
iRho = mkDepthInterpolCoefs fvcom.Siglay roms.h roms.zRho
iU = mkDepthInterpolCoefs fvcom.SiglayCenter uv roms.zU
iV = mkDepthInterpolCoefs fvcom.SiglayCenter uv roms.zV
}
let zInterpolProp (iz: InterpolCoefs) (adjRomsProp: float [] []) =
let pz = adjRomsProp |> matrix
iz
|> Array.Parallel.mapi (fun n x ->
let p = pz[ *, n ].ToArray() |> Array.rev
x
|> Array.map (fun ((i1, i2), (w1, w2)) -> p[i1] * w1 + p[i2] * w2))
|> matrix
|> fun x -> x.Transpose().ToArray()
// Trivially already satisfied by ajoint
let hInterpolNearestProp (adjRomsProp: float [] []) = adjRomsProp
let interpolateFromRoms doUv fvGrid rGrid rProps =
let aGrid, aProps = adjoinRomsToFvcom fvGrid rGrid rProps
let ic = mkAllDepthInterpolCoefs fvGrid aGrid
Log.Information "Interpolating props."
let fvData: Fvcom.FvcomRestart =
{
salinity = zInterpolProp ic.iRho aProps.salt |> propTo3D
temp = zInterpolProp ic.iRho aProps.temp |> propTo3D
zeta = aProps.zeta |> propTo2D
u = Array3D.zeroCreate 0 0 0
v = Array3D.zeroCreate 0 0 0
ua = Array2D.zeroCreate 0 0
va = Array2D.zeroCreate 0 0
}
if doUv then
Log.Information "Interpolating u and v."
let angles =
computeAngles fvGrid aGrid
|> Vector.Build.DenseOfArray
let cosA = Vector.Cos angles
let sinA = Vector.Sin angles
let u =
zInterpolProp ic.iU aProps.u
|> Matrix.Build.DenseOfArray
let v =
zInterpolProp ic.iV aProps.v
|> Matrix.Build.DenseOfArray
let k =
[|
for i = 0 to u.RowCount - 1 do
let u' = u.Row i
let v' = v.Row i
u'.PointwiseMultiply cosA
- v'.PointwiseMultiply sinA,
u'.PointwiseMultiply sinA
+ v'.PointwiseMultiply cosA
|]
Log.Information "Computing ubar and vbar."
let u' = k |> Array.map (fun (u, _) -> u.ToArray())
let v' = k |> Array.map (fun (_, v) -> v.ToArray())
let ubar, vbar = calcUVBar fvGrid (u', v')
{ fvData with
u = u' |> matrix |> fun x -> x.ToArray() |> propTo3D
v = v' |> matrix |> fun x -> x.ToArray() |> propTo3D
ua = ubar.ToArray() |> propTo2D
va = vbar.ToArray() |> propTo2D
}
else
fvData

26
src/NorKyst.fs Normal file
View File

@@ -0,0 +1,26 @@
module Oceanbox.FvcomKit.NorKyst
open System
open Thredds
let private getNorkystUrl (threddsUrl: string) (d: DateTime) =
let url = threddsUrl
let fmt (d: DateTime) =
$"{d.Year}%02d{d.Month}%02d{d.Day}T00Z.nc"
$"{url}/fou-hi/new_norkyst800m/his/ocean_his.an.{fmt d}"
let private getArchiveUrls urlf (t: DateTime) =
let t = t.ToUniversalTime()
let now = DateTime.Now.ToUniversalTime()
let dDay = (now.Date - t.Date).Days
if dDay < 0 then // no data available
[]
else
[ urlf t; urlf (t.AddDays -1) ]
let tryGetArchive (threddsUrl: string) (t: DateTime) =
getArchiveUrls (getNorkystUrl threddsUrl) t
|> tryOpenThredds
|> Option.bind (fun (_, ds) ->
tryGetTimeIndex ds t
|> Option.map (fun idx -> (ds, idx)))

51
src/NorShelf.fs Normal file
View File

@@ -0,0 +1,51 @@
module Oceanbox.FvcomKit.NorShelf
open System
open Serilog
open FSharpPlus
open Thredds
open ROMS
let private getNorshelfUrl (threddsUrl: string) (kind: Kind) (mode: Mode) (date: DateTime) =
let fmt (d: DateTime) =
$"{d.Year}%02d{d.Month}%02d{d.Day}T00Z.nc"
let url = threddsUrl + "/sea_norshelf_files"
$"{url}/norshelf_{kind}_{mode}_{fmt date}"
let private getArchiveUrls urlf (t: DateTime) =
let t = t.ToUniversalTime()
let now = DateTime.Now.ToUniversalTime()
let dDay = (now.Date - t.Date).Days
if dDay < -3 then // no data available
[]
elif dDay <= 0 then // forecast, count down from latest
dateRange now -1 |> List.map (urlf Forecast)
elif dDay > 1 then // use analyzed
[ urlf Analyzed t ]
else // limbo land
[ urlf Analyzed t; urlf Forecast (t.AddDays -1) ]
let tryGetArchive threddsUrl avg (t: DateTime) =
let kind = if avg then Avg else Qck
getArchiveUrls (getNorshelfUrl threddsUrl kind) t
|> tryOpenThredds
|> Option.bind (fun (fname, ds) ->
tryGetTimeIndex ds t
|> Option.map (fun idx -> (fname, ds, idx)))
let readArchive file reader =
tryOpenArchive file
|> Option.bind (fun nc ->
try
let data = reader nc
Log.Information $"Read NorShelf data from {file}"
Some data
with
| e ->
Log.Error e.Message
None)
let readRomsGrid fname = readArchive fname readGrid
let readRomsProps fname idx =
readArchive fname ((flip readProps) idx)

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<IsPackable>true</IsPackable>
<PackageId>Oceanbox.FvcomKit</PackageId>
<Authors></Authors>
<Company></Company>
<Version>0.0.1</Version>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="Types.fs"/>
<Compile Include="Grid.fs"/>
<Compile Include="Fvcom.fs"/>
<Compile Include="Thredds.fs"/>
<Compile Include="ROMS.fs"/>
<Compile Include="Interpol.fs"/>
<Compile Include="NorKyst.fs"/>
<Compile Include="NorShelf.fs"/>
<Compile Include="Smoothing.fs"/>
</ItemGroup>
<ItemGroup>
<PackageReference Update="FSharp.Core" Version="6.0.4-beta.22181.2"/>
<PackageReference Include="FSharp.Data" Version="4.2.7" />
<PackageReference Include="FSharpPlus" Version="1.2.2" />
<PackageReference Include="KDTree" Version="1.4.1"/>
<PackageReference Include="MathNet.Numerics.FSharp" Version="5.0.0"/>
<PackageReference Include="ProjNet.FSharp" Version="3.0.0"/>
<PackageReference Include="sdslite" Version="2.2.0"/>
<PackageReference Include="Serilog" Version="2.11.0-dev-01380"/>
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.2-dev-00890"/>
<PackageReference Include="Serilog.Sinks.Seq" Version="5.1.2-dev-00222"/>
<PackageReference Include="Thoth.Json.Net" Version="8.0.0"/>
</ItemGroup>
</Project>

376
src/ROMS.fs Normal file
View File

@@ -0,0 +1,376 @@
module Oceanbox.FvcomKit.ROMS
open System.Diagnostics
open Serilog
open Microsoft.Research.Science.Data
open MathNet.Numerics.LinearAlgebra
open KdTree
open Grid
open Types
type GridData<'a> = { rho: 'a; u: 'a; v: 'a }
type private CropBox = (float * float) * (float * float)
type private Mask = bool [,]
type private PosVec = (float * float) []
type private RomsPos = float [,] * float [,]
type GridMask = GridData<bool [,]>
type GridPos = GridData<RomsPos>
type GridVars = GridData<float [,]>
type RomsGrid =
{
h: GridVars
z: GridVars []
pos: GridPos
wetMask: GridMask
angle: float [,]
}
type RomsProps =
{
salt: float [,] []
temp: float [,] []
zeta: float [,]
u: float [,] []
v: float [,] []
}
type AdjoinedProp =
{
salt: float [] []
temp: float [] []
zeta: float []
u: float [] []
v: float [] []
}
let private lngLatToUtm33 = ProjNet.FSharp.WGS84_TO_UTM 33
let private toUtm33 ((lng, lat): float [,] * float [,]) =
lng
|> Array2D.mapi (fun i j lon -> lon, lat[i, j])
|> Array2D.map lngLatToUtm33.project
|> unzip2D
let private layerZ csR (vars: GridVars) =
let mult m x = Array2D.map ((*) x) m
let rho = csR |> Array.map (mult vars.rho)
let u = csR |> Array.map (mult vars.u)
let v = csR |> Array.map (mult vars.v)
Array.zip3 rho u v
|> Array.map (fun (a, b, c) -> { rho = a; u = b; v = c })
// convert fractional ROMS landmask to ocean mask
let readWetMask (nc: DataSet) =
let isWet = Array2D.map ((=) 1.)
{
rho = nc[ "mask_rho" ].GetData() :?> float [,] |> isWet
u = nc[ "mask_u" ].GetData() :?> float [,] |> isWet
v = nc[ "mask_v" ].GetData() :?> float [,] |> isWet
}
let private readProp3 (nc: DataSet) (p: string) t (prop: string) =
let nEta = nc.Dimensions[$"eta_{p}"].Length
let nXi = nc.Dimensions[$"xi_{p}"].Length
let scaling =
nc.Variables[prop].Metadata["scale_factor"] :?> single
|> float
let offset =
nc.Variables[prop].Metadata["add_offset"] :?> single
|> float
let o = [| t; 0; 0 |]
let shp = [| 1; nEta; nXi |]
let convert (x: int16 [,,]) =
x[0, *, *]
|> Array2D.map (fun x -> float x * scaling + offset)
nc[ prop ].GetData(o, shp) :?> int16 [,,]
|> convert
let private readProp4 (nc: DataSet) (p: string) t (prop: string) =
let nEta = nc.Dimensions[$"eta_{p}"].Length
let nXi = nc.Dimensions[$"xi_{p}"].Length
let nS = nc.Dimensions["s_rho"].Length
let scaling =
nc.Variables[prop].Metadata["scale_factor"] :?> single
|> float
let offset =
nc.Variables[prop].Metadata["add_offset"] :?> single
|> float
let o = [| t; 0; 0; 0 |]
let shp = [| 1; nS; nEta; nXi |]
let convert (x: int16 [,,,]) =
x[0, *, *, *]
|> Array3D.map (fun x -> float x * scaling + offset)
|> fun m ->
[|
for z = 0 to nS - 1 do
m[z, *, *]
|]
nc[ prop ].GetData(o, shp) :?> int16 [,,,]
|> convert
let readProps (nc: DataSet) t : RomsProps =
Log.Information "Reading ROMS props..."
let r3 = readProp3 nc "rho" t
let r4 = readProp4 nc "rho" t
{
salt = r4 "salt"
temp = r4 "temp"
zeta = r3 "zeta"
u = readProp4 nc "u" t "u"
v = readProp4 nc "v" t "v"
}
let readVerticalGrid path =
let nc =
match Thredds.tryOpenArchive path with
| Some ds -> ds
| None -> failwith "open vertical grid failed"
nc[ "Cs_r" ].GetData() :?> float [] |> Array.rev
let readGrid (nc: DataSet) =
Log.Information "Reading ROMS grid..."
let hRho =
nc[ "h" ].GetData() :?> float [,]
|> Matrix.Build.DenseOfArray
let csR = nc[ "Cs_r" ].GetData() :?> float [] |> Array.rev
let e1, e2 = hRho.ColumnCount - 2, hRho.RowCount - 2
let hU = (hRho[*, 1..] + hRho[*, ..e1]) / 2.
let hV = (hRho[1.., *] + hRho[..e2, *]) / 2.
let pos: GridPos =
{
rho = nc[ "lon_rho" ].GetData() :?> float [,], nc[ "lat_rho" ].GetData() :?> float [,]
u = nc[ "lon_u" ].GetData() :?> float [,], nc[ "lat_u" ].GetData() :?> float [,]
v = nc[ "lon_v" ].GetData() :?> float [,], nc[ "lat_v" ].GetData() :?> float [,]
}
let angle = nc[ "angle" ].GetData() :?> float [,]
let h = { rho = hRho.ToArray(); u = hU.ToArray(); v = hV.ToArray() }
{
h = h
z = layerZ csR h
pos = pos
wetMask = readWetMask nc
angle = angle
}
let private genCropIdx ((min, max): float * float) (m: float [,]) =
[|
for i = 0 to Array2D.length1 m - 1 do
for j = 0 to Array2D.length2 m - 1 do
if m[i, j] >= min && m[i, j] <= max then i, j else ()
|]
|> Set.ofArray
let private genCropMask ((xlim, ylim): CropBox) ((x, y): RomsPos) =
let ind1 = genCropIdx xlim x
let ind2 = genCropIdx ylim y
Set.intersect ind1 ind2 |> Set.toArray
// compute a padded fvcom bounding box for cropping roms grids
let private mkCropBox (box: BBox) : CropBox =
let pad min max =
let d = 5000.0
float min - d, float max + d
// let p = toUtm33 roms
let xlim = pad box.minX box.maxX
let ylim = pad box.minY box.maxY
xlim, ylim
// make an array of indeces of wet roms grid points inside the fvcom domain
let private genCullIdx (box: CropBox) (wet: Mask) (p: RomsPos) =
genCropMask box p
|> Array.filter (fun (i, j) -> wet[i, j])
// crop roms grid coordinates based in index mask of active, overlapping points
let private cullCoords (p: RomsPos) (idx: (int * int) []) =
let x, y = p
let x' = idx |> Array.map (fun (i, j) -> x[i, j])
let y' = idx |> Array.map (fun (i, j) -> y[i, j])
Array.zip x' y'
// make a kd-tree for fast nearest neighbour lookup
let private buildTree (points: (float * float) array) =
let tree = KdTree<float, int>(2, KdTree.Math.DoubleMath())
points
|> Array.iteri (fun n (x, y) -> tree.Add([| x; y |], n) |> ignore)
tree.Balance()
tree
// adjoinIdx: index to culled roms grid for each fvcom node
// cullIdx: index to roms points _actually_ in use
// oobIdx: out-of-bounds points, too far from a roms cell
type FvcomAdjoint = { adjoinIdx: int []; cullIdx: (int * int) []; oobIdx: int [] }
let private dist (a: float * float) (b: float * float) =
let x0, y0 = a
let x1, y1 = b
let dX = x1 - x0
let dY = y1 - y0
dX ** 2 + dY ** 2 |> sqrt |> (*) 0.5
let private distLngLat (a: float * float) (b: float * float) =
let x0, y0 = a |> lngLatToUtm33.project
let x1, y1 = b |> lngLatToUtm33.project
let dX = x1 - x0
let dY = y1 - y0
dX ** 2 + dY ** 2 |> sqrt |> (*) 0.5
let private genOobIdx (tree: KdTree<float, int>) (cullIdx: (int * int) []) (fPos: PosVec) (rPos: RomsPos) =
let rLon, rLat = rPos
let dMax = distLngLat (rLon[0, 0], rLat[0, 0]) (rLon[0, 1], rLat[0, 1])
fPos
|> Array.fold
(fun (n, a) (x, y as p0) ->
tree.GetNearestNeighbours([| x; y |], 1)
|> fun p ->
let i0, i1 = cullIdx[p[0].Value]
let p1 = lngLatToUtm33.project ((rLon[i0, i1], rLat[i0, i1]))
let d = dist p0 p1
if d > dMax then n + 1, n :: a else n + 1, a)
(0, [])
|> snd
|> Array.ofList
// compute the nearest neighbour index into the cropped roms grid
let private mkFvcomAdjoint (fPos: PosVec) (bbox: BBox) ((rPos, wet): RomsPos * Mask) =
let box = mkCropBox bbox
let pos' = toUtm33 rPos
let cullIdx = genCullIdx box wet pos'
let tree = cullCoords pos' cullIdx |> buildTree
let nearest =
fPos
|> Array.map (fun (x, y) ->
tree.GetNearestNeighbours([| x; y |], 1)
|> fun x -> x[0].Value)
{
adjoinIdx = nearest
cullIdx = cullIdx
oobIdx = genOobIdx tree cullIdx fPos rPos
}
let private getNearestNode (fvcom: Grid) (roms: RomsPos * Mask) =
let fvNodes = Array.map (fun (x, y) -> float x, float y) fvcom.Nodes
mkFvcomAdjoint fvNodes fvcom.BBox roms
// same as getNearestNode, but for uv in the center of a cell/element
let private getNearestCell (fvcom: Grid) (roms: RomsPos * Mask) =
let fvCells = Array.map (fun (x, y) -> float x, float y) fvcom.Cells
mkFvcomAdjoint fvCells fvcom.BBox roms
// pick out elements actually in use
let inline private cullRomsMatrix (culler: (int * int) []) (m: 'a [,]) =
culler |> Array.map (fun (i, j) -> m[i, j])
// adjoin fvcom and roms data based on nearest neighbours
let private adjoinMatrix (adj: FvcomAdjoint) (m: 'a [,]) =
let x = cullRomsMatrix adj.cullIdx m
adj.adjoinIdx |> Array.map (fun n -> x[n])
type IRomsToFvcom =
abstract uv: bool
abstract rhoToFvcom: 'a [,] -> 'a []
abstract uToFvcom: 'a [,] -> 'a []
abstract vToFvcom: 'a [,] -> 'a []
abstract rhoAdjoint: FvcomAdjoint
abstract uAdjoint: FvcomAdjoint
abstract vAdjoint: FvcomAdjoint
type AdjoinedGrid =
{
h: float []
zRho: float [] []
zU: float [] []
zV: float [] []
angle: float []
u: float []
v: float []
}
let adjoinGirds (r2f: IRomsToFvcom) (roms: RomsGrid) =
Log.Information "Adjoining grids."
{
h = r2f.rhoToFvcom roms.h.rho
zRho =
roms.z
|> Array.map (fun h -> r2f.rhoToFvcom h.rho)
zU = roms.z |> Array.map (fun h -> r2f.uToFvcom h.u)
zV = roms.z |> Array.map (fun h -> r2f.vToFvcom h.v)
angle = r2f.rhoToFvcom roms.angle
u = r2f.uToFvcom roms.h.u
v = r2f.vToFvcom roms.h.v
}
let adjoinProps (r2f: IRomsToFvcom) (props: RomsProps) =
Log.Information "Adjoining props."
{
salt = Array.map r2f.rhoToFvcom props.salt
temp = Array.map r2f.rhoToFvcom props.temp
zeta = r2f.rhoToFvcom props.zeta
u = Array.map r2f.uToFvcom props.u
v = Array.map r2f.vToFvcom props.v
}
let genRomsToFvcomAdjoints (fvcom: Grid) (roms: RomsGrid) =
let rhoM = getNearestNode fvcom (roms.pos.rho, roms.wetMask.rho)
let uvMappings =
let uM = getNearestCell fvcom (roms.pos.u, roms.wetMask.u)
let vM = getNearestCell fvcom (roms.pos.v, roms.wetMask.v)
uM, vM
{ new IRomsToFvcom with
member x.uv = true
member x.rhoToFvcom m = adjoinMatrix rhoM m
member x.uToFvcom m = adjoinMatrix (fst uvMappings) m
member x.vToFvcom m = adjoinMatrix (snd uvMappings) m
member x.rhoAdjoint = rhoM
member x.uAdjoint = fst uvMappings
member x.vAdjoint = snd uvMappings
}
let adjoinRomsToFvcom (fvcom: Grid) (roms: RomsGrid) (props: RomsProps) =
let r2f = genRomsToFvcomAdjoints fvcom roms
adjoinGirds r2f roms, adjoinProps r2f props
let computeAngles (grid: Grid) (adj: AdjoinedGrid) =
let a = adj.angle
grid.Elem
|> Array.map (fun (i, j, k) -> (a[i] + a[j] + a[k]) / 3.0)
let calcUVBar (fvcom: Grid) (u, v) =
let h = fvcom.Bathymetry
let s = fvcom.Siglev
let nZ = Array2D.length1 s
let hc =
fvcom.Elem
|> Array.map (fun (i, j, k) -> (h[i] + h[j] + h[k] |> float) / 3.0)
|> vector
let sigz =
[|
for z = 0 to nZ - 1 do
fvcom.Elem
|> Array.mapi (fun n (i, j, k) ->
hc[n] * (s[z, i] + s[z, j] + s[z, k] |> float)
/ 3.0)
|]
|> matrix
let dz =
sigz.ToColumnArrays()
|> Array.map (
Array.pairwise
>> Array.map (fun (x0, x1) -> x1 - x0)
)
|> matrix
|> fun x -> x.PointwiseAbs().Transpose()
let bar (m: Matrix<float>) =
m.PointwiseMultiply dz
|> fun x ->
let s = x.ColumnSums()
s.PointwiseDivide hc
let u' = Matrix.Build.DenseOfRowArrays u
let v' = Matrix.Build.DenseOfRowArrays v
bar u', bar v'

130
src/Smoothing.fs Normal file
View File

@@ -0,0 +1,130 @@
module Oceanbox.FvcomKit.Smoothing
open Fvcom
open Serilog
open Grid
let private OOBVAL = -100000f
let smooth (prop: single []) source =
source
|> Array.fold (fun a x -> a + prop[x]) 0f
|> fun x -> x / single source.Length
let smooth' (prop: single []) source =
source
|> Array.fold (fun (n, a) x -> if prop[x] > OOBVAL then n + 1, a + prop[x] else n, a) (0, 0f)
|> fun (n, a) -> a / single n
let smoothNodes (iGrid: IndexedFvcomGrid) (prop: single []) =
iGrid.NodesAroundNode.Values
|> Seq.toArray
|> Array.Parallel.map (smooth prop)
let smoothNodes2D (iGrid: IndexedFvcomGrid) (prop: single [,]) =
for k = 0 to Array2D.length1 prop - 1 do
Log.Debug $"2D smoothing nodes z = {k}"
prop[k, *] <- iGrid.NodesAroundNode.Values
|> Seq.toArray
|> Array.Parallel.map (smooth prop[k, *])
prop
let smoothNodes3D (iGrid: IndexedFvcomGrid) (prop: single [,,]) =
for k = 0 to Array3D.length1 prop - 1 do
for n = 0 to Array3D.length2 prop - 1 do
Log.Debug $"3D smoothing nodes z = {n}"
prop[k, n, *] <- iGrid.NodesAroundNode.Values
|> Seq.toArray
|> Array.Parallel.map (smooth prop[k, n, *])
prop
let smoothElements (iGrid: IndexedFvcomGrid) (prop: single []) =
iGrid.Grid.Elem
|> Array.Parallel.mapi (fun n _ ->
let elems = Grid.getElemsSurroundingElem iGrid n
smooth prop elems)
let smoothElements2D (iGrid: IndexedFvcomGrid) (prop: single [,]) =
for k = 0 to Array2D.length1 prop - 1 do
Log.Debug $"2D smoothing elements z = {k}"
prop[k, *] <- smoothElements iGrid prop[k, *]
prop
let smoothElements3D (iGrid: IndexedFvcomGrid) (prop: single [,,]) =
for k = 0 to Array3D.length1 prop - 1 do
for n = 0 to Array3D.length2 prop - 1 do
Log.Debug $"3D smoothing elements z = {n}"
prop[k, n, *] <- smoothElements iGrid prop[k, n, *]
prop
let private rectifyOob (getSurrounding: int -> int []) (oob: int []) (prop: single []) =
for i in oob do
prop[i] <- OOBVAL // reset
let rec rectify (oob: int []) =
Log.Debug $"rectify oob remaining {oob.Length}"
for n in oob do
let ns = getSurrounding n
prop[n] <- smooth' prop ns
oob
|> Array.fold (fun a x -> if prop[x] > OOBVAL then a else x :: a) []
|> Array.ofList
|> fun x -> if x.Length > 0 then rectify x else ()
rectify oob
prop
let rectifyOutOfBoundsNodes (iGrid: IndexedFvcomGrid) (oob: int []) (prop: single []) =
let f = Grid.getNodesSurroundingNode iGrid
rectifyOob f oob prop
let rectifyOutOfBoundsNodes2D (iGrid: IndexedFvcomGrid) (oob: int []) (prop: single [,]) =
let f = Grid.getNodesSurroundingNode iGrid
for k = 0 to Array2D.length1 prop - 1 do
prop[k, *] <- rectifyOob f oob prop[k, *]
prop
let rectifyOutOfBoundsNodes3D (iGrid: IndexedFvcomGrid) (oob: int []) (prop: single [,,]) =
let f = Grid.getNodesSurroundingNode iGrid
for k = 0 to Array3D.length1 prop - 1 do
for n = 0 to Array3D.length2 prop - 1 do
prop[k, n, *] <- rectifyOob f oob prop[k, n, *]
prop
let rectifyOutOfBoundsElements (iGrid: IndexedFvcomGrid) (oob: int []) (prop: single []) =
let f = Grid.getElemsSurroundingElem iGrid
rectifyOob f oob prop
let rectifyOutOfBoundsElements2D (iGrid: IndexedFvcomGrid) (oob: int []) (prop: single [,]) =
let f = Grid.getElemsSurroundingElem iGrid
for k = 0 to Array2D.length1 prop - 1 do
prop[k, *] <- rectifyOob f oob prop[k, *]
prop
let rectifyOutOfBoundsElements3D (iGrid: IndexedFvcomGrid) (oob: int []) (prop: single [,,]) =
let f = Grid.getElemsSurroundingElem iGrid
for k = 0 to Array3D.length1 prop - 1 do
for n = 0 to Array3D.length2 prop - 1 do
prop[k, n, *] <- rectifyOob f oob prop[k, n, *]
prop
let rectifyOutOfBoundProps fvcom rGrid iGrid (x: FvcomRestart) =
let a = ROMS.genRomsToFvcomAdjoints fvcom rGrid
{ x with
salinity = rectifyOutOfBoundsNodes3D iGrid a.rhoAdjoint.oobIdx x.salinity
temp = rectifyOutOfBoundsNodes3D iGrid a.rhoAdjoint.oobIdx x.temp
zeta = rectifyOutOfBoundsNodes2D iGrid a.rhoAdjoint.oobIdx x.zeta
u = rectifyOutOfBoundsElements3D iGrid a.uAdjoint.oobIdx x.u
v = rectifyOutOfBoundsElements3D iGrid a.vAdjoint.oobIdx x.v
ua = rectifyOutOfBoundsElements2D iGrid a.uAdjoint.oobIdx x.ua
va = rectifyOutOfBoundsElements2D iGrid a.vAdjoint.oobIdx x.va
}
let smoothProps iGrid (x: FvcomRestart) =
{ x with
salinity = smoothNodes3D iGrid x.salinity
temp = smoothNodes3D iGrid x.temp
zeta = smoothNodes2D iGrid x.zeta
u = smoothElements3D iGrid x.u
v = smoothElements3D iGrid x.v
ua = smoothElements2D iGrid x.ua
va = smoothElements2D iGrid x.va
}

61
src/Thredds.fs Normal file
View File

@@ -0,0 +1,61 @@
module Oceanbox.FvcomKit.Thredds
open System
open Microsoft.Research.Science.Data
open Microsoft.Research.Science.Data.NetCDF4
open Serilog
type Mode =
| Analyzed
| Forecast
override this.ToString() =
match this with
| Analyzed -> "an"
| Forecast -> "fc"
type Kind =
| Avg
| Qck
override this.ToString() =
match this with
| Avg -> "avg"
| Qck -> "qck"
let dateRange (start: DateTime) days =
if days < 0 then
List.unfold (fun d -> if d < days then None else Some(start.AddDays d, d - 1)) 0
else
List.unfold (fun d -> if d > days then None else Some(start.AddDays d, d + 1)) 0
let tryOpenArchive url =
let uri = NetCDFUri()
uri.Url <- url
try
let ds = NetCDFDataSet.Open uri
Some ds
with
| e ->
Log.Debug e.Message
None
let rec tryOpenThredds =
function
| [] -> None
| x :: xs ->
match tryOpenArchive x with
| Some ds ->
Log.Debug $"thredds: {x}"
Some(x, ds)
| None -> tryOpenThredds xs
let tryGetTimeIndex (ds: DataSet) (t: DateTime) =
let ot = ds[ "ocean_time" ].GetData() :?> double []
let t0 = DateTimeOffset.FromUnixTimeSeconds(ot[0] |> int64)
let t1 = DateTimeOffset.FromUnixTimeSeconds(ot[^0] |> int64)
Log.Debug $"t={t} t0={t0} tn={t1}"
if t < t0.DateTime || t > t1.DateTime then
Log.Error "time is out of bounds"
None
else
let dt = t - t0.DateTime
dt.Days * 24 + dt.Hours |> Some

33
src/Types.fs Normal file
View File

@@ -0,0 +1,33 @@
module Oceanbox.FvcomKit.Types
open System
let unzip2D (array: _ [,]) =
let len1 = Array2D.length1 array
let len2 = Array2D.length2 array
let res1 = Array2D.zeroCreate len1 len2
let res2 = Array2D.zeroCreate len1 len2
for i = 0 to len1 - 1 do
for j = 0 to len2 - 1 do
let x, y = array[i, j]
res1[i, j] <- x
res2[i, j] <- y
res1, res2
let inline propTo3D (p: 'a [,]) =
let z = Array2D.length1 p
let n = Array2D.length2 p
let m = Array3D.zeroCreate 1 z n
for i = 0 to z - 1 do
for j = 0 to n - 1 do
m[0, i, j] <- p[i, j] |> single
m
let inline propTo2D (p: 'a []) =
let n = p.Length
let m = Array2D.zeroCreate 1 n
for i = 0 to n - 1 do
m[0, i] <- p[i] |> single
m

409
src/packages.lock.json Normal file
View File

@@ -0,0 +1,409 @@
{
"version": 1,
"dependencies": {
"net6.0": {
"FSharp.Core": {
"type": "Direct",
"requested": "[6.0.4-beta.22181.2, )",
"resolved": "6.0.4-beta.22181.2",
"contentHash": "r2po3cDh7SvAQJAozPxlThH/aReaRMl0W5AqeMBE/pbLcJWoeavYpRWhs5VYdqJynxe9h2sgKvLIXa7zp7QhUw=="
},
"FSharp.Data": {
"type": "Direct",
"requested": "[4.2.7, )",
"resolved": "4.2.7",
"contentHash": "gQO0u0q1z9wXOkSmL7TVQLspAGR/S2Vm3CDPStEHCcTQyivkgoZie0IsB2Zyl2inC+hmQa/jcVQNQjo7XB7Ujg==",
"dependencies": {
"FSharp.Core": "4.7.2"
}
},
"FSharpPlus": {
"type": "Direct",
"requested": "[1.2.2, )",
"resolved": "1.2.2",
"contentHash": "YzWFuAua/OCevT05FoWSInP3i6DGAwRqyJd0DtFWSisPI6LAqCMTWaj5hT4BDLDabvricVZ5VD/FWfSNubL6Gg==",
"dependencies": {
"FSharp.Core": "4.6.2"
}
},
"KdTree": {
"type": "Direct",
"requested": "[1.4.1, )",
"resolved": "1.4.1",
"contentHash": "yWbb35v/V9y88SLLMUPTlAN3pQEoPhDfZf9PApFnlU4kLtwVQ75U9vW5mW4/alQnLBuLKWBKcy4W5xK95mYsuA=="
},
"MathNet.Numerics.FSharp": {
"type": "Direct",
"requested": "[5.0.0, )",
"resolved": "5.0.0",
"contentHash": "lKYhd68fReW5odX/q+Uzxw3357Duq3zmvkYvnZVqqcc2r/EmrYGDoOdUGuHnhfr8yj9V34js5gQH/7IWcxZJxg==",
"dependencies": {
"FSharp.Core": "6.0.2",
"MathNet.Numerics": "5.0.0"
}
},
"ProjNet.FSharp": {
"type": "Direct",
"requested": "[3.0.0, )",
"resolved": "3.0.0",
"contentHash": "TLBC2fzyuMMiAICWTKVc7JA3az8kd/zvA8/wGtQFiQROW8yj0hy/i8d9DplJM73KOrsc5UacxDgzIkFvJSXyWQ==",
"dependencies": {
"FSharp.Core": "6.0.2",
"FSharp.Data": "4.2.7",
"FSharpPlus": "1.2.2",
"ProjNet": "2.0.0"
}
},
"SDSLite": {
"type": "Direct",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "g4y+GyW8sAfF4b5Kwm0sTw/07rPHrtcgxqb7KzUxJsU8C7+k0GpCZwKc6Qs3wPgzKnAkdQfpP0Fa+Yrjkvxjhg==",
"dependencies": {
"DynamicInterop": "0.9.1"
}
},
"Serilog": {
"type": "Direct",
"requested": "[2.11.0-dev-01380, )",
"resolved": "2.11.0-dev-01380",
"contentHash": "isf3iNN0XMZZWQx2rkQL0TG9MOPgUGHb93o6i4p/qOkj7dtRBD+STFyNj3DQcJWe25uMjJkOG/Y+9sFD1bR4vQ=="
},
"Serilog.Sinks.Console": {
"type": "Direct",
"requested": "[4.0.2-dev-00890, )",
"resolved": "4.0.2-dev-00890",
"contentHash": "5l0OWV0dk0fAzascJUY0tWrAPqrleSs2uhZzW2y9iYXFYtEfMBPTi1+UHPO6Pa76Z4ZG06s2mpGfjAdPSpIxNw==",
"dependencies": {
"Serilog": "2.10.0"
}
},
"Serilog.Sinks.Seq": {
"type": "Direct",
"requested": "[5.1.2-dev-00222, )",
"resolved": "5.1.2-dev-00222",
"contentHash": "Ef/wXQM2/jj7YK1xsiew5SUkHoORVLxvTyYWrqls+3SgKrWWvrOksVuRADhAZsGihZZ1hOMDrN00y4ycQl10NQ==",
"dependencies": {
"Serilog": "2.10.0",
"Serilog.Formatting.Compact": "1.1.0",
"Serilog.Sinks.File": "4.0.0",
"Serilog.Sinks.PeriodicBatching": "2.3.0"
}
},
"Thoth.Json.Net": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "C/b+8g/xUTJTn7pbKC4bMAOy2tyolXAuHTXguT5TNzDKQ6sjnUfFa9B81fTt9PuUOdWFLyRKlXASuFhSQciJGQ==",
"dependencies": {
"FSharp.Core": "4.7.2",
"Fable.Core": "[3.0.0, 4.0.0)",
"Newtonsoft.Json": "11.0.2"
}
},
"DynamicInterop": {
"type": "Transitive",
"resolved": "0.9.1",
"contentHash": "n21+Hd+tceX8lgaOosPV+Pne+YqnZUd5RLW3OhnsVxWRzYXiAIAKmKweHIePYeY+fmcn3N5tjkJyQUccFuL3bg=="
},
"Fable.Core": {
"type": "Transitive",
"resolved": "3.0.0",
"contentHash": "pkCOWJKAkCk36f5+q4F3XqlfsgCJL6i2lTLl4ZZVDswn8rjXo21EBG/gJ296a88LVBkI5LL2VwxQYqGZncomJw==",
"dependencies": {
"FSharp.Core": "4.5.2"
}
},
"MathNet.Numerics": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "pg1W2VwaEQMAiTpGK840hZgzavnqjlCMTVSbtVCXVyT+7AX4mc1o89SPv4TBlAjhgCOo9c1Y+jZ5m3ti2YgGgA=="
},
"Microsoft.NETCore.Platforms": {
"type": "Transitive",
"resolved": "1.0.1",
"contentHash": "2G6OjjJzwBfNOO8myRV/nFrbTw5iA+DEm0N+qUqhrOmaVtn4pC77h38I1jsXGw5VH55+dPfQsqHD0We9sCl9FQ=="
},
"Microsoft.NETCore.Targets": {
"type": "Transitive",
"resolved": "1.0.1",
"contentHash": "rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw=="
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "11.0.2",
"contentHash": "IvJe1pj7JHEsP8B8J8DwlMEx8UInrs/x+9oVY+oCD13jpLu4JbJU2WCIsMRn5C4yW9+DgkaO8uiVE5VHKjpmdQ=="
},
"ProjNET": {
"type": "Transitive",
"resolved": "2.0.0",
"contentHash": "iMJG8qpGJ8SjFrB044O8wgo0raAWCdG1Bvly0mmVcjzsrexDHhC+dUct6Wb1YwQtupMBjSTWq7Fn00YeNErprA==",
"dependencies": {
"System.Memory": "4.5.3",
"System.Numerics.Vectors": "4.5.0"
}
},
"Serilog.Formatting.Compact": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "pNroKVjo+rDqlxNG5PXkRLpfSCuDOBY0ri6jp9PLe505ljqwhwZz8ospy2vWhQlFu5GkIesh3FcDs4n7sWZODA==",
"dependencies": {
"Serilog": "2.8.0"
}
},
"Serilog.Sinks.File": {
"type": "Transitive",
"resolved": "4.0.0",
"contentHash": "vBj43RkAbeP1dzoPFR2+LfV5GevDRPDq6265JJBv223lMvT9rfdwe/S/I9ow7aZSLYKfw4qPDw6NW8YwjbDbvg==",
"dependencies": {
"Serilog": "2.5.0",
"System.IO": "4.1.0",
"System.IO.FileSystem": "4.0.1",
"System.IO.FileSystem.Primitives": "4.0.1",
"System.Runtime.InteropServices": "4.1.0",
"System.Text.Encoding.Extensions": "4.0.11",
"System.Threading": "4.0.11",
"System.Threading.Timer": "4.0.1"
}
},
"Serilog.Sinks.PeriodicBatching": {
"type": "Transitive",
"resolved": "2.3.0",
"contentHash": "UYKSjTMTlUY9T3OgzMmLDLD+z0qPfgvq/RvG0rKfyz+O+Zrjw3X/Xpv14J4WMcGVsOjUaR+k8n2MdmqVpJtI6A==",
"dependencies": {
"Serilog": "2.0.0",
"System.Collections.Concurrent": "4.0.12",
"System.Threading.Timer": "4.0.1"
}
},
"System.Collections": {
"type": "Transitive",
"resolved": "4.0.11",
"contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.Runtime": "4.1.0"
}
},
"System.Collections.Concurrent": {
"type": "Transitive",
"resolved": "4.0.12",
"contentHash": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==",
"dependencies": {
"System.Collections": "4.0.11",
"System.Diagnostics.Debug": "4.0.11",
"System.Diagnostics.Tracing": "4.1.0",
"System.Globalization": "4.0.11",
"System.Reflection": "4.1.0",
"System.Resources.ResourceManager": "4.0.1",
"System.Runtime": "4.1.0",
"System.Runtime.Extensions": "4.1.0",
"System.Threading": "4.0.11",
"System.Threading.Tasks": "4.0.11"
}
},
"System.Diagnostics.Debug": {
"type": "Transitive",
"resolved": "4.0.11",
"contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.Runtime": "4.1.0"
}
},
"System.Diagnostics.Tracing": {
"type": "Transitive",
"resolved": "4.1.0",
"contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.Runtime": "4.1.0"
}
},
"System.Globalization": {
"type": "Transitive",
"resolved": "4.0.11",
"contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.Runtime": "4.1.0"
}
},
"System.IO": {
"type": "Transitive",
"resolved": "4.1.0",
"contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.Runtime": "4.1.0",
"System.Text.Encoding": "4.0.11",
"System.Threading.Tasks": "4.0.11"
}
},
"System.IO.FileSystem": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "IBErlVq5jOggAD69bg1t0pJcHaDbJbWNUZTPI96fkYWzwYbN6D9wRHMULLDd9dHsl7C2YsxXL31LMfPI1SWt8w==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.IO": "4.1.0",
"System.IO.FileSystem.Primitives": "4.0.1",
"System.Runtime": "4.1.0",
"System.Runtime.Handles": "4.0.1",
"System.Text.Encoding": "4.0.11",
"System.Threading.Tasks": "4.0.11"
}
},
"System.IO.FileSystem.Primitives": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "kWkKD203JJKxJeE74p8aF8y4Qc9r9WQx4C0cHzHPrY3fv/L/IhWnyCHaFJ3H1QPOH6A93whlQ2vG5nHlBDvzWQ==",
"dependencies": {
"System.Runtime": "4.1.0"
}
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.3",
"contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA=="
},
"System.Numerics.Vectors": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
},
"System.Reflection": {
"type": "Transitive",
"resolved": "4.1.0",
"contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.IO": "4.1.0",
"System.Reflection.Primitives": "4.0.1",
"System.Runtime": "4.1.0"
}
},
"System.Reflection.Primitives": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.Runtime": "4.1.0"
}
},
"System.Resources.ResourceManager": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.Globalization": "4.0.11",
"System.Reflection": "4.1.0",
"System.Runtime": "4.1.0"
}
},
"System.Runtime": {
"type": "Transitive",
"resolved": "4.1.0",
"contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1"
}
},
"System.Runtime.Extensions": {
"type": "Transitive",
"resolved": "4.1.0",
"contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.Runtime": "4.1.0"
}
},
"System.Runtime.Handles": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.Runtime": "4.1.0"
}
},
"System.Runtime.InteropServices": {
"type": "Transitive",
"resolved": "4.1.0",
"contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.Reflection": "4.1.0",
"System.Reflection.Primitives": "4.0.1",
"System.Runtime": "4.1.0",
"System.Runtime.Handles": "4.0.1"
}
},
"System.Text.Encoding": {
"type": "Transitive",
"resolved": "4.0.11",
"contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.Runtime": "4.1.0"
}
},
"System.Text.Encoding.Extensions": {
"type": "Transitive",
"resolved": "4.0.11",
"contentHash": "jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.Runtime": "4.1.0",
"System.Text.Encoding": "4.0.11"
}
},
"System.Threading": {
"type": "Transitive",
"resolved": "4.0.11",
"contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==",
"dependencies": {
"System.Runtime": "4.1.0",
"System.Threading.Tasks": "4.0.11"
}
},
"System.Threading.Tasks": {
"type": "Transitive",
"resolved": "4.0.11",
"contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.Runtime": "4.1.0"
}
},
"System.Threading.Timer": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "saGfUV8uqVW6LeURiqxcGhZ24PzuRNaUBtbhVeuUAvky1naH395A/1nY0P2bWvrw/BreRtIB/EzTDkGBpqCwEw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.1",
"Microsoft.NETCore.Targets": "1.0.1",
"System.Runtime": "4.1.0"
}
}
}
}
}

18
test/Tests.fs Normal file
View File

@@ -0,0 +1,18 @@
module Tests
open Expecto
let server =
testList
"Server"
[
testCase "Adding valid Todo"
<| fun _ ->
let expectedResult = Ok()
Expect.equal (Ok()) expectedResult "Result should be ok"
]
let all = testList "All" [ server ]
[<EntryPoint>]
let main _ = runTests defaultConfig all

16
test/Tests.fsproj Normal file
View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Tests.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\src\Oceanbox.FvcomKit.fsproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Expecto" Version="9.0.4" />
</ItemGroup>
</Project>