Compare commits

...

22 Commits

Author SHA1 Message Date
semantic-release-bot
a38dba5728 chore(release): 6.0.0
# [6.0.0](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/compare/v5.13.0...v6.0.0) (2026-01-19)

### Bug Fixes

* Update Arome to translate latlon to lambert ([655abeb](655abebe52))
2026-01-19 14:13:19 +00:00
083e5dac5b Merge branch 'simkir/arome' into 'main'
Update Arome for new rossby archives

See merge request oceanbox/Oceanbox.FvcomKit!35
2026-01-19 15:10:29 +01:00
622d9837fd Prerelease 6.0.0-alpha.1 2026-01-07 09:34:08 +01:00
655abebe52 fix: Update Arome to translate latlon to lambert
Also:
- Add test for reading uv from tile coords
- Build with nix
- Pin nix with npins
- Remove .config tools manifest
- Remove preview flag
2026-01-06 10:23:49 +01:00
18a9d70698 breaking: Do not project Arome.tryFind
Do that with tryFindWithProj, where you give the projection yourself,
instead.
2025-12-17 17:40:12 +01:00
semantic-release-bot
aaa597c52e chore(release): 5.13.0
# [5.13.0](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/compare/v5.12.2...v5.13.0) (2025-11-05)

### Features

* Add ElemsAroundElem to NeighborIndex ([71e861e](71e861e417))
2025-11-05 11:09:46 +00:00
Stig Rune Jensen
571302a86b Merge branch 'mrtz/e2e' into 'main'
feat: Add ElemsAroundElem to NeighborIndex

See merge request oceanbox/Oceanbox.FvcomKit!34
2025-11-05 11:07:03 +00:00
71e861e417 feat: Add ElemsAroundElem to NeighborIndex
Also format with Fantomas
2025-11-05 11:39:57 +01:00
semantic-release-bot
bec03ed5ec chore(release): 5.12.2
## [5.12.2](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/compare/v5.12.1...v5.12.2) (2025-09-01)

### Bug Fixes

* Bump Oceanbox.SDSLite to 2.8.0 and use bun for SR ([0c55b2d](0c55b2dcbd))
2025-09-01 13:32:02 +00:00
75ba2abcfe Merge branch 'mrtz/bump-sdslite' into 'main'
fix: Bump Oceanbox.SDSLite to 2.8.0 and use bun for SR

See merge request oceanbox/Oceanbox.FvcomKit!33
2025-09-01 15:28:05 +02:00
0c55b2dcbd fix: Bump Oceanbox.SDSLite to 2.8.0 and use bun for SR 2025-09-01 15:20:16 +02:00
semantic-release-bot
c9b3320464 chore(release): 5.12.1
## [5.12.1](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/compare/v5.12.0...v5.12.1) (2025-05-02)

### Bug Fixes

* include edge point in isInsideTriangle ([c89f35b](c89f35bc6e))
* remove unused double precision function ([8715f4d](8715f4d8c3))
2025-05-02 13:09:26 +00:00
Stig Rune Jensen
bfb3a8e489 Merge branch 'fix/edge-point-in-cell' into 'main'
fix: include edge point in isInsideTriangle

See merge request oceanbox/Oceanbox.FvcomKit!32
2025-05-02 13:06:29 +00:00
8715f4d8c3 fix: remove unused double precision function 2025-05-02 14:36:56 +02:00
c89f35bc6e fix: include edge point in isInsideTriangle
if a point is calculated to be exactly on a triangle edge, consider it
inside the cell
2025-05-02 14:14:26 +02:00
semantic-release-bot
c22ae77301 chore(release): 5.12.0
# [5.12.0](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/compare/v5.11.0...v5.12.0) (2025-03-07)

### Features

* add Grid.toLonLat function ([06a4aea](06a4aeabf1))
2025-03-07 07:02:32 +00:00
c4513c0b09 Merge branch 'main' of gitlab.com:oceanbox/Oceanbox.FvcomKit 2025-03-07 07:59:32 +01:00
06a4aeabf1 feat: add Grid.toLonLat function 2025-03-07 07:59:21 +01:00
semantic-release-bot
65fbf66016 chore(release): 5.11.0
# [5.11.0](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/compare/v5.10.0...v5.11.0) (2025-03-06)

### Bug Fixes

* init sha once explicitly from sha1 byte[] ([33b7b99](33b7b999c8))

### Features

* rename grid sha1 to hash for better generality ([bfbaa3a](bfbaa3aeff))
2025-03-06 07:58:43 +00:00
bfbaa3aeff feat: rename grid sha1 to hash for better generality 2025-03-06 08:55:49 +01:00
ad148f6284 Merge branch 'main' of gitlab.com:oceanbox/Oceanbox.FvcomKit 2025-03-06 08:53:15 +01:00
33b7b999c8 fix: init sha once explicitly from sha1 byte[] 2025-03-06 08:53:07 +01:00
38 changed files with 1637 additions and 3192 deletions

View File

@@ -18,7 +18,7 @@ let versionFile = Path.getFullName ".version"
Target.create "Clean" (fun _ -> Shell.cleanDir distPath)
Target.create "InstallClient" (fun _ ->
run npm "install" "."
run bun "install" "."
run dotnet "tool restore" "."
)

View File

@@ -72,16 +72,16 @@ let createProcess exe arg dir =
|> CreateProcess.ensureExitCode
let dotnet = createProcess "dotnet"
let npm =
let npmPath =
match ProcessUtils.tryFindFileOnPath "npm" with
let bun =
let bunPath =
match ProcessUtils.tryFindFileOnPath "bun" with
| Some path -> path
| None ->
"npm was not found in path. Please install it and make sure it's available from your path. " +
"bun 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
createProcess bunPath
let run proc arg dir =
proc arg dir

View File

@@ -1,20 +0,0 @@
{
"version": 1,
"isRoot": true,
"tools": {
"fantomas": {
"version": "7.0.1",
"commands": [
"fantomas"
],
"rollForward": false
},
"dotnet-outdated-tool": {
"version": "4.6.7",
"commands": [
"dotnet-outdated"
],
"rollForward": false
}
}
}

View File

@@ -1,30 +1,32 @@
FROM mcr.microsoft.com/dotnet/sdk:9.0
# Add keys and sources lists
RUN apt-get update && apt-get install -y ca-certificates gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
ENV NODE_MAJOR=20
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
# Bun version
ARG BUN_INSTALL=/usr/local
ARG BUN_VERSION=bun-v1.2.16
# Install node, 7zip, git, process tools
# Install node, 7zip, yarn, git, process tools
RUN apt-get update \
&& apt-get install -y nodejs p7zip-full git procps ssh-client
&& apt-get install -y p7zip-full git procps ssh-client unzip
# Install Bun
RUN set -eux; \
curl -fsSL https://bun.sh/install > /usr/local/bin/install-bun \
&& chmod +x /usr/local/bin/install-bun \
&& /usr/local/bin/install-bun $BUN_VERSION debug-info
ENV BUN_INSTALL=/usr/local
# 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
# 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
COPY settings.vscode.json /root/.vscode-remote/data/Machine/settings.json

View File

@@ -11,6 +11,10 @@ insert_final_newline = false
indent_size = 2
max_line_length= 80
[*.nix]
indent_size = 2
max_line_length= 80
[*.fs]
max_line_length= 120
@@ -21,6 +25,7 @@ fsharp_space_before_uppercase_invocation = true
fsharp_blank_lines_around_nested_multiline_expressions = false
fsharp_newline_between_type_definition_and_members = false
fsharp_multiline_bracket_style = stroustrup
fsharp_multi_line_lambda_closing_newline = true
fsharp_array_or_list_multiline_formatter = character_width
fsharp_max_array_or_list_width = 70

4
.envrc
View File

@@ -1 +1,3 @@
use_nix
export NPINS_DIRECTORY="nix"
use_nix

4
.gitignore vendored
View File

@@ -15,4 +15,6 @@ deploy
.ionide/
*.db
build.fsx.lock
dist/
dist/
.direnv/
result*

View File

@@ -1,8 +1,11 @@
variables:
DEPLOY_NAME: fvcomkit
SDK_VERSION: 9.0
SKIP_TESTS: "true"
include:
- project: oceanbox/gitlab-ci
ref: v2
file: DotnetPackage.gitlab-ci.yml
ref: v4.1
file: DotnetPackage.gitlab-ci.yml
inputs:
project-name: oceanbox.fvcomkit
project-dir: .

View File

@@ -1 +1 @@
5.10.0
6.0.0

View File

@@ -1,76 +0,0 @@
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
shell.nix = shell.nix
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
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "submodules", "submodules", "{F8275720-F8B1-43A2-B04D-4306D71CC340}"
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(ExtensibilityGlobals) = postSolution
SolutionGuid = {79A6998D-BCE6-4EC5-ADBC-69234C0D2EC5}
EndGlobalSection
EndGlobal

16
Oceanbox.FvcomKit.slnx Normal file
View File

@@ -0,0 +1,16 @@
<Solution>
<Configurations>
<Platform Name="Any CPU" />
<Platform Name="x64" />
<Platform Name="x86" />
</Configurations>
<Folder Name="/Solution Items/">
<File Path="LICENSE" />
<File Path="README.md" />
<File Path="shell.nix" />
</Folder>
<Folder Name="/submodules/" />
<Project Path="Build.fsproj" />
<Project Path="src/Oceanbox.FvcomKit.fsproj" />
<Project Path="xtest/xtest.fsproj" />
</Solution>

View File

@@ -1,5 +1,53 @@
# Changelog
# [6.0.0](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/compare/v5.13.0...v6.0.0) (2026-01-19)
### Bug Fixes
* Update Arome to translate latlon to lambert ([655abeb](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/commit/655abebe526a3587c26429e41130d78f326fa524))
# [5.13.0](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/compare/v5.12.2...v5.13.0) (2025-11-05)
### Features
* Add ElemsAroundElem to NeighborIndex ([71e861e](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/commit/71e861e4174a57d11cfd85ac04f69c51fc985b4c))
## [5.12.2](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/compare/v5.12.1...v5.12.2) (2025-09-01)
### Bug Fixes
* Bump Oceanbox.SDSLite to 2.8.0 and use bun for SR ([0c55b2d](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/commit/0c55b2dcbdb89337abdbe430a30461d09dbb0bd2))
## [5.12.1](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/compare/v5.12.0...v5.12.1) (2025-05-02)
### Bug Fixes
* include edge point in isInsideTriangle ([c89f35b](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/commit/c89f35bc6e316ac19d2de4ef35dbb78302881373))
* remove unused double precision function ([8715f4d](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/commit/8715f4d8c32d04f6d1c77502435234dadd0863ae))
# [5.12.0](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/compare/v5.11.0...v5.12.0) (2025-03-07)
### Features
* add Grid.toLonLat function ([06a4aea](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/commit/06a4aeabf1bcd2824db74a1851f0703797fdacf8))
# [5.11.0](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/compare/v5.10.0...v5.11.0) (2025-03-06)
### Bug Fixes
* init sha once explicitly from sha1 byte[] ([33b7b99](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/commit/33b7b999c8aaff3190dbe8a5e4d3f2e8e1cbf15e))
### Features
* rename grid sha1 to hash for better generality ([bfbaa3a](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/commit/bfbaa3aeff66b39526cf817009729df3fe4966cc))
# [5.10.0](https://gitlab.com/oceanbox/Oceanbox.FvcomKit/compare/v5.9.1...v5.10.0) (2025-03-06)

302
bun.lock Normal file
View File

