feat: initial commit from Primus
This commit is contained in:
69
.build/Build.fs
Normal file
69
.build/Build.fs
Normal 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
105
.build/Helpers.fs
Normal 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
18
.config/dotnet-tools.json
Normal 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
29
.devcontainer/Dockerfile
Normal 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
|
||||
11
.devcontainer/devcontainer.json
Normal file
11
.devcontainer/devcontainer.json
Normal 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"
|
||||
]
|
||||
}
|
||||
3
.devcontainer/settings.vscode.json
Normal file
3
.devcontainer/settings.vscode.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"FSharp.fsacRuntime":"netcore"
|
||||
}
|
||||
32
.editorconfig
Normal file
32
.editorconfig
Normal 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
17
.gitignore
vendored
Normal 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
9
.gitlab-ci.yml
Normal 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
41
.releaserc.yaml
Normal 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
16
Build.fsproj
Normal 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
21
LICENSE
Normal 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
77
Oceanbox.FvcomKit.sln
Normal 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
9
README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Oceanbox.FvcomKit
|
||||
|
||||
## Run
|
||||
|
||||
`dotnet run`
|
||||
|
||||
## Build
|
||||
|
||||
`dotnet run Bundle`
|
||||
1
RELEASE_NOTES.md
Normal file
1
RELEASE_NOTES.md
Normal file
@@ -0,0 +1 @@
|
||||
# Changelog
|
||||
10009
package-lock.json
generated
Normal file
10009
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
package.json
Normal file
11
package.json
Normal 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
262
src/Fvcom.fs
Normal 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
204
src/Grid.fs
Normal 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
142
src/Interpol.fs
Normal 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
26
src/NorKyst.fs
Normal 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
51
src/NorShelf.fs
Normal 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)
|
||||
38
src/Oceanbox.FvcomKit.fsproj
Normal file
38
src/Oceanbox.FvcomKit.fsproj
Normal 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
376
src/ROMS.fs
Normal 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
130
src/Smoothing.fs
Normal 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
61
src/Thredds.fs
Normal 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
33
src/Types.fs
Normal 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
409
src/packages.lock.json
Normal 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
18
test/Tests.fs
Normal 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
16
test/Tests.fsproj
Normal 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>
|
||||
Reference in New Issue
Block a user