@@ -0,0 +1,302 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"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",
},
},
},
"packages": {
"@babel/code-frame": ["@babel/code-frame@7.16.7", "", { "dependencies": { "@babel/highlight": "^7.16.7" } }, "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.16.7", "", {}, "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw=="],
"@babel/highlight": ["@babel/highlight@7.16.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@semantic-release/changelog": ["@semantic-release/changelog@6.0.1", "", { "dependencies": { "@semantic-release/error": "^3.0.0", "aggregate-error": "^3.0.0", "fs-extra": "^9.0.0", "lodash": "^4.17.4" } }, "sha512-FT+tAGdWHr0RCM3EpWegWnvXJ05LQtBkQUaQRIExONoXjVjLuOILNm4DEKNaV+GAQyJjbLRVs57ti//GypH6PA=="],
"@semantic-release/error": ["@semantic-release/error@3.0.0", "", {}, "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw=="],
"@semantic-release/exec": ["@semantic-release/exec@6.0.3", "", { "dependencies": { "@semantic-release/error": "^3.0.0", "aggregate-error": "^3.0.0", "debug": "^4.0.0", "execa": "^5.0.0", "lodash": "^4.17.4", "parse-json": "^5.0.0" } }, "sha512-bxAq8vLOw76aV89vxxICecEa8jfaWwYITw6X74zzlO0mc/Bgieqx9kBRz9z96pHectiTAtsCwsQcUyLYWnp3VQ=="],
"@semantic-release/git": ["@semantic-release/git@10.0.1", "", { "dependencies": { "@semantic-release/error": "^3.0.0", "aggregate-error": "^3.0.0", "debug": "^4.0.0", "dir-glob": "^3.0.0", "execa": "^5.0.0", "lodash": "^4.17.4", "micromatch": "^4.0.0", "p-reduce": "^2.0.0" } }, "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w=="],
"@semantic-release/gitlab": ["@semantic-release/gitlab@7.0.4", "", { "dependencies": { "@semantic-release/error": "^3.0.0", "aggregate-error": "^3.0.0", "debug": "^4.0.0", "dir-glob": "^3.0.0", "escape-string-regexp": "^3.0.0", "form-data": "^4.0.0", "fs-extra": "^10.0.0", "globby": "^11.0.0", "got": "^11.0.0", "lodash": "^4.17.11", "parse-path": "^4.0.0", "url-join": "^4.0.0" } }, "sha512-TL6kT526+ir/uehMFdTlJNXUj+p+SjPAYUkit6lh5Rs8kxeHQ01bgmpYLQlc94ZDpy9x2Tzcb/NRwKojkmLG4A=="],
"@sindresorhus/is": ["@sindresorhus/is@4.3.0", "", {}, "sha512-wwOvh0eO3PiTEivGJWiZ+b946SlMSb4pe+y+Ur/4S87cwo09pYi+FWHHnbrM3W9W7cBYKDqQXcrFYjYUCOJUEQ=="],
"@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="],
"@types/cacheable-request": ["@types/cacheable-request@6.0.2", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "*", "@types/node": "*", "@types/responselike": "*" } }, "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA=="],
"@types/glob": ["@types/glob@7.2.0", "", { "dependencies": { "@types/minimatch": "*", "@types/node": "*" } }, "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA=="],
"@types/http-cache-semantics": ["@types/http-cache-semantics@4.0.1", "", {}, "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ=="],
"@types/keyv": ["@types/keyv@3.1.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg=="],
"@types/minimatch": ["@types/minimatch@3.0.5", "", {}, "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ=="],
"@types/node": ["@types/node@17.0.9", "", {}, "sha512-5dNBXu/FOER+EXnyah7rn8xlNrfMOQb/qXnw4NQgLkCygKBKhdmF/CA5oXVOKZLBEahw8s2WP9LxIcN/oDDRgQ=="],
"@types/responselike": ["@types/responselike@1.0.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA=="],
"aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
"ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
"array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha1-x57Zf380y48robyXkLzDZkdLS3k="],
"at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"braces": ["braces@3.0.2", "", { "dependencies": { "fill-range": "^7.0.1" } }, "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A=="],
"cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="],
"cacheable-request": ["cacheable-request@7.0.2", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew=="],
"call-bind": ["call-bind@1.0.2", "", { "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" } }, "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA=="],
"chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
"clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="],
"clone-response": ["clone-response@1.0.2", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws="],
"color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"color-name": ["color-name@1.1.3", "", {}, "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="],
"cross-spawn": ["cross-spawn@7.0.3", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w=="],
"debug": ["debug@4.3.3", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q=="],
"decode-uri-component": ["decode-uri-component@0.2.0", "", {}, "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="],
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
"defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="],
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
"end-of-stream": ["end-of-stream@1.4.4", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="],
"error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="],
"escape-string-regexp": ["escape-string-regexp@3.0.0", "", {}, "sha512-11dXIUC3umvzEViLP117d0KN6LJzZxh5+9F4E/7WLAAw7GrHk8NpUR+g9iJi/pe9C0py4F8rs0hreyRCwlAuZg=="],
"execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
"fast-glob": ["fast-glob@3.2.11", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew=="],
"fastq": ["fastq@1.13.0", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw=="],
"fill-range": ["fill-range@7.0.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ=="],
"filter-obj": ["filter-obj@1.1.0", "", {}, "sha1-mzERErxsYSehbgFsbF1/GeCAXFs="],
"form-data": ["form-data@4.0.0", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww=="],
"fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="],
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="],
"function-bind": ["function-bind@1.1.1", "", {}, "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="],
"get-intrinsic": ["get-intrinsic@1.1.1", "", { "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1" } }, "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q=="],
"get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
"glob": ["glob@7.2.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q=="],
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"glob-promise": ["glob-promise@4.2.2", "", { "dependencies": { "@types/glob": "^7.1.3" }, "peerDependencies": { "glob": "^7.1.6" } }, "sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw=="],
"globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="],
"got": ["got@11.8.3", "", { "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" } }, "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg=="],
"graceful-fs": ["graceful-fs@4.2.9", "", {}, "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ=="],
"has": ["has@1.0.3", "", { "dependencies": { "function-bind": "^1.1.1" } }, "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw=="],
"has-flag": ["has-flag@3.0.0", "", {}, "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="],
"has-symbols": ["has-symbols@1.0.2", "", {}, "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="],
"http-cache-semantics": ["http-cache-semantics@4.1.0", "", {}, "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ=="],
"http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="],
"human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
"ignore": ["ignore@5.2.0", "", {}, "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ=="],
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-ssh": ["is-ssh@1.3.3", "", { "dependencies": { "protocols": "^1.1.0" } }, "sha512-NKzJmQzJfEEma3w5cJNcUMxoXfDjz0Zj0eyCalHn2E6VOwlzjZo0yuO2fcBSf8zhFuVCL/82/r5gRcoi6aEPVQ=="],
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"isexe": ["isexe@2.0.0", "", {}, "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
"jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="],
"keyv": ["keyv@4.0.5", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-531pkGLqV3BMg0eDqqJFI0R1mkK1Nm5xIP2mM6keP5P8WfFtCkg2IOwplTUmlGoTgIg9yQYZ/kdihhz89XH3vA=="],
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="],
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromatch": ["micromatch@4.0.4", "", { "dependencies": { "braces": "^3.0.1", "picomatch": "^2.2.3" } }, "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg=="],
"mime-db": ["mime-db@1.51.0", "", {}, "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g=="],
"mime-types": ["mime-types@2.1.34", "", { "dependencies": { "mime-db": "1.51.0" } }, "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A=="],
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
"minimatch": ["minimatch@3.0.4", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA=="],
"ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
"normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="],
"npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
"object-inspect": ["object-inspect@1.12.0", "", {}, "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha1-WDsap3WWHUsROsF9nFC6753Xa9E="],
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="],
"p-reduce": ["p-reduce@2.1.0", "", {}, "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw=="],
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
"parse-path": ["parse-path@4.0.3", "", { "dependencies": { "is-ssh": "^1.3.0", "protocols": "^1.4.0", "qs": "^6.9.4", "query-string": "^6.13.8" } }, "sha512-9Cepbp2asKnWTJ9x2kpw6Fe8y9JDbqwahGCTvklzd/cEq5C5JC59x2Xb0Kx+x0QZ8bvNquGO8/BWP0cwBHzSAA=="],
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"protocols": ["protocols@1.4.8", "", {}, "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg=="],
"pump": ["pump@3.0.0", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww=="],
"qs": ["qs@6.10.3", "", { "dependencies": { "side-channel": "^1.0.4" } }, "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ=="],
"query-string": ["query-string@6.14.1", "", { "dependencies": { "decode-uri-component": "^0.2.0", "filter-obj": "^1.1.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" } }, "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="],
"resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="],
"responselike": ["responselike@2.0.0", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw=="],
"reusify": ["reusify@1.0.4", "", {}, "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"sax": ["sax@1.2.4", "", {}, "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="],
"semantic-release-dotnet": ["semantic-release-dotnet@1.0.0", "", { "dependencies": { "glob": "^7.1.7", "glob-promise": "^4.2.0", "xml-js": "^1.6.11" } }, "sha512-U/cHwqqzFbJpPCQ/KMTSZtwzPNWCNCVStZRznMGK0xjhiLoDfRe5KFRs/9dzWBtPa358D7IiVzz97ZzepUAtfQ=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"side-channel": ["side-channel@1.0.4", "", { "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", "object-inspect": "^1.9.0" } }, "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw=="],
"signal-exit": ["signal-exit@3.0.6", "", {}, "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ=="],
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"split-on-first": ["split-on-first@1.1.0", "", {}, "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="],
"strict-uri-encode": ["strict-uri-encode@2.0.0", "", {}, "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="],
"strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
"supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"universalify": ["universalify@2.0.0", "", {}, "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="],
"url-join": ["url-join@4.0.1", "", {}, "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="],
"xml-js": ["xml-js@1.6.11", "", { "dependencies": { "sax": "^1.2.4" }, "bin": "bin/cli.js" }, "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g=="],
"@semantic-release/gitlab/fs-extra": ["fs-extra@10.0.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ=="],
"cacheable-request/get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
"chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="],
"clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="],
}
}

19
default.nix Normal file
View File

@@ -0,0 +1,19 @@
{
sources ? import ./nix,
system ? builtins.currentSystem,
pkgs ? import sources.nixpkgs {
inherit system;
config = { };
overlays = [ ];
},
}:
let
sdk = pkgs.dotnetCorePackages.sdk_9_0;
sdslite = pkgs.callPackage ./nix/sdslite.nix { dotnet-sdk = sdk; };
projnetFsharp = pkgs.callPackage ./nix/projnet.fsharp.nix { dotnet-sdk = sdk; };
in
pkgs.callPackage ./src {
SDSLite = sdslite;
projnet = projnetFsharp;
dotnet-sdk = sdk;
}

146
nix/default.nix Normal file
View File

@@ -0,0 +1,146 @@
/*
This file is provided under the MIT licence:
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.
*/
# Generated by npins. Do not modify; will be overwritten regularly
let
data = builtins.fromJSON (builtins.readFile ./sources.json);
version = data.version;
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
range =
first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
concatMapStrings = f: list: concatStrings (map f list);
concatStrings = builtins.concatStringsSep "";
# If the environment variable NPINS_OVERRIDE_${name} is set, then use
# the path directly as opposed to the fetched source.
# (Taken from Niv for compatibility)
mayOverride =
name: path:
let
envVarName = "NPINS_OVERRIDE_${saneName}";
saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name;
ersatz = builtins.getEnv envVarName;
in
if ersatz == "" then
path
else
# this turns the string into an actual Nix path (for both absolute and
# relative paths)
builtins.trace "Overriding path of \"${name}\" with \"${ersatz}\" due to set \"${envVarName}\"" (
if builtins.substring 0 1 ersatz == "/" then
/. + ersatz
else
/. + builtins.getEnv "PWD" + "/${ersatz}"
);
mkSource =
name: spec:
assert spec ? type;
let
path =
if spec.type == "Git" then
mkGitSource spec
else if spec.type == "GitRelease" then
mkGitSource spec
else if spec.type == "PyPi" then
mkPyPiSource spec
else if spec.type == "Channel" then
mkChannelSource spec
else if spec.type == "Tarball" then
mkTarballSource spec
else
builtins.throw "Unknown source type ${spec.type}";
in
spec // { outPath = mayOverride name path; };
mkGitSource =
{
repository,
revision,
url ? null,
submodules,
hash,
branch ? null,
...
}:
assert repository ? type;
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
# In the latter case, there we will always be an url to the tarball
if url != null && !submodules then
builtins.fetchTarball {
inherit url;
sha256 = hash; # FIXME: check nix version & use SRI hashes
}
else
let
url =
if repository.type == "Git" then
repository.url
else if repository.type == "GitHub" then
"https://github.com/${repository.owner}/${repository.repo}.git"
else if repository.type == "GitLab" then
"${repository.server}/${repository.repo_path}.git"
else
throw "Unrecognized repository type ${repository.type}";
urlToName =
url: rev:
let
matched = builtins.match "^.*/([^/]*)(\\.git)?$" url;
short = builtins.substring 0 7 rev;
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
in
"${if matched == null then "source" else builtins.head matched}${appendShort}";
name = urlToName url revision;
in
builtins.fetchGit {
rev = revision;
inherit name;
# hash = hash;
inherit url submodules;
};
mkPyPiSource =
{ url, hash, ... }:
builtins.fetchurl {
inherit url;
sha256 = hash;
};
mkChannelSource =
{ url, hash, ... }:
builtins.fetchTarball {
inherit url;
sha256 = hash;
};
mkTarballSource =
{
url,
locked_url ? url,
hash,
...
}:
builtins.fetchTarball {
url = locked_url;
sha256 = hash;
};
in
if version == 5 then
builtins.mapAttrs mkSource data.pins
else
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"

29
nix/projnet.fsharp.nix Normal file
View File

@@ -0,0 +1,29 @@
{
dotnet-sdk,
fetchFromGitLab,
buildDotnetModule
}:
let
src = fetchFromGitLab {
owner = "oceanbox";
repo = "ProjNet.FSharp";
# tag = "v5.2.0";
rev = "722ce0c23fda6844a81e995afbb2d81cbd5f38ec";
private = true;
forceFetchGit = true;
hash = "sha256-Rvnnf/D2x90pwgvTbXz307MJVBlVPK/cCf1hqj2VosE=";
};
in
buildDotnetModule {
name = "ProjNet.FSharp";
src = src;
dotnet-sdk = dotnet-sdk;
projectFile = "src/ProjNet.FSharp.fsproj";
nugetDeps = "${src}/src/deps.json";
packNupkg = true;
executables = [ ];
}

29
nix/sdslite.nix Normal file
View File

@@ -0,0 +1,29 @@
{
dotnet-sdk,
fetchFromGitLab,
buildDotnetModule
}:
let
src = fetchFromGitLab {
owner = "oceanbox";
repo = "SDSlite";
# tag = "v2.8.0";
rev = "8c1a158206c37bc57a5bd726a792bd6a9cd2ec01";
private = true;
forceFetchGit = true;
hash = "sha256-i9pNrmH/VC0Q9FCldbWGdZHkqSL1cdYtAOs7vX+DlXM=";
};
in
buildDotnetModule {
name = "Oceanbox.SDSLite";
src = src;
dotnet-sdk = dotnet-sdk;
projectFile = "ScientificDataSet/ScientificDataSet.csproj";
nugetDeps = "${src}/ScientificDataSet/deps.json";
packNupkg = true;
executables = [ ];
}

23
nix/sources.json Normal file
View File

@@ -0,0 +1,23 @@
{
"pins": {
"nix-utils": {
"type": "Git",
"repository": {
"type": "Git",
"url": "https://git.sr.ht/~mrtz/nix-utils"
},
"branch": "trunk",
"submodules": false,
"revision": "098f594425d2b9dde0657becad0f6498d074f8b3",
"url": null,
"hash": "0hh52w1fkpr1xx6j8cjm6g88j2352yv2ysqm1q51j59y6f583vyb"
},
"nixpkgs": {
"type": "Channel",
"name": "nixpkgs-unstable",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre905319.f720de590661/nixexprs.tar.xz",
"hash": "07n4hhch0j6n69b0zchdjg0l80z2xrdk7k57ykv90cvhklim5dz1"
}
},
"version": 5
}

4
nix/sources.nix Normal file
View File

@@ -0,0 +1,4 @@
{
"ProjNet.FSharp" = "https://gitlab.com/api/v4/projects/35009572/packages/nuget/download";
"Oceanbox.SDSLite" = "https://gitlab.com/api/v4/projects/34025102/packages/nuget/download";
}

2772
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,25 @@
with import <nixpkgs> {};
mkShell {
buildInputs = [
netcdf
nodejs
let
sources = import ./nix;
pkgs = import sources.nixpkgs {};
dotnet-sdk = pkgs.dotnetCorePackages.sdk_9_0;
in
with import <nixpkgs> { };
mkShell rec {
packages = [
bun
dotnet-sdk
fsautocomplete
fantomas
npins
nixfmt
nuget-to-json
];
DOTNET_ROOT = "${dotnet-sdk_8}";
shellHook = ''
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${stdenv.cc.cc.lib}/lib;
'';
buildInputs = [
netcdf
stdenv.cc.cc.lib
];
DOTNET_ROOT = "${dotnet-sdk}/share/dotnet";
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
}

View File

@@ -139,6 +139,7 @@ let private genOobIdx (proj: IProj) (tree: KdTree<_, _>) (cullIdx: (int * int)[]
// cullIdx = cullIdx
// oobIdx = genOobIdx proj tree cullIdx fPos rPos
// }
let private mkFvcomAdjoint (proj: IProj) (fPos: PosVec) (bbox: BBox) ((rPos, wet): BiPos * Mask) =
let box = mkCropBox bbox
let pos' = reProject proj rPos
@@ -147,7 +148,9 @@ let private mkFvcomAdjoint (proj: IProj) (fPos: PosVec) (bbox: BBox) ((rPos, wet
let nearest =
fPos
|> Array.map (fun (x, y) -> tree.GetNearestNeighbours ([| x; y |], 1) |> Array.head |> (fun x -> x.Value))
{ adjoinIdx = nearest; cullIdx = cullIdx; oobIdx = genOobIdx proj tree cullIdx fPos rPos }
let oobIdx = genOobIdx proj tree cullIdx fPos rPos
{ adjoinIdx = nearest; cullIdx = cullIdx; oobIdx = oobIdx }
let inline float2 x = bimap float float x

View File

@@ -1,20 +1,76 @@
module Oceanbox.FvcomKit.Arome
open FSharpPlus
open System
open Microsoft.Research.Science.Data
open ProjNet.FSharp
open Serilog
open Types
let trans = makeTransform CoordSys.WGS84 (CoordSys.LCCMet ())
[<Struct>]
type Pointf = {
x: float
y: float
} with
static member Zero = { x = 0.0; y = 0.0 }
static member OfTuple (x, y) = { x = x; y = y }
static member OfStructTuple struct (x, y) = { x = x; y = y }
/// Single precision
[<Struct>]
type Points = {
x: single
y: single
} with
static member Zero = { x = 0.0f; y = 0.0f }
static member OfTuple (x, y) = { x = x; y = y }
static member OfStructTuple struct (x, y) = { x = x; y = y }
module Pointf =
let ofPoints (p: Points) : Pointf = { x = float p.x; y = float p.y }
let getBBox (points: Pointf array) : BBox =
let minX = points |> Array.minBy _.x |> _.x
let maxX = points |> Array.maxBy _.x |> _.x
let minY = points |> Array.minBy _.y |> _.y
let maxY = points |> Array.maxBy _.y |> _.y
let center = (minX + (maxX - minX)) / 2., (minY + (maxY - minY)) / 2.
{
minX = minX
maxX = maxX
minY = minY
maxY = maxY
center = center
}
module Points =
let getBBox (points: Points array) : BBox =
let minX = points |> Array.minBy _.x |> _.x
let maxX = points |> Array.maxBy _.x |> _.x
let minY = points |> Array.minBy _.y |> _.y
let maxY = points |> Array.maxBy _.y |> _.y
let center = float (minX + (maxX - minX)) / 2., float (minY + (maxY - minY)) / 2.
{
minX = float minX
maxX = float maxX
minY = float minY
maxY = float maxY
center = center
}
[<Struct>]
type SquareGrid = {
dimensions: int * int
BBox: BBox
squareSize: float
projection: ProjNet.CoordinateSystems.CoordinateSystem
points: Points array
} with
member this.getBoundingBox() = this.BBox
static member empty = {
@@ -22,15 +78,93 @@ type SquareGrid = {
BBox = BBox.empty
squareSize = 0.0
projection = CoordSys.LCCMet ()
points = Array.empty
}
let getBBox (xs: float array) (ys: float array) : BBox =
let private square x = x * x
let haversineDistance (earthRadius: float) (x0: float) (y0: float) (x1: float) (y1: float) : float =
let mutable lat1 = y0
let mutable lat2 = y1
let lon1 = x0
let lon2 = x1
let dLat = Double.DegreesToRadians (lat2 - lat1)
let dLon = Double.DegreesToRadians (lon2 - lon1)
lat1 <- Double.DegreesToRadians lat1
lat2 <- Double.DegreesToRadians lat2
let a = square (Math.Sin (dLat / 2.0)) + Math.Cos lat1 * Math.Cos lat2 * square (Math.Sin (dLon / 2.0))
let c = 2.0 * Math.Asin (Math.Sqrt a)
let result = earthRadius * c
result
let getGrid (ds: DataSet) : Result<SquareGrid, string> =
try
let minX = Array.min xs
let maxX = Array.max xs
let minY = Array.min ys
let maxY = Array.max ys
let center = float (minX + (maxX - minX)) / 2., float (minY + (maxY - minY)) / 2.
let dimensions = ds.Dimensions["x"].Length, ds.Dimensions["y"].Length
let longs : float array2d = ds["longitude"].GetData () :?> float[,]
let lats : float array2d = ds["latitude"].GetData () :?> float[,]
// NOTE: The netcdf file dimensions are defined as (y, x)
let width = Array2D.length2 longs
let height = Array2D.length1 lats
let points : Points array =
let result = Array.create (width * height) Points.Zero
for i in 0 .. height - 1 do
for j in 0 .. width - 1 do
let lat = lats[i, j]
let lon = longs[i, j]
let p = lon, lat
// NOTE(simkir): Convert to lambert projection
let x, y = trans.project p
result[i * width + j] <- Points.OfTuple (single x, single y)
result
let bbox = Points.getBBox points
if points.Length < 2 then
Error "The dataset must contain at least 1 square"
else
let p0 = points[0 * width + 0]
let p1 = points[0 * width + 1]
let p2 = points[1 * width + 0]
// let p3 = points[1 * width + 1]
let x1, x0 = if p1.x > p0.x then p1.x, p0.x else p0.x, p1.x
let y1, y0 = if p2.y > p1.y then p2.y, p1.y else p1.y, p2.y
let lengthX = x1 - x0
let lengthY = y1 - y0
let isSquare = lengthX = lengthY
if not isSquare then
Log.Warning (
"FvcomKit.Arome.getGrid grid is not square: {X1} - {X0} = {LengthX} = {LengthY} = {Y1} - {Y0}",
x1,
x0,
lengthX,
lengthY,
y1,
y0
)
Ok {
SquareGrid.empty with
dimensions = dimensions
BBox = bbox
squareSize = float lengthX
points = points
}
with exn ->
Log.Error (exn, "Sorcerer.Arome.getAromeSquareGrid exception")
Error $"Error reading arome grid: {exn.Message}"
let private getBBox (xs: float array) (ys: float array) : BBox =
try
let minX = xs |> Array.min
let maxX = xs |> Array.max
let minY = ys |> Array.min
let maxY = ys |> Array.max
let center = (minX + (maxX - minX)) / 2., (minY + (maxY - minY)) / 2.
{
minX = minX
@@ -43,7 +177,8 @@ let getBBox (xs: float array) (ys: float array) : BBox =
Log.Error $"{e}"
BBox.empty
let getGrid (ds: DataSet) : Result<SquareGrid, string> =
/// Depends on the netcdf having the 'x' and 'y' variables
let getSquareGrid (ds: DataSet) : Result<SquareGrid, string> =
try
let dimensions = ds.Dimensions["x"].Length, ds.Dimensions["y"].Length
let xs = (ds["x"].GetData () :?> single[]) |> Array.map float
@@ -77,27 +212,35 @@ let getGrid (ds: DataSet) : Result<SquareGrid, string> =
Log.Error (exn, "FvcomKit.Arome.getGrid exception")
Error $"Error reading arome grid: {exn.Message}"
let readUV (ds: DataSet) t x y =
let u = ds["x_wind_10m"].GetData ([| t; y; x |], [| 1; 1; 1 |]) :?> single[,,]
let v = ds["y_wind_10m"].GetData ([| t; y; x |], [| 1; 1; 1 |]) :?> single[,,]
u[0, 0, 0], v[0, 0, 0]
let readUV (ds: DataSet) (t: int) x y : single * single =
let xWind = ds["x_wind_10m"]
let yWind = ds["y_wind_10m"]
let origin = [| t; 0; y; x |]
let shape = [| 1; 1; 1; 1; |]
Log.Verbose("""Fetching NetCDF["x_wind_10m"]({Origin}, {Shape})""", origin, shape)
let us = xWind.GetData (origin, shape) :?> single[,,,]
let vs = yWind.GetData (origin, shape) :?> single[,,,]
us[0, 0, 0, 0], vs[0, 0, 0, 0]
/// Finds the index of a tile within a square grid, given its bounding box and square length
let tryFindIndex squareSize (wide, tall) (minX, minY) (maxX, maxY) (p0, p1) =
if (minX < p0 && p0 < maxX) && (minY < p1 && p1 < maxY) then
let dx = p0 - minX
let dy = p1 - minY
let xIdx = int (dx / squareSize)
let yIdx = int (dy / squareSize)
let tryFindIndex (grid: SquareGrid) (x0, y0) =
let wide, tall = grid.dimensions
let bbox = grid.BBox
if bbox.minX <= x0 && x0 < bbox.maxX && bbox.minY <= y0 && y0 < bbox.maxY then
let dx = x0 - bbox.minX
let dy = y0 - bbox.minY
let xIdx = int (dx / grid.squareSize)
let yIdx = int (dy / grid.squareSize)
if xIdx < wide && yIdx < tall then
Some (xIdx, yIdx)
else
Log.Warning (
"Got wrong indices within the bounding box of the archive: min {@Min}, max {@Max}, point {@Point}, delta {@Delta}m, indices {@Indices}",
(minX, minY),
(maxX, maxY),
(p0, p1),
(bbox.minX, bbox.minY),
(bbox.maxX, bbox.maxY),
(x0, y0),
(dx, dy),
(xIdx, yIdx)
)
@@ -106,17 +249,12 @@ let tryFindIndex squareSize (wide, tall) (minX, minY) (maxX, maxY) (p0, p1) =
None
/// Tries to get the closest x and y in the arome dataset based on position p
let tryFind (grid: SquareGrid) (p0: float, p1: float) : (int * int) option =
let min = (grid.BBox.minX, grid.BBox.minY)
let max = (grid.BBox.maxX, grid.BBox.maxY)
let tryFind (grid: SquareGrid) (p: float * float) : (int * int) option =
tryFindIndex grid p
let tryFindWithProj (proj: Projection) (grid: SquareGrid) (p0: float, p1: float) : (int * int) option =
let coordSys : ProjNet.CoordinateSystems.CoordinateSystem = Projection.ToCoordinateSystem proj
let trans = makeTransform coordSys grid.projection
let p = trans.project ((p0, p1))
tryFindIndex grid.squareSize grid.dimensions min max p
let tryFindWithProj (grid: SquareGrid) proj (p0: float, p1: float) : (int * int) option =
let trans = Transform.makeTransform proj grid.projection
let min = (grid.BBox.minX, grid.BBox.minY)
let max = (grid.BBox.maxX, grid.BBox.maxY)
let p = trans.project ((p0, p1))
tryFindIndex grid.squareSize grid.dimensions min max p
tryFindIndex grid p

View File

@@ -29,6 +29,7 @@ type FvcomGrid = {
member this.getVertices() = this.Nodes
member this.getCells() = this.Elem
member this.getBoundingBox() = this.BBox
static member empty = {
Elem = Array.empty
Nodes = Array.empty
@@ -39,6 +40,7 @@ type FvcomGrid = {
SiglayCenter = Array2D.zeroCreate 0 0
Siglev = Array2D.zeroCreate 0 0
}
member this.ToGrid() = { Elem = this.Elem; Nodes = this.Nodes; BBox = this.BBox }
let getNumFrames (ds: DataSet) =
@@ -76,7 +78,7 @@ let getTimeSpanSinceStart (ds: DataSet) n =
let days = ds["Itime"].GetData () :?> int[]
let msec = ds["Itime2"].GetData () :?> int[]
let t0 = TimeSpan.FromDays days[n]
let t1 = TimeSpan.FromMilliseconds msec[n]
let t1 = TimeSpan.FromMilliseconds (float msec[n])
t0 + t1 |> Some
with e ->
Log.Error $"getTimeInDays exception: {e.Message}"
@@ -526,7 +528,7 @@ module Singular =
Log.Error $"{err}"
0f, 0f
let readTemp (ds: DataSet) n t d =
let readTemp (ds: DataSet) n t d : single =
try
let t = ds["temp"].GetData ([| t; d; n |], [| 1; 1; 1 |]) :?> single[,,]
t[0, 0, 0]

View File

@@ -9,7 +9,7 @@ open ProjNet.FSharp
open MessagePack
open MBrace.FsPickler
//open FsKDTree
open KdTree // C# version
open KdTree // NOTE: C# version
open Types
@@ -21,8 +21,8 @@ type Elem = NodeIdx * NodeIdx * NodeIdx
type Node = float * float
type Pos = float * float
type Leaf<'a> = { Pos : Pos; Data : 'a }
type Field = (float * float) []
type Leaf<'a> = { Pos: Pos; Data: 'a }
type Field = (float * float) array
type Cell = NodeIdx * NodeIdx * NodeIdx
@@ -30,16 +30,15 @@ type IGrid =
abstract getVertex: int -> Vertex
abstract getCell: int -> Cell
abstract getCellVertices: int -> Vertex * Vertex * Vertex
abstract getVertices: unit -> Vertex []
abstract getCells: unit -> Cell []
abstract getVertices: unit -> Vertex array
abstract getCells: unit -> Cell array
abstract getBoundingBox: unit -> BBox
type Grid =
{
Elem: Elem array
Nodes: Node array
BBox: BBox
}
type Grid = {
Elem: Elem array
Nodes: Node array
BBox: BBox
} with
interface IGrid with
member this.getVertex n = this.Nodes[n]
member this.getCell n = this.Elem[n]
@@ -49,55 +48,48 @@ type Grid =
member this.getVertices() = this.Nodes
member this.getCells() = this.Elem
member this.getBoundingBox() = this.BBox
static member empty =
{
Elem = Array.empty
Nodes = Array.empty
BBox = BBox.empty
}
static member empty = { Elem = Array.empty; Nodes = Array.empty; BBox = BBox.empty }
type ElemsAroundNode = Map<NodeIdx, ElemIdx []>
type NodesAroundNode = Map<NodeIdx, NodeIdx []>
type ElemsAroundNode = Map<NodeIdx, ElemIdx array>
type NodesAroundNode = Map<NodeIdx, NodeIdx array>
type ElemsAroundElem = Map<ElemIdx, ElemIdx array>
type NeighborIndex =
{
ElemsAroundNode: ElemsAroundNode
NodesAroundNode: NodesAroundNode
}
static member empty = { ElemsAroundNode = Map.empty; NodesAroundNode = Map.empty }
type private Ean = Map<NodeIdx, ElemIdx list>
type NeighborIndex = {
ElemsAroundNode: ElemsAroundNode
NodesAroundNode: NodesAroundNode
ElemsAroundElem: ElemsAroundElem
} with
static member empty = { ElemsAroundNode = Map.empty; NodesAroundNode = Map.empty; ElemsAroundElem = Map.empty }
// NOTE(SimenLK): The amount of items to be stored in the trees leafs
// let treeLeafSize = LeafNodeSize 64
let private createTree (points: Leaf<int> []) =
let tree = KdTree<float, int>(2, KdTree.Math.DoubleMath())
points
|> Array.iter (fun a -> tree.Add([| fst a.Pos; snd a.Pos |], a.Data) |> ignore)
let private createTree (points: Leaf<int> array) =
let tree = KdTree<float, int> (2, KdTree.Math.DoubleMath ())
do points |> Array.iter (fun a -> tree.Add ([| fst a.Pos; snd a.Pos |], a.Data) |> ignore)
if points.Length > 0 then
tree.Balance()
do tree.Balance ()
else
Log.Warning $"Empty kd-tree"
do Log.Warning $"Empty kd-tree"
tree
type private Ean = Map<NodeIdx, ElemIdx array>
let private makeElemsSurroundingNodeMap (elem: Elem array) : ElemsAroundNode =
let addElIdx k v (nodes: Ean) =
Map.tryFind k nodes
|> Option.defaultWith (fun () -> [])
|> fun nds -> Map.add k (v :: nds) nodes
elem
|> Array.fold
(fun (n, acc) (a, b, c) ->
let acc' =
acc
|> addElIdx a n
|> addElIdx b n
|> addElIdx c n
n + 1, acc')
(0, Map.empty)
let addElIdx k v (nodes: Ean) : Ean =
let nds =
Map.tryFind k nodes
|> Option.defaultValue [||]
nodes |> Map.add k (Array.append [|v|] nds)
let folder (n, acc) (a, b, c) =
let acc' = acc |> addElIdx a n |> addElIdx b n |> addElIdx c n
n + 1, acc'
((0, Map.empty), elem)
||> Array.fold folder
|> snd
|> Map.mapValues toArray
let private makeElemsSurroundingNodeMap' (elem: Elem array) : ElemsAroundNode =
let addElemIdx k v nodes =
@@ -108,12 +100,9 @@ let private makeElemsSurroundingNodeMap' (elem: Elem array) : ElemsAroundNode =
elem
|> Array.fold
(fun (n, acc) (a, b, c) ->
let acc' =
acc
|> addElemIdx a n
|> addElemIdx b n
|> addElemIdx c n
n + 1, acc')
let acc' = acc |> addElemIdx a n |> addElemIdx b n |> addElemIdx c n
n + 1, acc'
)
(0, Map.empty)
|> snd
|> Map.mapValues toArray
@@ -124,28 +113,45 @@ let private makeNodesSurroudingNodeMap (n2e: ElemsAroundNode) (elem: Elem array)
n
|> Array.collect (fun x ->
let n1, n2, n3 = elem[x]
[| n1; n2; n3 |])
|> Array.distinct)
[| n1; n2; n3 |]
)
|> Array.distinct
)
let getSurrounding (idx: Map<int, int []>) (a, b, c) =
[| idx[a]; idx[b]; idx[c] |]
|> Array.concat
|> Array.distinct
let private makeElemsSurroundingElemMap (ean: ElemsAroundNode) (elem: Elem array) : ElemsAroundElem =
elem
|> Array.mapi (fun elemIdx (n1, n2, n3) ->
// For each element, find all elements that share any of its nodes
let surroundingElems =
[| ean[n1]; ean[n2]; ean[n3] |]
|> Array.concat
|> Array.distinct
|> Array.filter (fun x -> x <> elemIdx) // Remove self
elemIdx, surroundingElems
)
|> Map.ofArray
let getSurrounding (idx: Map<int, int[]>) (a, b, c) =
[| idx[a]; idx[b]; idx[c] |] |> Array.concat |> Array.distinct
let makeNeighborIndex (grid: IGrid) =
let elem = grid.getCells ()
let ean = makeElemsSurroundingNodeMap elem
{
ElemsAroundNode = ean
NodesAroundNode = makeNodesSurroudingNodeMap ean elem
ElemsAroundElem = makeElemsSurroundingElemMap ean elem
}
let getElemsSurroundingNode (idx: NeighborIndex) n = idx.ElemsAroundNode[n]
let getNodesSurroundingNode (idx: NeighborIndex) n = idx.NodesAroundNode[n]
let getNodesSurroundingElem (idx: NeighborIndex) (grid: Grid) e =
getSurrounding idx.NodesAroundNode grid.Elem[e]
let getNodesSurroundingElem (idx: NeighborIndex) e = idx.ElemsAroundElem[e]
// let getNodesSurroundingElem (idx: NeighborIndex) (grid: Grid) e =
// getSurrounding idx.NodesAroundNode grid.Elem[e]
let getElemsSurroundingElem (idx: NeighborIndex) (grid: Grid) e =
getSurrounding idx.ElemsAroundNode grid.Elem[e]
@@ -182,61 +188,68 @@ let bboxToLngLat (coordsys: CoordinateSystem) b =
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 }
{
minX = x0
maxX = x1
minY = y0
maxY = y1
center = proj b.center
}
let projectGrid proj (grid: Grid) : Grid =
{ grid with
let projectGrid proj (grid: Grid) : Grid = {
grid with
Nodes = grid.Nodes |> Array.Parallel.map proj
BBox = projectBBox proj grid.BBox
}
}
let rescaleGrid (factor: float) (grid: Grid) : Grid =
let nodes' = grid.Nodes |> Array.Parallel.map (fun (x, y) -> factor * x, factor * y)
let bbox' = calcBBox nodes'
{ grid with
Nodes = nodes'
BBox = bbox'
}
{ grid with Nodes = nodes'; BBox = bbox' }
let translateGrid (x0, y0) grid =
let nodes' = grid.Nodes |> Array.Parallel.map (fun (x, y) -> x + x0, y + y0)
let bbox' = calcBBox nodes'
{ grid with
Nodes = nodes'
BBox = bbox'
}
{ grid with Nodes = nodes'; BBox = bbox' }
let toWebMercator (coordsys: CoordinateSystem) (grid: Grid) =
let toWebMercator = makeTransform coordsys CoordSys.EPSG3857
let s = System.Diagnostics.Stopwatch.StartNew()
let s = System.Diagnostics.Stopwatch.StartNew ()
let g = grid |> projectGrid toWebMercator.project
s.Stop()
s.Stop ()
Log.Debug $"Reprojected grid: {s.ElapsedMilliseconds} ms"
g
let toLonLat (coordsys: CoordinateSystem) (grid: Grid) =
let toLonLat = makeTransform coordsys CoordSys.WGS84
let s = System.Diagnostics.Stopwatch.StartNew ()
let g = grid |> projectGrid toLonLat.project
s.Stop ()
Log.Debug $"Reprojected grid: {s.ElapsedMilliseconds} ms"
g
let private chomp (l: string) = l.Split ' ' |> Array.filter ((<>) "")
// TODO: pattern match and compare sizes for more flex
let private readGrdHeader (h: string []) =
let private readGrdHeader (h: string[]) =
let parse x = (chomp x)[3] |> int
try
let nodes = parse h[0]
let nele = parse h[1]
Some(nodes, nele)
with
| _ -> None
Some (nodes, nele)
with _ ->
None
let private readObcHeader (h: string) =
try
(chomp h)[4] |> int |> Some
with
| _ -> None
with _ ->
None
let private reader (parser: string [] -> 'a) (f: string array) =
let private reader (parser: string[] -> 'a) (f: string array) =
try
f |> Array.map (chomp >> parser) |> Some
with
| e ->
with e ->
Log.Error e.Message
None
@@ -274,13 +287,13 @@ let readGrdFile (filename: string) =
let elem = readElem els
let nodes = readNodes nds
let toGrid e n = { Elem = e; Nodes = n; BBox = calcBBox n }
toGrid <!> elem <*> nodes)
toGrid <!> elem <*> nodes
)
let readObcFile (filename: string) =
let f = System.IO.File.ReadAllLines filename
let hdr, rest = Array.splitAt 1 f
readObcHeader hdr[0]
|> Option.bind (fun _ -> readObc rest)
readObcHeader hdr[0] |> Option.bind (fun _ -> readObc rest)
module Boundary =
let normalizeElement (a, b, c) =
@@ -294,11 +307,8 @@ module Boundary =
match Map.tryFind edge a with
| Some v -> Map.add edge (n :: v) a
| None -> Map.add edge [ n ] a
let appendEdges a (n, x: Edge []) =
a
|> appendEdge (n, x[0])
|> appendEdge (n, x[1])
|> appendEdge (n, x[2])
let appendEdges a (n, x: Edge[]) =
a |> appendEdge (n, x[0]) |> appendEdge (n, x[1]) |> appendEdge (n, x[2])
grid.Elem
|> Array.mapi normElIdx
|> Array.fold appendEdges Map.empty
@@ -316,8 +326,7 @@ module Boundary =
|> Map.mapValues (fun x -> List.head x, List.last x)
let makeBoundaryByElementMap (edgeMap: Map<Edge, ElemIdx>) =
edgeMap
|> Map.fold (fun a k v -> Map.add v k a) Map.empty
edgeMap |> Map.fold (fun a k v -> Map.add v k a) Map.empty
let getBoundaryNodesArray (edgeMap: Map<Edge, ElemIdx>) =
edgeMap
@@ -337,8 +346,7 @@ module Util =
(x0 + x1 + x2) / 3f, (y0 + y1 + y2) / 3f
static member calcArea((x0, y0), (x1, y1), (x2, y2)) =
x0 * (y1 - y2) + x1 * (y2 - y0) + x2 * (y0 - y1)
|> (*) 0.5
x0 * (y1 - y2) + x1 * (y2 - y0) + x2 * (y0 - y1) |> (*) 0.5
// static member calcArea((x0, y0), (x1, y1), (x2, y2)) =
// x0 * (y1 - y2) + x1 * (y2 - y0) + x2 * (y0 - y1)
@@ -356,20 +364,20 @@ module Util =
let Sy = det Tx T2 E |> (*) 0.5
let a = det Tx Ty E
let b = det Tx Ty T2
if abs a < 1.0e-12 then failwith "co-linear vertices"
if abs a < 1.0e-12 then
failwith "co-linear vertices"
let S2 = (Sx, Sy) |> square
let r = (b / a + S2 / (a * a)) |> sqrt
Sx / a, Sy / a, r
static member propToNodal (nIdx: NeighborIndex) (s: float []) =
static member propToNodal (nIdx: NeighborIndex) (s: float[]) =
[| 0 .. nIdx.NodesAroundNode.Count - 1 |] // total number of nodes
|> Array.Parallel.map (fun i ->
let ns = nIdx.ElemsAroundNode[i]
ns
|> Array.fold (fun a n -> s[n] + a) 0.
|> (*) (1. / float ns.Length))
ns |> Array.fold (fun a n -> s[n] + a) 0. |> (*) (1. / float ns.Length)
)
static member speedToNodal (nIdx: NeighborIndex) (s: (float * float) []) =
static member speedToNodal (nIdx: NeighborIndex) (s: (float * float)[]) =
[| 0 .. nIdx.NodesAroundNode.Count - 1 |] // total number of nodes
|> Array.Parallel.map (fun i ->
let ns = nIdx.ElemsAroundNode[i]
@@ -377,11 +385,13 @@ module Util =
|> Array.fold
(fun a n ->
let u, v = s[n]
sqrt (u * u + v * v) + a)
sqrt (u * u + v * v) + a
)
0.
|> (*) (1. / float ns.Length))
|> (*) (1. / float ns.Length)
)
static member velocityToNodal (nIdx: NeighborIndex) (s: (float * float) []) =
static member velocityToNodal (nIdx: NeighborIndex) (s: (float * float)[]) =
[| 0 .. nIdx.NodesAroundNode.Count - 1 |] // total number of nodes
|> Array.Parallel.map (fun i ->
let ns = nIdx.ElemsAroundNode[i]
@@ -390,9 +400,11 @@ module Util =
|> Array.fold
(fun (au, av) n ->
let u, v = s[n]
u + au, v + av)
u + au, v + av
)
(0., 0.)
|> fun (u, v) -> u / n, v / n)
|> fun (u, v) -> u / n, v / n
)
type Node =
static member calcNodeControlArea (idx: NeighborIndex) (grid: IGrid) =
@@ -411,8 +423,10 @@ module Util =
let centroid = Element.calcCentroid (p0, p1, p2)
let a1 = Element.calcArea (p0', p1', p2')
let a2 = Element.calcArea (p1', centroid, p2')
a1 + a2)
|> Array.sum)
a1 + a2
)
|> Array.sum
)
static member calcNodeArea (idx: NeighborIndex) (grid: IGrid) =
let nodes = grid.getVertices ()
@@ -420,7 +434,8 @@ module Util =
|> Array.Parallel.map (fun n ->
getElemsSurroundingNode idx n
|> Array.map (grid.getCellVertices >> Element.calcArea)
|> Array.sum)
|> Array.sum
)
let calcCentroids (grid: IGrid) =
let n = grid.getVertices ()
@@ -429,33 +444,19 @@ module Util =
let p0 = n[a]
let p1 = n[b]
let p2 = n[c]
Element.calcCentroid (p0, p1, p2))
Element.calcCentroid (p0, p1, p2)
)
let inline isInsideTriangle (x, y, z) p =
let sign (p1x, p1y) (p2x, p2y) (p3x, p3y) =
(p1x - p3x) * (p2y - p3y)
- (p2x - p3x) * (p1y - p3y)
(p1x - p3x) * (p2y - p3y) - (p2x - p3x) * (p1y - p3y)
let d1 = sign p x y
let d2 = sign p y z
let d3 = sign p z x
let neg = (d1 <= 0.) || (d2 <= 0.) || (d3 <= 0.)
let pos = (d1 >= 0.) || (d2 >= 0.) || (d3 >= 0.)
(neg && pos) |> not
let inline isInsideTriangle_dbl (x, y, z) p =
let sign (p1x, p1y) (p2x, p2y) (p3x, p3y) =
(p1x - p3x) * (p2y - p3y)
- (p2x - p3x) * (p1y - p3y)
let d1 = sign p x y
let d2 = sign p y z
let d3 = sign p z x
let neg = (d1 <= 0.0) || (d2 <= 0.0) || (d3 <= 0.0)
let pos = (d1 >= 0.0) || (d2 >= 0.0) || (d3 >= 0.0)
let neg = (d1 < 0.) || (d2 < 0.) || (d3 < 0.)
let pos = (d1 > 0.) || (d2 > 0.) || (d3 > 0.)
(neg && pos) |> not
@@ -464,7 +465,8 @@ module Util =
grid.getVertices ()
|> Array.mapi (fun i v ->
let x, y = v
{ Pos = x, y; Data = i })
{ Pos = x, y; Data = i }
)
// |> create2DTree treeLeafSize
|> createTree
@@ -472,12 +474,10 @@ module Util =
let buildNearestElementTree (grid: IGrid) =
grid.getCells ()
|> Array.mapi (fun i _ ->
let pos =
i
|> grid.getCellVertices
|> Element.calcCentroid
// |> fun (x, y) -> { X = x; Y = y }
{ Pos = pos; Data = i })
let pos = i |> grid.getCellVertices |> Element.calcCentroid
// |> fun (x, y) -> { X = x; Y = y }
{ Pos = pos; Data = i }
)
// |> create2DTree treeLeafSize
|> createTree
@@ -493,14 +493,15 @@ module Util =
// else None)
let tryFindElement (grid: IGrid) (tree: KdTree<float, int>) ((p0, p1): float * float) =
tree.GetNearestNeighbours([| p0; p1 |], 1)
tree.GetNearestNeighbours ([| p0; p1 |], 1)
|> Array.tryHead
|> Option.bind (fun leaf ->
let vx = grid.getCellVertices leaf.Value
if isInsideTriangle vx (p0, p1) then
Some leaf.Value
else
None)
None
)
// type private IdxTree = Tree<Leaf<single, ElemIdx> array, Node<single>>
// type private NodeIdxTree = Tree<Leaf<single, NodeIdx> array, Node<single>>
@@ -509,23 +510,21 @@ type private NodeIdxTree = KdTree<float, int>
[<MessagePackObject>]
type BinGrid = {
[<Key(0)>] hash : byte[]
[<Key(1)>] vertices : (float * float)[]
[<Key(2)>] cells : (int * int * int)[]
[<Key(0)>]
hash: byte[]
[<Key(1)>]
vertices: (float * float)[]
[<Key(2)>]
cells: (int * int * int)[]
} with
member this.toGrid (): Grid =
{
Nodes = this.vertices
Elem = this.cells
BBox = calcBBox this.vertices
}
member this.toGrid() : Grid = { Nodes = this.vertices; Elem = this.cells; BBox = calcBBox this.vertices }
type ExtendedGrid(grid: IGrid) =
let mutable nodeTree: NodeIdxTree option = None
let mutable elementTree: IdxTree option = None
let mutable neighborIndex: NeighborIndex option = None
let mutable centroids: Vertex [] option = None
let mutable gridSha1: byte[] = [||]
let mutable centroids: Vertex[] option = None
let mutable gridHash: byte[] = [||]
let getNeighborIdx () =
match neighborIndex with
@@ -548,12 +547,11 @@ type ExtendedGrid(grid: IGrid) =
member this.Grid = grid
member this.ToGrid() =
{
Nodes = this.Grid.getVertices ()
Elem = this.Grid.getCells ()
BBox = this.Grid.getBoundingBox ()
}
member this.ToGrid() = {
Nodes = this.Grid.getVertices ()
Elem = this.Grid.getCells ()
BBox = this.Grid.getBoundingBox ()
}
member this.initNeighborIndex(?cache: string) =
@@ -573,7 +571,7 @@ type ExtendedGrid(grid: IGrid) =
member this.nearestNode(p0: float, p1: float) =
let nearest (tree: KdTree<_, _>) =
tree.GetNearestNeighbours([| p0; p1 |], 1)
tree.GetNearestNeighbours ([| p0; p1 |], 1)
|> Array.tryHead
|> Option.map (fun l -> l.Value)
match nodeTree with
@@ -586,32 +584,38 @@ type ExtendedGrid(grid: IGrid) =
this.nearestNode p
|> Option.bind (fun n ->
this.getElemsSurroundingNode n
|> Array.fold (fun (a: int option) e ->
if a.IsNone then
let vx = this.Grid.getCellVertices e
if Util.isInsideTriangle vx p then Some n else None
else a) None
)
|> Array.fold
(fun (a: int option) e ->
if a.IsNone then
let vx = this.Grid.getCellVertices e
if Util.isInsideTriangle vx p then Some n else None
else
a
)
None
)
member private this.tryFindElementTwice (grid: IGrid) (tree: IdxTree) ((p0, p1): float * float as p) =
Util.tryFindElement grid tree p
|> Option.orElse (
tree.GetNearestNeighbours([| p0; p1 |], 1)
tree.GetNearestNeighbours ([| p0; p1 |], 1)
|> Array.tryHead
|> Option.bind (fun leaf ->
this.getElemsSurroundingElem leaf.Value
|> Array.tryFind (fun eIdx ->
let vx = this.Grid.getCellVertices eIdx
Util.isInsideTriangle vx p))
Util.isInsideTriangle vx p
)
)
)
member this.tryGetElement p =
elementTree
|> Option.bind (fun tree ->
this.tryFindElementTwice grid tree p)
|> Option.bind (fun tree -> this.tryFindElementTwice grid tree p)
|> Option.orElseWith (fun () ->
this.initElementTree ()
this.tryFindElementTwice grid elementTree.Value p)
this.tryFindElementTwice grid elementTree.Value p
)
member this.tryGetElementSloppy(p0, p1 as p) =
match elementTree with
@@ -621,24 +625,24 @@ type ExtendedGrid(grid: IGrid) =
// this.initNodeTree ()
Util.tryFindElement grid elementTree.Value p
member this.initSha1(grid: BinGrid) =
if gridSha1.Length = 0 && grid.hash.Length > 0 then
gridSha1 <- grid.hash
member this.initHash(hash: byte[]) =
if gridHash.Length = 0 && hash.Length > 0 then
gridHash <- hash
member this.getSha1() =
if gridSha1.Length = 0 then
let bg : BinGrid = {
member this.getHash() =
if gridHash.Length = 0 then
let bg: BinGrid = {
hash = [||]
vertices = (this :> IGrid).getVertices ()
cells = (this :> IGrid).getCells ()
}
let bytes = MessagePackSerializer.Serialize(bg)
let sha1 = System.Security.Cryptography.SHA1.Create()
gridSha1 <- sha1.ComputeHash bytes
gridSha1
let bytes = MessagePackSerializer.Serialize (bg)
let sha1 = System.Security.Cryptography.SHA1.Create ()
gridHash <- sha1.ComputeHash bytes
gridHash
member this.getSha1String() =
this.getSha1() |> Convert.ToHexStringLower
member this.getHashString() =
this.getHash () |> Convert.ToHexStringLower
member this.getCentroids() =
match centroids with
@@ -653,12 +657,10 @@ type ExtendedGrid(grid: IGrid) =
Util.Element.calcCircumscribedCircle triangle
member this.getElemsSurroundingNode n =
getNeighborIdx ()
|> fun idx -> idx.ElemsAroundNode[n]
getNeighborIdx () |> fun idx -> idx.ElemsAroundNode[n]
member this.getNodesSurroundingNode n =
getNeighborIdx ()
|> fun idx -> idx.NodesAroundNode[n]
getNeighborIdx () |> fun idx -> idx.NodesAroundNode[n]
member this.getNodesSurroundingElem e =
let idx = getNeighborIdx ()
@@ -671,35 +673,34 @@ type ExtendedGrid(grid: IGrid) =
getSurrounding idx.ElemsAroundNode elem
member this.saveNeighborIndex(fname: string) =
let binarySerializer = FsPickler.CreateBinarySerializer()
let binarySerializer = FsPickler.CreateBinarySerializer ()
let nix = getNeighborIdx ()
let pickle = binarySerializer.Pickle nix
IO.File.WriteAllBytes(fname, pickle)
IO.File.WriteAllBytes (fname, pickle)
member this.loadNeighborIndex(fname: string) =
let binarySerializer = FsPickler.CreateBinarySerializer()
let binarySerializer = FsPickler.CreateBinarySerializer ()
if IO.File.Exists fname then
let pickle = IO.File.ReadAllBytes fname
neighborIndex <-
binarySerializer.UnPickle<NeighborIndex> pickle
|> Some
neighborIndex <- binarySerializer.UnPickle<NeighborIndex> pickle |> Some
true
else
false
member this.saveNodeTree(fname: string) =
let binarySerializer = FsPickler.CreateBinarySerializer()
let binarySerializer = FsPickler.CreateBinarySerializer ()
let tree =
nodeTree
|> Option.defaultWith (fun () ->
this.initNodeTree ()
nodeTree.Value)
nodeTree.Value
)
let pickle = binarySerializer.Pickle tree
IO.File.WriteAllBytes(fname, pickle)
IO.File.WriteAllBytes (fname, pickle)
member this.loadNodeTree(fname: string) =
let binarySerializer = FsPickler.CreateBinarySerializer()
let binarySerializer = FsPickler.CreateBinarySerializer ()
if IO.File.Exists fname then
let pickle = IO.File.ReadAllBytes fname
nodeTree <- binarySerializer.UnPickle<IdxTree> pickle |> Some
@@ -708,18 +709,19 @@ type ExtendedGrid(grid: IGrid) =
false
member this.saveElementTree(fname: string) =
let binarySerializer = FsPickler.CreateBinarySerializer()
let binarySerializer = FsPickler.CreateBinarySerializer ()
let tree =
elementTree
|> Option.defaultWith (fun () ->
this.initElementTree ()
elementTree.Value)
elementTree.Value
)
let pickle = binarySerializer.Pickle tree
IO.File.WriteAllBytes(fname, pickle)
IO.File.WriteAllBytes (fname, pickle)
member this.loadElementTree(fname: string) =
let binarySerializer = FsPickler.CreateBinarySerializer()
let binarySerializer = FsPickler.CreateBinarySerializer ()
if IO.File.Exists fname then
let pickle = IO.File.ReadAllBytes fname
elementTree <- binarySerializer.UnPickle<IdxTree> pickle |> Some

View File

@@ -21,9 +21,10 @@ let private calcInterpolationWeightedIdx (rn, fn) =
let nearestIdx = Array.map (fun fzi -> findNearestZ fzi rn) fn
nearestIdx
|> Array.mapi (fun i j ->
let last = Array.last rn
if fn[i] <= rn[0] then
(0, 0), (1.0, 0.0)
elif fn[i] >= rn[^0] then
elif fn[i] >= last 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)

View File

@@ -1,18 +1,21 @@
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
@@ -21,4 +24,4 @@ let private getArchiveUrls urlf (t: DateTime) =
let tryGetArchive (threddsUrl: string) (t: DateTime) =
getArchiveUrls (getNorkystUrl threddsUrl) t
|> tryOpenThredds
|> Option.bind (fun (_, ds) -> tryGetTimeIndex ds t |> Option.map (fun idx -> (ds, idx)))
|> Option.bind (fun (_, ds) -> tryGetTimeIndex ds t |> Option.map (fun idx -> ds, idx))

View File

@@ -10,12 +10,14 @@ let private getNorshelfUrl (threddsUrl: string) (kind: Kind) (mode: Mode) (date:
let fmt (d: DateTime) =
$"{d.Year}%02d{d.Month}%02d{d.Day}T00Z.nc"
let url = threddsUrl + "/sea_norshelf_files"
$"{url}/{date.Year}/%02d{date.Month}/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
@@ -27,6 +29,7 @@ let private getArchiveUrls urlf (t: DateTime) =
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)))

View File

@@ -3,12 +3,13 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net9.0</TargetFramework>
<IsPackable>true</IsPackable>
<PackageId>Oceanbox.FvcomKit</PackageId>
<Company>Oceanbox AS</Company>
<Authors/>
<Company/>
<Version>5.10.0</Version>
<LangVersion>preview</LangVersion>
<PackageId>Oceanbox.FvcomKit</PackageId>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<Version>6.0.0</Version>
<RestorePackagesWithLockFile>false</RestorePackagesWithLockFile>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<Compile Include="Types.fs"/>
@@ -32,11 +33,14 @@
<PackageReference Include="MathNet.Numerics.FSharp" Version="5.0.0"/>
<PackageReference Include="MessagePack" Version="3.1.3"/>
<PackageReference Include="ProjNet.FSharp" Version="5.2.0"/>
<PackageReference Include="SDSlite.Oceanbox" Version="2.7.3"/>
<PackageReference Include="Oceanbox.SDSLite" Version="2.8.0"/>
<PackageReference Include="Serilog" Version="4.2.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0"/>
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0"/>
<PackageReference Include="Thoth.Json.Net" Version="12.0.0"/>
<PackageReference Update="FSharp.Core" Version="9.0.201"/>
<PackageReference Update="FSharp.Core" Version="9.0.303"/>
</ItemGroup>
<ItemGroup>
<PackageUpdate Include="ProjNet.FSharp" Version="*" Condition=" '$(ContinuousIntegrationBuild)'=='true' "/>
<PackageUpdate Include="Oceanbox.SDSLite" Version="*" Condition=" '$(ContinuousIntegrationBuild)'=='true' "/>
</ItemGroup>
</Project>

View File

@@ -1,6 +1,7 @@
module Oceanbox.FvcomKit.Thredds
open System
open Microsoft.Research.Science.Data
open Microsoft.Research.Science.Data.NetCDF4
open Serilog
@@ -21,20 +22,20 @@ type Kind =
| Avg -> "avg"
| Qck -> "qck"
let dateRange (start: DateTime) days =
let dateRange (start: DateTime) days : DateTime list =
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 tryOpenArchive url : DataSet option =
let uri = NetCDFUri ()
uri.Url <- url
do uri.Url <- url
try
let ds = NetCDFDataSet.Open uri
Some ds
with e ->
Log.Debug e.Message
do Log.Debug e.Message
None
let rec tryOpenThredds =
@@ -42,18 +43,22 @@ let rec tryOpenThredds =
| [] -> None
| x :: xs ->
match tryOpenArchive x with
| None ->
tryOpenThredds xs
| Some ds ->
Log.Debug $"thredds: {x}"
do 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}"
let first = Array.head ot
let last = Array.last ot
let t0 = DateTimeOffset.FromUnixTimeSeconds (first |> int64)
let t1 = DateTimeOffset.FromUnixTimeSeconds (last |> int64)
do Log.Debug $"t={t} t0={t0} tn={t1}"
if t < t0.DateTime || t > t1.DateTime then
Log.Error "time is out of bounds"
do Log.Error "time is out of bounds"
None
else
let dt = t - t0.DateTime

View File

@@ -4,6 +4,7 @@ open System
type Vertex = float * float
[<Struct>]
type BBox = {
minX: float
maxX: float

41
src/default.nix Normal file
View File

@@ -0,0 +1,41 @@
{
SDSLite,
projnet,
dotnet-sdk,
dotnet-runtime,
buildDotnetModule,
}:
let
name = "Oceanbox.Fvcomkit";
projectFile = ./Oceanbox.FvcomKit.fsproj;
versionMatch = builtins.match ".*<Version>([^<]+)</Version>.*" (
builtins.readFile projectFile
);
version = builtins.head versionMatch;
in
buildDotnetModule {
name = name;
pname = name;
version = version;
src = ./.;
buildInputs = [
projnet
SDSLite
];
projectFile = "Oceanbox.FvcomKit.fsproj";
inherit
dotnet-sdk
dotnet-runtime
;
nugetDeps = ./deps.json;
packNupkg = true;
# NOTE(mrtz): Can't package nuget without it
# [ref](https://github.com/dotnet/fsharp/issues/12320)
dotnetFlags = "--property:TargetsForTfmSpecificContentInPackage=";
}

272
src/deps.json Normal file
View File

@@ -0,0 +1,272 @@
[
{
"pname": "DynamicInterop",
"version": "0.9.1",
"hash": "sha256-IB76dA0+K/y/2s/qYL7AfVOF0+6W2hVIBgf9YdZ1oJY="
},
{
"pname": "FSharp.Core",
"version": "9.0.303",
"hash": "sha256-AxR6wqodeU23KOTgkUfIgbavgbcSuzD4UBP+tiFydgA="
},
{
"pname": "FSharp.Data",
"version": "6.3.0",
"hash": "sha256-zhVkSfqCljqr6UR0IUMOHUBlR61PvwYKq63PQ09yJPM="
},
{
"pname": "FSharp.Data",
"version": "6.4.1",
"hash": "sha256-+Z7zbD8cKmhHJWg7Z8XHJ8IeJXCWr/kgRl+VbbsMFw8="
},
{
"pname": "FSharp.Data.Csv.Core",
"version": "6.3.0",
"hash": "sha256-JdOr3NDmLPohkPpZaWjKqssw0+Wr1lVDtJwTNJ/JhcY="
},
{
"pname": "FSharp.Data.Csv.Core",
"version": "6.4.1",
"hash": "sha256-oz040beVF7WMONi3n0dPQlZD5deQWnClSXKRijgnw/k="
},
{
"pname": "FSharp.Data.Html.Core",
"version": "6.3.0",
"hash": "sha256-tSstVvAT9o+0Pr6cIReJOvh0kcthOWgt1CPzgIRoYRQ="
},
{
"pname": "FSharp.Data.Html.Core",
"version": "6.4.1",
"hash": "sha256-QBbvE8WXUVjS/0mW3aohZBuyfr3M7UGw7kt1oSrlq+s="
},
{
"pname": "FSharp.Data.Http",
"version": "6.3.0",
"hash": "sha256-/PzzLT0ev4miFswct+YscFDwoaq05BSJATM4fPvxk8o="
},
{
"pname": "FSharp.Data.Http",
"version": "6.4.1",
"hash": "sha256-0YD/jSCppE1siXrUcTx0OmVdgsjMk+gn0pHp+3GS3V4="
},
{
"pname": "FSharp.Data.Json.Core",
"version": "6.3.0",
"hash": "sha256-MFe88psxmHWGQYoG8NXi4z33TlWO+dMwOV4NViaUmTM="
},
{
"pname": "FSharp.Data.Json.Core",
"version": "6.4.1",
"hash": "sha256-0Fmo0f1jC3s+Dime8j2oqLnOK+elqo1xWmktpEYrZlk="
},
{
"pname": "FSharp.Data.Runtime.Utilities",
"version": "6.3.0",
"hash": "sha256-psc/tsHLYrorjeBBBLviwwA57XMFXUP2ywZqLMzfxac="
},
{
"pname": "FSharp.Data.Runtime.Utilities",
"version": "6.4.1",
"hash": "sha256-rNo2XQMME1zrPaIezD15P0RoTu8wyhtiJyB99Qp1hcE="
},
{
"pname": "FSharp.Data.WorldBank.Core",
"version": "6.3.0",
"hash": "sha256-QdL5ylUCvvrhvnnSPWj4MfN7B78hMbb5IRmozK7oJjM="
},
{
"pname": "FSharp.Data.WorldBank.Core",
"version": "6.4.1",
"hash": "sha256-nUyyziwpY58UnBNpqFoe/1bgDfQIq6gOqtQIwAo7x/c="
},
{
"pname": "FSharp.Data.Xml.Core",
"version": "6.3.0",
"hash": "sha256-67ftkfQJZ3iD62YKFh8Tu9Fuusb96KlKWxgykP1Wd9U="
},
{
"pname": "FSharp.Data.Xml.Core",
"version": "6.4.1",
"hash": "sha256-ZiD2aiD5yUZR4CDZl6mh4ji1G3xvm4Yd9Gya/e+D32w="
},
{
"pname": "FSharpPlus",
"version": "1.5.0",
"hash": "sha256-jQUlF3hsi3xpg+AdTnQw2L+lzbvTh5BIyLXCdVT6u6M="
},
{
"pname": "FSharpPlus",
"version": "1.7.0",
"hash": "sha256-6hDoDOnMFXQC5Hrk6Fhd+Wj+PbPFXzL9+xLIqgILJuY="
},
{
"pname": "FsPickler",
"version": "5.3.2",
"hash": "sha256-hjtm55aPJllzcVMPjFP4KYiEEBYtCcrUhbVOR+34agg="
},
{
"pname": "KdTree",
"version": "1.4.1",
"hash": "sha256-R4+L26pJoliLiwMuxmJDoa3Vf16gBq417fN+iNCy7Yc="
},
{
"pname": "MathNet.Numerics",
"version": "5.0.0",
"hash": "sha256-RHJCVM6OxquJF7n5Mbe/oNbucBbkge6ULcbAczOgmVo="
},
{
"pname": "MathNet.Numerics.FSharp",
"version": "5.0.0",
"hash": "sha256-pPbh8JdmMjBgEu84c/qV4YJ+LLr4+c31C6t++u29qBs="
},
{
"pname": "MessagePack",
"version": "3.1.3",
"hash": "sha256-OBn7iltr/rdE7ZKmv0MCUQSS+6OJKUYtlHdTbhEwzzE="
},
{
"pname": "MessagePack.Annotations",
"version": "3.1.3",
"hash": "sha256-o+T3u+xaHtW1c7AeWysCmIDUfN8lRhes2LoW5iQBafs="
},
{
"pname": "MessagePackAnalyzer",
"version": "3.1.3",
"hash": "sha256-5t4Av4CQ8HI7y9aAw+2qcOp+fsY0/3PdaFPJeCEAXQ0="
},
{
"pname": "Microsoft.NET.StringTools",
"version": "17.11.4",
"hash": "sha256-lWfzY35WQ+iKS9TpuztDTljgF9CIORhFhFEm0p1dVBE="
},
{
"pname": "Microsoft.NETCore.Platforms",
"version": "1.1.0",
"hash": "sha256-FeM40ktcObQJk4nMYShB61H/E8B7tIKfl9ObJ0IOcCM="
},
{
"pname": "Microsoft.NETCore.Targets",
"version": "1.1.0",
"hash": "sha256-0AqQ2gMS8iNlYkrD+BxtIg7cXMnr9xZHtKAuN4bjfaQ="
},
{
"pname": "ProjNET",
"version": "2.0.0",
"hash": "sha256-GjBnuGXmdFagIw9mX51Kpu/nn4gXta6a0cK/dxOWaZY="
},
{
"pname": "runtime.any.System.IO",
"version": "4.3.0",
"hash": "sha256-vej7ySRhyvM3pYh/ITMdC25ivSd0WLZAaIQbYj/6HVE="
},
{
"pname": "runtime.any.System.Reflection",
"version": "4.3.0",
"hash": "sha256-ns6f++lSA+bi1xXgmW1JkWFb2NaMD+w+YNTfMvyAiQk="
},
{
"pname": "runtime.any.System.Reflection.Primitives",
"version": "4.3.0",
"hash": "sha256-LkPXtiDQM3BcdYkAm5uSNOiz3uF4J45qpxn5aBiqNXQ="
},
{
"pname": "runtime.any.System.Runtime",
"version": "4.3.0",
"hash": "sha256-qwhNXBaJ1DtDkuRacgHwnZmOZ1u9q7N8j0cWOLYOELM="
},
{
"pname": "runtime.any.System.Text.Encoding",
"version": "4.3.0",
"hash": "sha256-Q18B9q26MkWZx68exUfQT30+0PGmpFlDgaF0TnaIGCs="
},
{
"pname": "runtime.any.System.Threading.Tasks",
"version": "4.3.0",
"hash": "sha256-agdOM0NXupfHbKAQzQT8XgbI9B8hVEh+a/2vqeHctg4="
},
{
"pname": "runtime.native.System",
"version": "4.3.0",
"hash": "sha256-ZBZaodnjvLXATWpXXakFgcy6P+gjhshFXmglrL5xD5Y="
},
{
"pname": "runtime.unix.System.Private.Uri",
"version": "4.3.0",
"hash": "sha256-c5tXWhE/fYbJVl9rXs0uHh3pTsg44YD1dJvyOA0WoMs="
},
{
"pname": "Serilog",
"version": "4.2.0",
"hash": "sha256-7f3EpCsEbDxXgsuhE430KVI14p7oDUuCtwRpOCqtnbs="
},
{
"pname": "Serilog.Sinks.Console",
"version": "6.0.0",
"hash": "sha256-QH8ykDkLssJ99Fgl+ZBFBr+RQRl0wRTkeccQuuGLyro="
},
{
"pname": "Serilog.Sinks.File",
"version": "6.0.0",
"hash": "sha256-KQmlUpG9ovRpNqKhKe6rz3XMLUjkBqjyQhEm2hV5Sow="
},
{
"pname": "Serilog.Sinks.Seq",
"version": "9.0.0",
"hash": "sha256-NnAkRbxwQGdNXz6DDONRxorNh1nqH2TfAQtokbq5qDw="
},
{
"pname": "System.IO",
"version": "4.3.0",
"hash": "sha256-ruynQHekFP5wPrDiVyhNiRIXeZ/I9NpjK5pU+HPDiRY="
},
{
"pname": "System.Memory",
"version": "4.5.3",
"hash": "sha256-Cvl7RbRbRu9qKzeRBWjavUkseT2jhZBUWV1SPipUWFk="
},
{
"pname": "System.Numerics.Vectors",
"version": "4.5.0",
"hash": "sha256-qdSTIFgf2htPS+YhLGjAGiLN8igCYJnCCo6r78+Q+c8="
},
{
"pname": "System.Private.Uri",
"version": "4.3.0",
"hash": "sha256-fVfgcoP4AVN1E5wHZbKBIOPYZ/xBeSIdsNF+bdukIRM="
},
{
"pname": "System.Reflection",
"version": "4.3.0",
"hash": "sha256-NQSZRpZLvtPWDlvmMIdGxcVuyUnw92ZURo0hXsEshXY="
},
{
"pname": "System.Reflection.Emit.ILGeneration",
"version": "4.3.0",
"hash": "sha256-mKRknEHNls4gkRwrEgi39B+vSaAz/Gt3IALtS98xNnA="
},
{
"pname": "System.Reflection.Emit.Lightweight",
"version": "4.3.0",
"hash": "sha256-rKx4a9yZKcajloSZHr4CKTVJ6Vjh95ni+zszPxWjh2I="
},
{
"pname": "System.Reflection.Primitives",
"version": "4.3.0",
"hash": "sha256-5ogwWB4vlQTl3jjk1xjniG2ozbFIjZTL9ug0usZQuBM="
},
{
"pname": "System.Runtime",
"version": "4.3.0",
"hash": "sha256-51813WXpBIsuA6fUtE5XaRQjcWdQ2/lmEokJt97u0Rg="
},
{
"pname": "System.Text.Encoding",
"version": "4.3.0",
"hash": "sha256-GctHVGLZAa/rqkBNhsBGnsiWdKyv6VDubYpGkuOkBLg="
},
{
"pname": "System.Threading.Tasks",
"version": "4.3.0",
"hash": "sha256-Z5rXfJ1EXp3G32IKZGiZ6koMjRu0n8C1NGrwpdIen4w="
}
]

View File

@@ -1,18 +0,0 @@
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

View File

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

189
xtest/Arome.fs Normal file
View File

@@ -0,0 +1,189 @@
module Arome
open System
open Microsoft.Research.Science.Data
open Xunit
open FsUnit.Xunit
open FsUnit.CustomMatchers
open Serilog
open ProjNet.FSharp
open Oceanbox.FvcomKit
let logger =
LoggerConfiguration()
.MinimumLevel.Is(Events.LogEventLevel.Verbose)
.WriteTo.Console()
.CreateLogger()
do Log.Logger <- logger
[<Literal>]
let private path = "/data/archives/Arome/meps_det_sfc_20220102T00Z.nc"
let trans = makeTransform CoordSys.WGS84 (CoordSys.LCCMet ())
// Use Netcdf to open dataset
let private openDataset (path: string) : DataSet =
let sw = Diagnostics.Stopwatch.StartNew()
let uri = NetCDF4.NetCDFUri()
do uri.FileName <- path
do uri.OpenMode <- ResourceOpenMode.ReadOnly
do uri.Deflate <- NetCDF4.DeflateLevel.Off
let ds = NetCDF4.NetCDFDataSet.Open uri
do Log.Debug $"openDataSet: {path} completed in {sw.ElapsedMilliseconds}ms"
ds
let private aromeGrid = {
Arome.SquareGrid.empty with
dimensions = 949, 1069
BBox = {
minX = -18.12241427
maxX = 54.24126163
minY = 49.7653854
maxY = 75.22869642
center = 27.12063081, 37.61434821
}
squareSize = 2.444065489
}
[<Fact>]
let ``Create grid``() =
use ds = openDataset path
let res = Arome.getGrid ds
// "Should be able to create square grid from /data/archives/Arome/meps_det_sfc_20220102T00Z.nc"
res |> should be (ofCase <@ Result<Arome.SquareGrid, string>.Ok @>)
[<Fact>]
let ``test grid regularity``() =
let trans = makeTransform CoordSys.WGS84 (CoordSys.LCCMet ())
use ds = openDataset path
let dimensions = ds.Dimensions["x"].Length, ds.Dimensions["y"].Length
let longs : float array2d = ds["longitude"].GetData () :?> float[,]
let lats : float array2d = ds["latitude"].GetData () :?> float[,]
// NOTE: The netcdf file dimensions are defined as (y, x)
let width = Array2D.length2 longs
let height = Array2D.length1 lats
let points : Arome.Points array =
let result = Array.create (width * height) Arome.Points.Zero
for i in 0 .. height - 1 do
for j in 0 .. width - 1 do
let lat = lats[i, j]
let lon = longs[i, j]
let p = lon, lat
// NOTE(simkir): Convert to lambert and undo all I've done... :(
let x, y = trans.project p
result[i * width + j] <- Arome.Points.OfTuple (single x, single y)
result
let mutable acc = 0.0f
for i in 0 .. height - 1 do
for j in 0 .. width - 2 do
let p0 = points[i * width + j]
let p1 = points[i * width + j + 1]
let dx = if p0.x < p1.x then p1.x - p0.x else p0.x - p1.x
acc <- acc + dx
let count = width * height
let avgDiffX = acc / single count
Log.Debug("Avg. X distance: {Avg}m", avgDiffX)
acc <- 0.0f
for i in 0 .. height - 2 do
for j in 0 .. width - 1 do
let p0 = points[i * width + j]
let p1 = points[(i + 1) * width + j]
let dy = if p0.y < p1.y then p1.y - p0.y else p0.y - p1.y
acc <- acc + dy
let avgDiffY = acc / single count
Log.Debug("Avg. Y distance: {Avg}m", avgDiffY)
[<Fact>]
let ``point within first rect``() =
use ds = openDataset path
let res = Arome.getGrid ds
match res with
| Ok grid ->
let width, _height = grid.dimensions
let p0 = grid.points[0 * width + 0]
let p1 = grid.points[1 * width + 1]
let rect = Arome.Rect.OfPoints p0 p1
// { x = -1064984.25f y = -1353489.375f }, { x = -1062474.625f y = -1350985.875f }
let p2 = Arome.Points.OfTuple (-1063000.25f, -1353486.0f)
let within = Arome.Rect.pointWithin rect p2
within |> should equal true
| Error _ ->
failwith "Should create grid"
[<Fact>]
let ``Read uvs for index (1, 1)``() =
use ds = openDataset path
let res = Arome.getGrid ds
match res with
| Ok grid ->
let idx =
let lat = 50.35
let lon = 0.304
let p = trans.project((lon, lat))
Arome.tryFindIndex grid p
match idx with
| Some (i, j) ->
let u, v = Arome.readUV ds 0 i j
(u, v) |> should equal (8.530239f, 6.659096f) // "Find uv (8.530239, 6.659096) at index (1, 1)"
| None ->
failwith "Should find idx"
| Error _ ->
failwith "Should find grid"
[<Fact>]
let ``Try search index (1, 1069) based on map coords``() =
let lat = 72.800
let lon = -17.850
let p = trans.project((lon, lat))
let res =
use ds = openDataset path
Arome.getGrid ds
match res with
| Ok grid ->
let idx = Arome.tryFindIndex grid p
idx |> should equal (Some (4, 1067)) // "Should find idx (1, 1069)"
| Error _ ->
failwith "Should find grid"
[<Fact>]
let ``Read uvs for pos (60.3562, 5.2178)``() =
let lat = 60.3562
let lon = 5.2178
let p = trans.project((lon, lat))
use ds = openDataset path
let res = Arome.getGrid ds
match res with
| Ok grid ->
let idx = Arome.tryFindIndex grid p
match idx with
| Some (i, j) ->
let u, v = Arome.readUV ds 0 i j
// "Find uv (2.367153168, -0.3955917358) at pos (60.3562, 5.2178) almost in Bergen"
(u, v) |> should equal (-0.3750343323f, 7.744056702f)
| None ->
failwith "Should find idx"
| Error _ ->
failwith "Should find grid"
[<Fact>]
let ``Read uvs for t 23 x 43 y 144``() =
use ds = openDataset path
let u, v = Arome.readUV ds 23 43 144
// "Find uv (2.367153168, -0.3955917358) at pos (60.3562, 5.2178) almost in Bergen"
(u, v) |> should equal (11.77926254f, 5.764093399f)

38
xtest/xtest.fsproj Normal file
View File

@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
<RootNamespace>xtest</RootNamespace>
<TargetFramework>net9.0</TargetFramework>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<!--
This template uses native xUnit.net command line options when using 'dotnet run' and
VSTest by default when using 'dotnet test'. For more information on how to enable support
for Microsoft Testing Platform, please visit:
https://xunit.net/docs/getting-started/v3/microsoft-testing-platform
-->
</PropertyGroup>
<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<Compile Include="Arome.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FsUnit" Version="7.1.1" />
<PackageReference Include="FsUnit.xUnit" Version="7.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="Oceanbox.SDSLite" Version="2.8.0" />
<PackageReference Include="xunit.v3" Version="3.2.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\src\Oceanbox.FvcomKit.fsproj" />
</ItemGroup>
</Project>

3
xtest/xunit.runner.json Normal file
View File

@@ -0,0 +1,3 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json"
}