diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 00000000..5e71f5bf --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1 @@ +CMakePresets.json diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..0ca21696 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,19 @@ +Checks: + - '-*' + - 'bugprone-*' + - 'clang-analyzer-*' + - 'cppcoreguidelines-*' + - 'misc-*' + - 'modernize-*' + - 'performance-*' + - '-bugprone-easily-swappable-parameters' + - '-cppcoreguidelines-avoid-do-while' + - '-cppcoreguidelines-avoid-non-const-global-variables' + - '-cppcoreguidelines-pro-type-reinterpret-cast' + - '-cppcoreguidelines-pro-type-union-access' + - '-misc-include-cleaner' + - '-misc-non-private-member-variables-in-classes' + - '-misc-use-anonymous-namespace' + - '-modernize-return-braced-init-list' + - '-modernize-use-trailing-return-type' +FormatStyle: 'file' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba82d35a..e3033807 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ on: concurrency: # Cancel in-progress jobs for the same pull request - group: ${{ github.head_ref || github.run_id }} + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: @@ -20,61 +20,130 @@ jobs: fail-fast: false matrix: include: - - name: win64_msvc2022 + - name: windows_msvc2022_x64 os: windows-2022 build_type: Release env_cc: '' env_cxx: '' compiler: msvc + cppflags: '' + ldflags: '' msvc_arch: x64 msvc_version: 2022 - qt_version: 6.7.2 - qt_arch_aqt: win64_msvc2019_64 - qt_arch_dir: msvc2019_64 + qt_version: 6.9.2 + qt_arch_aqt: win64_msvc2022_64 + qt_arch_dir: msvc2022_64 qt_modules: qtimageformats qtmultimedia qtpositioning qtserialport qt_tools: '' - conan_arch: x86_64 - conan_compiler: Visual Studio - conan_compiler_version: 17 - conan_compiler_runtime: --settings compiler.runtime=MD conan_package_manager: '' + conan_profile: scwx-windows_msvc2022_x64 + appimage_arch: '' artifact_suffix: windows-x64 - - name: linux64_gcc + - name: linux_gcc_x64 os: ubuntu-22.04 build_type: Release env_cc: gcc-11 env_cxx: g++-11 compiler: gcc - qt_version: 6.7.2 + cppflags: '' + ldflags: '' + qt_version: 6.9.2 qt_arch_aqt: linux_gcc_64 qt_arch_dir: gcc_64 qt_modules: qtimageformats qtmultimedia qtpositioning qtserialport qt_tools: '' - conan_arch: x86_64 - conan_compiler: gcc - conan_compiler_version: 11 - conan_compiler_runtime: '' conan_package_manager: --conf tools.system.package_manager:mode=install --conf tools.system.package_manager:sudo=True + conan_profile: scwx-linux_gcc-11 + appimage_arch: x86_64 artifact_suffix: linux-x64 + compiler_packages: '' + - name: linux_clang_x64 + os: ubuntu-24.04 + build_type: Release + env_cc: clang-17 + env_cxx: clang++-17 + compiler: clang + cppflags: '' + ldflags: '' + qt_version: 6.9.2 + qt_arch_aqt: linux_gcc_64 + qt_arch_dir: gcc_64 + qt_modules: qtimageformats qtmultimedia qtpositioning qtserialport + qt_tools: '' + conan_package_manager: --conf tools.system.package_manager:mode=install --conf tools.system.package_manager:sudo=True + conan_profile: scwx-linux_clang-17 + appimage_arch: x86_64 + artifact_suffix: linux-clang-x64 + compiler_packages: clang-17 + - name: linux_gcc_arm64 + os: ubuntu-24.04-arm + build_type: Release + env_cc: gcc-11 + env_cxx: g++-11 + compiler: gcc + cppflags: '' + ldflags: '' + qt_version: 6.9.2 + qt_arch_aqt: linux_gcc_arm64 + qt_arch_dir: gcc_arm64 + qt_modules: qtimageformats qtmultimedia qtpositioning qtserialport + qt_tools: '' + conan_package_manager: --conf tools.system.package_manager:mode=install --conf tools.system.package_manager:sudo=True + conan_profile: scwx-linux_gcc-11_armv8 + appimage_arch: aarch64 + artifact_suffix: linux-arm64 + compiler_packages: g++-11 + - name: macos_clang18_x64 + os: macos-13 + build_type: Release + env_cc: clang + env_cxx: clang++ + compiler: clang + qt_version: 6.9.2 + qt_arch_aqt: clang_64 + qt_arch_dir: macos + qt_modules: qtimageformats qtmultimedia qtpositioning qtserialport + qt_tools: '' + conan_package_manager: '' + conan_profile: scwx-macos_clang-18 + appimage_arch: '' + artifact_suffix: macos-x64 + - name: macos_clang18_arm64 + os: macos-14 + build_type: Release + env_cc: clang + env_cxx: clang++ + compiler: clang + qt_version: 6.9.2 + qt_arch_aqt: clang_64 + qt_arch_dir: macos + qt_modules: qtimageformats qtmultimedia qtpositioning qtserialport + qt_tools: '' + conan_package_manager: '' + conan_profile: scwx-macos_clang-18_armv8 + appimage_arch: '' + artifact_suffix: macos-arm64 name: ${{ matrix.name }} env: CC: ${{ matrix.env_cc }} CXX: ${{ matrix.env_cxx }} - SCWX_VERSION: v0.4.5 + SCWX_VERSION: v0.5.1 runs-on: ${{ matrix.os }} steps: - name: Setup run: git config --global core.longpaths true - + - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: source submodules: recursive - name: Install Qt - uses: jurplel/install-qt-action@v3 + uses: jdpurcell/install-qt-action@v5 + env: + AQT_CONFIG: ${{ github.workspace }}/source/tools/aqt-settings.ini with: version: ${{ matrix.qt_version }} arch: ${{ matrix.qt_arch_aqt }} @@ -89,50 +158,79 @@ jobs: vsversion: ${{ matrix.msvc_version }} - name: Setup Ubuntu Environment - if: matrix.os == 'ubuntu-22.04' + if: ${{ startsWith(matrix.os, 'ubuntu') }} shell: bash run: | sudo apt-get install doxygen \ libfuse2 \ - ninja-build + ninja-build \ + wayland-protocols \ + libwayland-dev \ + libwayland-egl-backend-dev \ + flatpak \ + flatpak-builder \ + ${{ matrix.compiler_packages }} + + - name: Setup macOS Environment + if: ${{ startsWith(matrix.os, 'macos') }} + shell: bash + run: | + brew install llvm@18 + LLVM_PATH=$(brew --prefix llvm@18) + echo "CC=${LLVM_PATH}/bin/clang" >> $GITHUB_ENV + echo "CXX=${LLVM_PATH}/bin/clang++" >> $GITHUB_ENV + echo "CPPFLAGS=-I${LLVM_PATH}/include" >> $GITHUB_ENV + echo "LDFLAGS=-L${LLVM_PATH}/lib -L${LLVM_PATH}/lib/c++" >> $GITHUB_ENV - name: Setup Python Environment shell: pwsh run: | pip install geopandas ` - GitPython + GitPython ` + conan + + - name: Cache Conan Packages + uses: actions/cache@v4 + with: + path: ~/.conan2 + key: build-${{ matrix.conan_profile }}-${{ hashFiles('./source/conanfile.py', './source/tools/conan/profiles/*') }} - name: Install Conan Packages shell: pwsh run: | - pip install "conan<2.0" - conan profile new default --detect - conan install ./source/ ` + conan config install ` + ./source/tools/conan/profiles/${{ matrix.conan_profile }} ` + -tf profiles + mkdir build + cd build + mkdir conan + conan install ../source/ ` --remote conancenter ` --build missing ` - --settings arch=${{ matrix.conan_arch }} ` - --settings build_type=${{ matrix.build_type }} ` - --settings compiler="${{ matrix.conan_compiler }}" ` - --settings compiler.version=${{ matrix.conan_compiler_version }} ` - ${{ matrix.conan_compiler_runtime }} ` + --profile:all ${{ matrix.conan_profile }} ` + --settings:all build_type=${{ matrix.build_type }} ` + --output-folder ./conan/ ` ${{ matrix.conan_package_manager }} - name: Build Supercell Wx shell: pwsh run: | - mkdir build cd build cmake ../source/ ` -G Ninja ` -DCMAKE_BUILD_TYPE="${{ matrix.build_type }}" ` - -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/supercell-wx" + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES="${{ github.workspace }}/source/external/cmake-conan/conan_provider.cmake" ` + -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/supercell-wx" ` + -DCONAN_HOST_PROFILE="${{ matrix.conan_profile }}" ` + -DCONAN_BUILD_PROFILE="${{ matrix.conan_profile }}" ninja supercell-wx wxtest - name: Separate Debug Symbols (Linux) - if: matrix.os == 'ubuntu-22.04' + if: ${{ startsWith(matrix.os, 'ubuntu') }} shell: bash run: | cd build/ + cd Release/ cd bin/ objcopy --only-keep-debug supercell-wx supercell-wx.debug objcopy --strip-debug --strip-unneeded supercell-wx @@ -148,7 +246,7 @@ jobs: cmake --install . --component supercell-wx - name: Collect Artifacts - if: matrix.os == 'ubuntu-22.04' + if: ${{ startsWith(matrix.os, 'ubuntu') }} shell: bash run: | pushd supercell-wx/ @@ -161,6 +259,8 @@ jobs: cd plugins/ mkdir -p sqldrivers/ cp "${RUNNER_WORKSPACE}/Qt/${{ matrix.qt_version }}/${{ matrix.qt_arch_dir }}/plugins/sqldrivers/libqsqlite.so" sqldrivers/ + mkdir -p platforms/ + cp ${RUNNER_WORKSPACE}/Qt/${{ matrix.qt_version }}/${{ matrix.qt_arch_dir }}/plugins/platforms/libqwayland* platforms/ cd .. popd tar -czf supercell-wx-${{ matrix.artifact_suffix }}.tar.gz supercell-wx/ @@ -177,23 +277,23 @@ jobs: uses: actions/upload-artifact@v4 with: name: supercell-wx-debug-${{ matrix.artifact_suffix }} - path: ${{ github.workspace }}/build/bin/*.pdb + path: ${{ github.workspace }}/build/Release/bin/*.pdb - name: Upload Artifacts (Linux) - if: matrix.os == 'ubuntu-22.04' + if: ${{ startsWith(matrix.os, 'ubuntu') }} uses: actions/upload-artifact@v4 with: name: supercell-wx-${{ matrix.artifact_suffix }} path: ${{ github.workspace }}/supercell-wx-${{ matrix.artifact_suffix }}.tar.gz - name: Upload Debug Artifacts (Linux) - if: matrix.os == 'ubuntu-22.04' + if: ${{ startsWith(matrix.os, 'ubuntu') }} uses: actions/upload-artifact@v4 with: name: supercell-wx-debug-${{ matrix.artifact_suffix }} path: | - ${{ github.workspace }}/build/bin/*.debug - ${{ github.workspace }}/build/lib/*.debug + ${{ github.workspace }}/build/Release/bin/*.debug + ${{ github.workspace }}/build/Release/lib/*.debug - name: Build Installer (Windows) if: matrix.os == 'windows-2022' @@ -210,42 +310,90 @@ jobs: path: ${{ github.workspace }}/build/supercell-wx-*.msi* - name: Build AppImage (Linux) - if: matrix.os == 'ubuntu-22.04' + if: ${{ startsWith(matrix.os, 'ubuntu') }} env: - APPIMAGE_DIR: ${{ github.workspace }}/supercell-wx/ - LDAI_UPDATE_INFORMATION: gh-releases-zsync|dpaulat|supercell-wx|latest|*x86_64.AppImage.zsync - LDAI_OUTPUT: supercell-wx-${{ env.SCWX_VERSION }}-x86_64.AppImage + INSTALL_DIR: ${{ github.workspace }}/supercell-wx/ + APPIMAGE_DIR: ${{ github.workspace }}/supercell-wx-appimage/ + LDAI_UPDATE_INFORMATION: gh-releases-zsync|dpaulat|supercell-wx|latest|*${{ matrix.appimage_arch }}.AppImage.zsync + LDAI_OUTPUT: supercell-wx-${{ env.SCWX_VERSION }}-${{ matrix.appimage_arch }}.AppImage LINUXDEPLOY_OUTPUT_APP_NAME: supercell-wx LINUXDEPLOY_OUTPUT_VERSION: ${{ env.SCWX_VERSION }} shell: bash run: | - wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage - chmod +x linuxdeploy-x86_64.AppImage + wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-${{ matrix.appimage_arch }}.AppImage + chmod +x linuxdeploy-${{ matrix.appimage_arch }}.AppImage cp "${{ github.workspace }}/source/scwx-qt/res/icons/scwx-256.png" supercell-wx.png cp "${{ github.workspace }}/source/scwx-qt/res/linux/supercell-wx.desktop" . + cp -r "${{ env.INSTALL_DIR }}" "${{ env.APPIMAGE_DIR }}" pushd "${{ env.APPIMAGE_DIR }}" mkdir -p usr/ mv bin/ usr/ mv lib/ usr/ mv plugins/ usr/ popd - ./linuxdeploy-x86_64.AppImage --appdir ${{ env.APPIMAGE_DIR }} -i supercell-wx.png -d supercell-wx.desktop - ./linuxdeploy-x86_64.AppImage --appdir ${{ env.APPIMAGE_DIR }} --output appimage - rm -f linuxdeploy-x86_64.AppImage + ./linuxdeploy-${{ matrix.appimage_arch }}.AppImage --appdir ${{ env.APPIMAGE_DIR }} -i supercell-wx.png -d supercell-wx.desktop + ./linuxdeploy-${{ matrix.appimage_arch }}.AppImage --appdir ${{ env.APPIMAGE_DIR }} --output appimage + rm -f linuxdeploy-${{ matrix.appimage_arch }}.AppImage - name: Upload AppImage (Linux) - if: matrix.os == 'ubuntu-22.04' + if: ${{ startsWith(matrix.os, 'ubuntu') }} uses: actions/upload-artifact@v4 with: - name: supercell-wx-appimage-x64 - path: ${{ github.workspace }}/*-x86_64.AppImage* + name: supercell-wx-appimage-${{ matrix.artifact_suffix }} + path: ${{ github.workspace }}/*-${{ matrix.appimage_arch }}.AppImage* + + - name: Build FlatPak (Linux) + if: ${{ startsWith(matrix.os, 'ubuntu') }} + env: + INSTALL_DIR: ${{ github.workspace }}/supercell-wx/ + FLATPAK_DIR: ${{ github.workspace }}/supercell-wx-flatpak/ + shell: bash + run: | + cp -r ${{ env.INSTALL_DIR }} ${{ env.FLATPAK_DIR }} + # Copy krb5 libraries to flatpak + cp /usr/lib/*/libkrb5.so* \ + /usr/lib/*/libkrb5support.so* \ + /usr/lib/*/libgssapi_krb5.so* \ + /usr/lib/*/libk5crypto.so* \ + /usr/lib/*/libkeyutils.so* \ + ${{ env.FLATPAK_DIR }}/lib + + flatpak remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo + flatpak-builder --force-clean \ + --user \ + --install-deps-from=flathub \ + --repo=flatpak-repo \ + --install flatpak-build \ + ${{ github.workspace }}/source/tools/net.supercellwx.app.yml + flatpak build-bundle flatpak-repo supercell-wx.flatpak net.supercellwx.app + + - name: Upload FlatPak (Linux) + if: ${{ startsWith(matrix.os, 'ubuntu') }} + uses: actions/upload-artifact@v4 + with: + name: supercell-wx-flatpak-${{ matrix.artifact_suffix }} + path: ${{ github.workspace }}/supercell-wx.flatpak + + - name: Build Disk Image (macOS) + if: ${{ startsWith(matrix.os, 'macos') }} + shell: pwsh + run: | + cd build + cpack + + - name: Upload Disk Image (macOS) + if: ${{ startsWith(matrix.os, 'macos') }} + uses: actions/upload-artifact@v4 + with: + name: supercell-wx-${{ matrix.artifact_suffix }} + path: ${{ github.workspace }}/build/supercell-wx-*.dmg* - name: Test Supercell Wx working-directory: ${{ github.workspace }}/build env: MAPBOX_API_KEY: ${{ secrets.MAPBOX_API_KEY }} MAPTILER_API_KEY: ${{ secrets.MAPTILER_API_KEY }} - run: ctest -C ${{ matrix.build_type }} --exclude-regex test_mln.* + run: ctest -C ${{ matrix.build_type }} --exclude-regex "test_mln.*|NtpClient.*|UpdateManager.*" - name: Upload Test Logs if: ${{ !cancelled() }} diff --git a/.github/workflows/clang-format-check.yml b/.github/workflows/clang-format-check.yml new file mode 100644 index 00000000..7eaa1e95 --- /dev/null +++ b/.github/workflows/clang-format-check.yml @@ -0,0 +1,42 @@ +name: clang-format-check + +on: + workflow_dispatch: + pull_request: + branches: + - 'develop' + +concurrency: + # Cancel in-progress jobs for the same pull request + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + format: + runs-on: ubuntu-24.04 + steps: + + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + submodules: false + + - name: Update References + shell: bash + run: | + git fetch origin develop + + - name: Setup Ubuntu Environment + shell: bash + run: | + sudo apt-get install clang-format-19 + sudo rm -f /usr/bin/clang-format + sudo ln -s /usr/bin/clang-format-19 /usr/bin/clang-format + + - name: Check Formatting + shell: bash + run: | + MERGE_BASE=$(git merge-base origin/develop ${{ github.event.pull_request.head.sha || github.ref }}) + echo "Comparing against ${MERGE_BASE}" + git clang-format-19 --diff --style=file -v ${MERGE_BASE} diff --git a/.github/workflows/clang-tidy-comments.yml b/.github/workflows/clang-tidy-comments.yml new file mode 100644 index 00000000..9a6df52d --- /dev/null +++ b/.github/workflows/clang-tidy-comments.yml @@ -0,0 +1,21 @@ +name: Post clang-tidy Review Comments + +on: + workflow_run: + workflows: ["clang-tidy-review"] + types: + - completed + +jobs: + build: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion != 'cancelled' }} + + steps: + - name: Post Comments + uses: ZedThree/clang-tidy-review/post@v0.21.0 + with: + lgtm_comment_body: '' + annotations: false + max_comments: 25 + num_comments_as_exitcode: false diff --git a/.github/workflows/clang-tidy-review.yml b/.github/workflows/clang-tidy-review.yml new file mode 100644 index 00000000..b3de8edf --- /dev/null +++ b/.github/workflows/clang-tidy-review.yml @@ -0,0 +1,152 @@ +name: clang-tidy-review + +on: + pull_request: + branches: + - 'develop' + +concurrency: + # Cancel in-progress jobs for the same pull request + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + build: + strategy: + matrix: + include: + - name: linux_clang-tidy_x64 + os: ubuntu-24.04 + build_type: Release + env_cc: clang-18 + env_cxx: clang++-18 + qt_version: 6.9.2 + qt_arch_aqt: linux_gcc_64 + qt_modules: qtimageformats qtmultimedia qtpositioning qtserialport + qt_tools: '' + conan_package_manager: --conf tools.system.package_manager:mode=install --conf tools.system.package_manager:sudo=True + conan_profile: scwx-linux_clang-18 + compiler_packages: clang-18 clang-tidy-18 + clang_tidy_binary: clang-tidy-18 + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + env: + CC: ${{ matrix.env_cc }} + CXX: ${{ matrix.env_cxx }} + steps: + + - name: Checkout + uses: actions/checkout@v5 + with: + path: source + submodules: recursive + + - name: Checkout clang-tidy-review Repository + uses: actions/checkout@v5 + with: + repository: ZedThree/clang-tidy-review + ref: v0.20.1 + path: clang-tidy-review + + - name: Install Qt + uses: jdpurcell/install-qt-action@v5 + env: + AQT_CONFIG: ${{ github.workspace }}/source/tools/aqt-settings.ini + with: + version: ${{ matrix.qt_version }} + arch: ${{ matrix.qt_arch_aqt }} + modules: ${{ matrix.qt_modules }} + tools: ${{ matrix.qt_tools }} + + - name: Setup Ubuntu Environment + if: ${{ startsWith(matrix.os, 'ubuntu') }} + shell: bash + run: | + sudo apt-get install doxygen \ + libfuse2 \ + ninja-build \ + wayland-protocols \ + libwayland-dev \ + libwayland-egl-backend-dev \ + ${{ matrix.compiler_packages }} + + - name: Setup Python Environment + shell: pwsh + run: | + pip install geopandas ` + GitPython ` + conan + pip install --break-system-packages clang-tidy-review/post/clang_tidy_review + + - name: Cache Conan Packages + uses: actions/cache@v4 + with: + path: ~/.conan2 + key: build-${{ matrix.conan_profile }}-${{ hashFiles('./source/conanfile.py', './source/tools/conan/profiles/*') }} + + - name: Install Conan Packages + shell: pwsh + run: | + conan config install ` + ./source/tools/conan/profiles/${{ matrix.conan_profile }} ` + -tf profiles + mkdir build + cd build + mkdir conan + conan install ../source/ ` + --remote conancenter ` + --build missing ` + --profile:all ${{ matrix.conan_profile }} ` + --settings:all build_type=${{ matrix.build_type }} ` + --output-folder ./conan/ ` + ${{ matrix.conan_package_manager }} + + - name: Autogenerate + shell: pwsh + run: | + cd build + cmake ../source/ ` + -G Ninja ` + -DCMAKE_BUILD_TYPE="${{ matrix.build_type }}" ` + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES="${{ github.workspace }}/source/external/cmake-conan/conan_provider.cmake" ` + -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/supercell-wx" ` + -DCONAN_HOST_PROFILE="${{ matrix.conan_profile }}" ` + -DCONAN_BUILD_PROFILE="${{ matrix.conan_profile }}" ` + -DCMAKE_EXPORT_COMPILE_COMMANDS=on + ninja glad_gl_core_33 ` + scwx-qt_generate_counties_db ` + scwx-qt_generate_versions ` + scwx-qt_autogen + + - name: Code Review + id: review + shell: bash + run: | + cd source + review --clang_tidy_binary=${{ matrix.clang_tidy_binary }} \ + --token=${{ github.token }} \ + --repo='${{ github.repository }}' \ + --pr='${{ github.event.pull_request.number }}' \ + --build_dir='../build' \ + --base_dir='${{ github.workspace }}/source' \ + --clang_tidy_checks='' \ + --config_file='' \ + --include='*.[ch],*.[ch]xx,*.[chi]pp,*.[ch]++,*.cc,*.hh' \ + --exclude='' \ + --apt-packages='' \ + --cmake-command='' \ + --max-comments=25 \ + --lgtm-comment-body='' \ + --split_workflow=true \ + --annotations=false \ + --parallel=0 + rsync -avzh --ignore-missing-args clang-tidy-review-output.json ../ + rsync -avzh --ignore-missing-args clang-tidy-review-metadata.json ../ + rsync -avzh --ignore-missing-args clang_fixes.json ../ + + - name: Upload Review + uses: ZedThree/clang-tidy-review/upload@v0.21.0 + + - name: Status Check + if: steps.review.outputs.total_comments > 0 + run: exit 1 diff --git a/.gitignore b/.gitignore index 9e6da487..cb46cec7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,20 @@ CMakeLists.txt.user CMakeCache.txt CMakeFiles CMakeScripts +CMakeUserPresets.json Testing cmake_install.cmake install_manifest.txt compile_commands.json CTestTestfile.cmake _deps + +# Editor directories +.idea/ +.vs/ + +# Python Virtual Environment +.venv/ + +# Specific excludes for Supercell Wx +tools/lib/user-setup.sh diff --git a/.gitmodules b/.gitmodules index 52ede30b..5bf3b307 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,39 +1,45 @@ -[submodule "external/cmake-conan"] - path = external/cmake-conan - url = https://github.com/conan-io/cmake-conan.git -[submodule "test/data"] - path = test/data - url = https://github.com/dpaulat/supercell-wx-test-data -[submodule "external/hsluv-c"] - path = external/hsluv-c - url = https://github.com/hsluv/hsluv-c.git -[submodule "external/stb"] - path = external/stb - url = https://github.com/nothings/stb.git [submodule "data"] path = data url = https://github.com/dpaulat/supercell-wx-data +[submodule "external/aws-sdk-cpp"] + path = external/aws-sdk-cpp + url = https://github.com/aws/aws-sdk-cpp.git +[submodule "external/cmake-conan"] + path = external/cmake-conan + url = https://github.com/conan-io/cmake-conan.git +[submodule "external/date"] + path = external/date + url = https://github.com/HowardHinnant/date.git +[submodule "external/glad"] + path = external/glad + url = https://github.com/Dav1dde/glad.git +[submodule "external/hsluv-c"] + path = external/hsluv-c + url = https://github.com/hsluv/hsluv-c.git [submodule "external/imgui"] path = external/imgui url = https://github.com/ocornut/imgui.git [submodule "external/imgui-backend-qt"] path = external/imgui-backend-qt url = https://github.com/dpaulat/imgui-backend-qt -[submodule "external/aws-sdk-cpp"] - path = external/aws-sdk-cpp - url = https://github.com/aws/aws-sdk-cpp.git -[submodule "external/date"] - path = external/date - url = https://github.com/HowardHinnant/date.git -[submodule "external/units"] - path = external/units - url = https://github.com/nholthaus/units.git -[submodule "external/textflowcpp"] - path = external/textflowcpp - url = https://github.com/catchorg/textflowcpp.git -[submodule "external/maplibre-native-qt"] - path = external/maplibre-native-qt - url = https://github.com/dpaulat/maplibre-native-qt.git [submodule "external/maplibre-native"] path = external/maplibre-native url = https://github.com/dpaulat/maplibre-gl-native.git +[submodule "external/maplibre-native-qt"] + path = external/maplibre-native-qt + url = https://github.com/dpaulat/maplibre-native-qt.git +[submodule "external/qt6ct"] + path = external/qt6ct + url = https://github.com/AdenKoperczak/qt6ct.git +[submodule "external/stb"] + path = external/stb + url = https://github.com/nothings/stb.git +[submodule "external/textflowcpp"] + path = external/textflowcpp + url = https://github.com/catchorg/textflowcpp.git +[submodule "external/units"] + path = external/units + url = https://github.com/nholthaus/units.git +[submodule "test/data"] + path = test/data + url = https://github.com/dpaulat/supercell-wx-test-data diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md index 154d0f6c..1608b2ae 100644 --- a/ACKNOWLEDGEMENTS.md +++ b/ACKNOWLEDGEMENTS.md @@ -23,7 +23,7 @@ Supercell Wx uses code from the following dependencies: | [FreeType GL](https://github.com/rougier/freetype-gl) | [BSD 2-Clause with views sentence](https://spdx.org/licenses/BSD-2-Clause-Views.html) | | [GeographicLib](https://geographiclib.sourceforge.io/) | [MIT License](https://spdx.org/licenses/MIT.html) | | [geos](https://libgeos.org/) | [GNU Lesser General Public License v2.1 or later](https://spdx.org/licenses/LGPL-2.1-or-later.html) | -| [GLEW](https://www.opengl.org/sdk/libs/GLEW/) | [MIT License](https://spdx.org/licenses/MIT.html) | +| [GLAD](https://github.com/Dav1dde/glad) | [MIT License](https://spdx.org/licenses/MIT.html) | | [GLM](https://github.com/g-truc/glm) | [MIT License](https://spdx.org/licenses/MIT.html) | | [GoogleTest](https://google.github.io/googletest/) | [BSD 3-Clause "New" or "Revised" License](https://spdx.org/licenses/BSD-3-Clause.html) | | [HSLuv](https://www.hsluv.org/) | [MIT License](https://spdx.org/licenses/MIT.html) | @@ -32,9 +32,12 @@ Supercell Wx uses code from the following dependencies: | [libpng](http://libpng.org/pub/png/libpng.html) | [PNG Reference Library version 2](https://spdx.org/licenses/libpng-2.0.html) | | [libxml2](http://xmlsoft.org/) | [MIT License](https://spdx.org/licenses/MIT.html) | | [MapLibre Native](https://maplibre.org/projects/maplibre-native/) | [BSD 2-Clause "Simplified" License](https://spdx.org/licenses/BSD-2-Clause.html) | +| [Mesa 3D](https://mesa3d.org/) | [MIT License](https://spdx.org/licenses/MIT.html) | | [nunicode](https://bitbucket.org/alekseyt/nunicode/src/master/) | [MIT License](https://spdx.org/licenses/MIT.html) | Modified for MapLibre Native | | [OpenSSL](https://www.openssl.org/) | [OpenSSL License](https://spdx.org/licenses/OpenSSL.html) | | [Qt](https://www.qt.io/) | [GNU Lesser General Public License v3.0 only](https://spdx.org/licenses/LGPL-3.0-only.html) | Qt Core, Qt GUI, Qt Multimedia, Qt Network, Qt OpenGL, Qt Positioning, Qt Serial Port, Qt SQL, Qt SVG, Qt Widgets
Additional Licenses: https://doc.qt.io/qt-6/licenses-used-in-qt.html | +| [qt6ct](https://github.com/trialuser02/qt6ct) | [BSD 2-Clause "Simplified" License](https://spdx.org/licenses/BSD-2-Clause.html) | +| [range-v3](https://github.com/ericniebler/range-v3) | [Boost Software License 1.0](https://spdx.org/licenses/BSL-1.0.html)
[MIT License](https://spdx.org/licenses/MIT.html)
[Stepanov and McJones, "Elements of Programming" license](https://github.com/ericniebler/range-v3/tree/0.12.0?tab=License-1-ov-file)
[SGI C++ Standard Template Library license](https://github.com/ericniebler/range-v3/tree/0.12.0?tab=License-1-ov-file) | | [re2](https://github.com/google/re2) | [BSD 3-Clause "New" or "Revised" License](https://spdx.org/licenses/BSD-3-Clause.html) | | [spdlog](https://github.com/gabime/spdlog) | [MIT License](https://spdx.org/licenses/MIT.html) | | [SQLite](https://www.sqlite.org/) | Public Domain | @@ -67,6 +70,7 @@ Supercell Wx uses assets from the following sources: | [Font Awesome Free](https://fontawesome.com/) | CC BY 4.0 License | | [Inconsolata](https://fonts.google.com/specimen/Inconsolata) | SIL Open Font License | | [NOAA's Weather and Climate Toolkit](https://www.ncdc.noaa.gov/wct/) | Public Domain | Default Color Tables | +| [qt6ct](https://github.com/trialuser02/qt6ct) | [BSD 2-Clause "Simplified" License](https://spdx.org/licenses/BSD-2-Clause.html) | | [Roboto Flex](https://fonts.google.com/specimen/Roboto+Flex) | SIL Open Font License | | [Supercell thunderstorm with dramatic clouds](https://www.shutterstock.com/image-photo/supercell-thunderstorm-dramatic-clouds-1354353521) | Shutterstock Standard License | Photo by John Sirlin diff --git a/CMakeLists.txt b/CMakeLists.txt index c06a46e6..e76cb8d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,14 @@ -cmake_minimum_required(VERSION 3.21) +cmake_minimum_required(VERSION 3.24) set(PROJECT_NAME supercell-wx) + +include(tools/scwx_config.cmake) + +set(CMAKE_OSX_DEPLOYMENT_TARGET 12.0) + +scwx_python_setup() + project(${PROJECT_NAME} - VERSION 0.4.5 + VERSION 0.5.1 DESCRIPTION "Supercell Wx is a free, open source advanced weather radar viewer." HOMEPAGE_URL "https://github.com/dpaulat/supercell-wx" LANGUAGES C CXX) @@ -11,40 +18,21 @@ set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) set(CMAKE_POLICY_DEFAULT_CMP0079 NEW) set(CMAKE_POLICY_DEFAULT_CMP0148 OLD) # aws-sdk-cpp uses FindPythonInterp +scwx_output_dirs_setup() + enable_testing() set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) -include(${PROJECT_SOURCE_DIR}/external/cmake-conan/conan.cmake) - set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS conanfile.py) -# Don't use RelWithDebInfo Conan packages -if (${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") - set(conan_build_type "Release") -else() - set(conan_build_type ${CMAKE_BUILD_TYPE}) -endif() - -conan_cmake_autodetect(settings - BUILD_TYPE ${conan_build_type}) - -conan_cmake_install(PATH_OR_REFERENCE ${PROJECT_SOURCE_DIR} - BUILD missing - REMOTE conancenter - SETTINGS ${settings}) - -include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) -include(${CMAKE_BINARY_DIR}/conan_paths.cmake) -conan_basic_setup(TARGETS) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DBOOST_ALL_NO_LIB") set(SCWX_DIR ${PROJECT_SOURCE_DIR}) -set(SCWX_VERSION "0.4.5") +set(SCWX_VERSION "0.5.1") option(SCWX_ADDRESS_SANITIZER "Build with Address Sanitizer" OFF) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 00000000..ec997016 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,383 @@ +{ + "version": 5, + "cmakeMinimumRequired": { + "major": 3, + "minor": 24, + "patch": 0 + }, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "CMAKE_PROJECT_TOP_LEVEL_INCLUDES": "${sourceDir}/external/cmake-conan/conan_provider.cmake", + "SCWX_VIRTUAL_ENV": "${sourceDir}/.venv" + } + }, + { + "name": "windows-base", + "inherits": "base", + "hidden": true, + "generator": "Visual Studio 17 2022", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { + "hostOS": [ + "Windows" + ] + } + } + }, + { + "name": "windows-x64-base", + "inherits": "windows-base", + "hidden": true + }, + { + "name": "linux-base", + "inherits": "base", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "windows-msvc2022-x64-base", + "inherits": "windows-x64-base", + "hidden": true, + "cacheVariables": { + "CMAKE_PREFIX_PATH": "C:/Qt/6.9.2/msvc2022_64" + } + }, + { + "name": "windows-msvc2022-x64-ninja-base", + "inherits": "windows-msvc2022-x64-base", + "hidden": true, + "generator": "Ninja", + "cacheVariables": { + "CMAKE_PREFIX_PATH": "C:/Qt/6.9.2/msvc2022_64" + } + }, + { + "name": "linux-gcc-base", + "inherits": "linux-base", + "hidden": true, + "cacheVariables": { + "CMAKE_PREFIX_PATH": "/opt/Qt/6.9.2/gcc_64" + }, + "environment": { + "CC": "gcc-11", + "CXX": "g++-11" + } + }, + { + "name": "windows-msvc2022-x64-debug", + "inherits": "windows-msvc2022-x64-base", + "displayName": "Windows MSVC 2022 x64 Debug", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CONAN_HOST_PROFILE": "scwx-windows_msvc2022_x64-debug", + "CONAN_BUILD_PROFILE": "scwx-windows_msvc2022_x64-debug" + } + }, + { + "name": "windows-msvc2022-x64-release", + "inherits": "windows-msvc2022-x64-base", + "displayName": "Windows MSVC 2022 x64 Release", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CONAN_HOST_PROFILE": "scwx-windows_msvc2022_x64", + "CONAN_BUILD_PROFILE": "scwx-windows_msvc2022_x64" + } + }, + { + "name": "windows-msvc2022-x64-ninja-debug", + "inherits": "windows-msvc2022-x64-ninja-base", + "displayName": "Windows MSVC 2022 x64 Ninja Debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CONAN_HOST_PROFILE": "scwx-windows_msvc2022_x64-debug", + "CONAN_BUILD_PROFILE": "scwx-windows_msvc2022_x64-debug" + } + }, + { + "name": "windows-msvc2022-x64-ninja-release", + "inherits": "windows-msvc2022-x64-ninja-base", + "displayName": "Windows MSVC 2022 x64 Ninja Release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CONAN_HOST_PROFILE": "scwx-windows_msvc2022_x64", + "CONAN_BUILD_PROFILE": "scwx-windows_msvc2022_x64" + } + }, + { + "name": "linux-gcc-debug", + "inherits": "linux-gcc-base", + "displayName": "Linux GCC Debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/${presetName}/Debug/supercell-wx", + "CONAN_HOST_PROFILE": "scwx-linux_gcc-11-debug", + "CONAN_BUILD_PROFILE": "scwx-linux_gcc-11-debug" + } + }, + { + "name": "linux-gcc-release", + "inherits": "linux-gcc-base", + "displayName": "Linux GCC Release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/${presetName}/Release/supercell-wx", + "CONAN_HOST_PROFILE": "scwx-linux_gcc-11", + "CONAN_BUILD_PROFILE": "scwx-linux_gcc-11" + } + }, + { + "name": "linux-gcc-debug-asan", + "inherits": "linux-gcc-base", + "displayName": "Linux GCC Debug Address Sanitizer", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/${presetName}/Debug/supercell-wx", + "CONAN_HOST_PROFILE": "scwx-linux_gcc-11-debug", + "CONAN_BUILD_PROFILE": "scwx-linux_gcc-11-debug", + "SCWX_ADDRESS_SANITIZER": { + "type": "BOOL", + "value": "ON" + } + } + }, + { + "name": "linux-gcc-release-asan", + "inherits": "linux-gcc-base", + "displayName": "Linux GCC Release Address Sanitizer", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/${presetName}/Release/supercell-wx", + "CONAN_HOST_PROFILE": "scwx-linux_gcc-11", + "CONAN_BUILD_PROFILE": "scwx-linux_gcc-11", + "SCWX_ADDRESS_SANITIZER": { + "type": "BOOL", + "value": "ON" + } + } + }, + { + "name": "ci-linux-gcc14", + "inherits": "linux-gcc-base", + "displayName": "CI Linux GCC 14", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CONAN_HOST_PROFILE": "scwx-linux_gcc-14", + "CONAN_BUILD_PROFILE": "scwx-linux_gcc-14" + }, + "environment": { + "CC": "gcc-14", + "CXX": "g++-14" + } + }, + { + "name": "ci-linux-clang17", + "inherits": "linux-gcc-base", + "displayName": "CI Linux Clang 17", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CONAN_HOST_PROFILE": "scwx-linux_clang-17", + "CONAN_BUILD_PROFILE": "scwx-linux_clang-17" + }, + "environment": { + "CC": "clang-17", + "CXX": "clang++-17" + } + }, + { + "name": "ci-linux-gcc-arm64", + "inherits": "linux-gcc-base", + "displayName": "CI Linux GCC ARM64", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_PREFIX_PATH": "/opt/Qt/6.9.2/gcc_arm64", + "CONAN_HOST_PROFILE": "scwx-linux_gcc-11_armv8", + "CONAN_BUILD_PROFILE": "scwx-linux_gcc-11_armv8" + }, + "environment": { + "CC": "gcc-11", + "CXX": "g++-11" + } + }, + { + "name": "macos-base", + "inherits": "base", + "hidden": true, + "cacheVariables": { + "CMAKE_PREFIX_PATH": "$env{HOME}/Qt/6.9.2/macos" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + } + }, + { + "name": "macos-clang18-base", + "inherits": "macos-base", + "hidden": true + }, + { + "name": "macos-clang18-x64-base", + "inherits": "macos-clang18-base", + "hidden": true, + "environment": { + "CC": "/usr/local/opt/llvm@18/bin/clang", + "CXX": "/usr/local/opt/llvm@18/bin/clang++", + "PATH": "/usr/local/opt/llvm@18/bin:$penv{PATH}", + "CPPFLAGS": "-I/usr/local/opt/llvm@18/include", + "LDFLAGS": "-L/usr/local/opt/llvm@18/lib -L/usr/local/opt/llvm@18/lib/c++" + } + }, + { + "name": "macos-clang18-arm64-base", + "inherits": "macos-clang18-base", + "hidden": true, + "environment": { + "CC": "/opt/homebrew/opt/llvm@18/bin/clang", + "CXX": "/opt/homebrew/opt/llvm@18/bin/clang++", + "PATH": "/opt/homebrew/opt/llvm@18/bin:$penv{PATH}", + "CPPFLAGS": "-I/opt/homebrew/opt/llvm@18/include", + "LDFLAGS": "-L/opt/homebrew/opt/llvm@18/lib -L/opt/homebrew/opt/llvm@18/lib/c++" + } + }, + { + "name": "macos-clang18-x64-debug", + "inherits": "macos-clang18-x64-base", + "displayName": "macOS Clang 18 x64 Debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CONAN_HOST_PROFILE": "scwx-macos_clang-18-debug", + "CONAN_BUILD_PROFILE": "scwx-macos_clang-18-debug" + } + }, + { + "name": "macos-clang18-x64-release", + "inherits": "macos-clang18-x64-base", + "displayName": "macOS Clang 18 x64 Release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CONAN_HOST_PROFILE": "scwx-macos_clang-18", + "CONAN_BUILD_PROFILE": "scwx-macos_clang-18" + } + }, + { + "name": "macos-clang18-arm64-debug", + "inherits": "macos-clang18-arm64-base", + "displayName": "macOS Clang 18 Arm64 Debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CONAN_HOST_PROFILE": "scwx-macos_clang-18_armv8-debug", + "CONAN_BUILD_PROFILE": "scwx-macos_clang-18_armv8-debug" + } + }, + { + "name": "macos-clang18-arm64-release", + "inherits": "macos-clang18-arm64-base", + "displayName": "macOS Clang 18 Arm64 Release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CONAN_HOST_PROFILE": "scwx-macos_clang-18_armv8", + "CONAN_BUILD_PROFILE": "scwx-macos_clang-18_armv8" + } + } + ], + "buildPresets": [ + { + "name": "windows-msvc2022-x64-debug", + "configurePreset": "windows-msvc2022-x64-debug", + "displayName": "Windows MSVC 2022 x64 Debug", + "configuration": "Debug" + }, + { + "name": "windows-msvc2022-x64-release", + "configurePreset": "windows-msvc2022-x64-release", + "displayName": "Windows MSVC 2022 x64 Release", + "configuration": "Release" + }, + { + "name": "linux-gcc-debug", + "configurePreset": "linux-gcc-debug", + "displayName": "Linux GCC Debug", + "configuration": "Debug" + }, + { + "name": "linux-gcc-release", + "configurePreset": "linux-gcc-release", + "displayName": "Linux GCC Release", + "configuration": "Release" + }, + { + "name": "macos-clang18-x64-debug", + "configurePreset": "macos-clang18-x64-debug", + "displayName": "macOS Clang 18 x64 Debug", + "configuration": "Debug" + }, + { + "name": "macos-clang18-x64-release", + "configurePreset": "macos-clang18-x64-release", + "displayName": "macOS Clang 18 x64 Release", + "configuration": "Release" + }, + { + "name": "macos-clang18-arm64-debug", + "configurePreset": "macos-clang18-arm64-debug", + "displayName": "macOS Clang 18 Arm64 Debug", + "configuration": "Debug" + }, + { + "name": "macos-clang18-arm64-release", + "configurePreset": "macos-clang18-arm64-release", + "displayName": "macOS Clang 18 Arm64 Release", + "configuration": "Release" + } + ], + "testPresets": [ + { + "name": "windows-msvc2022-x64-debug", + "configurePreset": "windows-msvc2022-x64-debug", + "displayName": "Windows MSVC 2022 x64 Debug", + "configuration": "Debug" + }, + { + "name": "windows-msvc2022-x64-release", + "configurePreset": "windows-msvc2022-x64-release", + "displayName": "Windows MSVC 2022 x64 Release", + "configuration": "Release" + }, + { + "name": "linux-gcc-debug", + "configurePreset": "linux-gcc-debug", + "displayName": "Linux GCC Debug", + "configuration": "Debug" + }, + { + "name": "linux-gcc-release", + "configurePreset": "linux-gcc-release", + "displayName": "Linux GCC Release", + "configuration": "Release" + } + ] +} diff --git a/LICENSE.txt b/LICENSE.txt index 8c9c5fbd..799086a0 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2021-2024 Dan Paulat +Copyright (c) 2021-2025 Dan Paulat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 321c33f2..81b0e698 100644 --- a/README.md +++ b/README.md @@ -26,32 +26,36 @@ Supercell Wx supports the following 64-bit operating systems: - Fedora Linux 34+ - openSUSE Tumbleweed - Ubuntu 22.04+ + - NixOS 25.05+ - Most distributions supporting the GCC Standard C++ Library 11+ - +- macOS + - 13.6+ for Intel-based Macs + - 14.0+ for Apple silicon-based Macs + ## Linux Dependencies Supercell Wx requires the following Linux dependencies: -- Linux/X11 (Wayland works too) with support for GCC 11 and OpenGL 3.3 +- Linux/X11 (Wayland works too) with support for GCC 11, OpenGL 3.3 and OpenGL ES 3.0 - X11/XCB libraries including xcb-cursor - + ## FAQ Frequently asked questions: - Q: Why is the map black when loading for the first time? - + - A. You must obtain a free API key from either (or both) [MapTiler](https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/maps/) which currently does not require a credit/debit card, or [Mapbox](https://account.mapbox.com/) which ***does*** require a credit/debit card, but as of writing, you will receive 200K free requests per month, which should be sufficient for an individual user. - Q: Why is it that when I change my color table, API key, grid width/height settings, nothing happens after hitting apply? - A. As of right now, you must restart Supercell Wx in order to apply these changes. In future iterations, this will no longer be an issue. - + - Q. Is it possible to get dark mode? - + - A. In Windows, make sure to set the flag `-style fusion` at the end of the target path of the .exe - Example: `C:\Users\Administrator\Desktop\Supercell-Wx\bin\supercell-wx.exe -style fusion` - A. In Linux, if you're using KDE, Supercell Wx should automatically follow your theme settings. - + - Q: How can I contribute? - A. Head to [Developer Setup](https://supercell-wx.readthedocs.io/en/stable/development/developer-setup.html) and [Contributing](CONTRIBUTING.md) to configure the Supercell Wx development environment for your IDE. Currently Visual Studio and Visual Studio Code are recommended, with other IDEs remaining untested at this time. diff --git a/conanfile.py b/conanfile.py index b18f2d31..6803a56c 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,37 +1,66 @@ -from conans import ConanFile +from conan import ConanFile +from conan.tools.cmake import CMake +from conan.tools.files import copy +import os class SupercellWxConan(ConanFile): settings = ("os", "compiler", "build_type", "arch") - requires = ("boost/1.85.0", - "cpr/1.10.5", + requires = ("boost/1.88.0", + "cpr/1.12.0", "fontconfig/2.15.0", "freetype/2.13.2", - "geographiclib/2.3", - "geos/3.12.2", - "glew/2.2.0", - "glm/cci.20230113", - "gtest/1.15.0", - "libcurl/8.9.1", - "libxml2/2.12.7", - "openssl/3.3.1", - "re2/20240702", - "spdlog/1.14.1", - "sqlite3/3.46.0", - "vulkan-loader/1.3.243.0", + "geographiclib/2.4", + "geos/3.13.0", + "glm/1.0.1", + "gtest/1.17.0", + "libcurl/8.12.1", + "libpng/1.6.50", + "libxml2/2.14.5", + "openssl/3.5.0", + "range-v3/0.12.0", + "re2/20250722", + "spdlog/1.15.1", + "sqlite3/3.49.1", + "vulkan-loader/1.3.290.0", "zlib/1.3.1") - generators = ("cmake", - "cmake_find_package", - "cmake_paths") - default_options = {"geos:shared" : True, - "libiconv:shared" : True, - "openssl:no_module": True, - "openssl:shared" : True} + generators = ("CMakeDeps") + default_options = {"geos/*:shared" : True, + "libiconv/*:shared" : True} + + def configure(self): + if self.settings.os == "Windows": + self.options["libcurl"].with_ssl = "schannel" + elif self.settings.os == "Linux": + self.options["openssl"].shared = True + self.options["libcurl"].ca_bundle = "none" + self.options["libcurl"].ca_path = "none" + elif self.settings.os == "Macos": + self.options["openssl"].shared = True + self.options["libcurl"].ca_bundle = "none" + self.options["libcurl"].ca_path = "none" def requirements(self): if self.settings.os == "Linux": - self.requires("onetbb/2021.12.0") + self.requires("mesa-glu/9.0.3") + self.requires("onetbb/2022.2.0") - def imports(self): - self.copy("*.dll", dst="bin", src="bin") - self.copy("*.dylib", dst="bin", src="lib") - self.copy("license*", dst="licenses", src=".", folder=True, ignore_case=True) + def generate(self): + build_folder = os.path.join(self.build_folder, + "..", + str(self.settings.build_type), + self.cpp_info.bindirs[0]) + + for dep in self.dependencies.values(): + if dep.cpp_info.bindirs: + copy(self, "*.dll", dep.cpp_info.bindirs[0], build_folder) + if dep.cpp_info.libdirs: + copy(self, "*.dylib", dep.cpp_info.libdirs[0], build_folder) + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() diff --git a/data b/data index 8eb89b19..fd72b32c 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 8eb89b19fdd1c78e896cc6cb47e07425bb473699 +Subproject commit fd72b32cc12419b4a9c9a72487e58ffa04fb2a70 diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index bbc76c64..1039e96e 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.24) set(PROJECT_NAME scwx-external) set_property(DIRECTORY @@ -6,18 +6,22 @@ set_property(DIRECTORY PROPERTY CMAKE_CONFIGURE_DEPENDS aws-sdk-cpp.cmake date.cmake + glad.cmake hsluv-c.cmake imgui.cmake maplibre-native-qt.cmake stb.cmake textflowcpp.cmake - units.cmake) + units.cmake + qt6ct.cmake) include(aws-sdk-cpp.cmake) include(date.cmake) +include(glad.cmake) include(hsluv-c.cmake) include(imgui.cmake) include(maplibre-native-qt.cmake) include(stb.cmake) include(textflowcpp.cmake) include(units.cmake) +include(qt6ct.cmake) diff --git a/external/aws-sdk-cpp b/external/aws-sdk-cpp index d5eb42fe..8d31e042 160000 --- a/external/aws-sdk-cpp +++ b/external/aws-sdk-cpp @@ -1 +1 @@ -Subproject commit d5eb42fe7c632868d4535b454ee2e5ba0e349b7f +Subproject commit 8d31e042f950fe70924391a205cceaf342ecec00 diff --git a/external/aws-sdk-cpp.cmake b/external/aws-sdk-cpp.cmake index 1ba641db..f88df9ca 100644 --- a/external/aws-sdk-cpp.cmake +++ b/external/aws-sdk-cpp.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.24) set(PROJECT_NAME scwx-aws-sdk-cpp) set(AWS_SDK_WARNINGS_ARE_ERRORS OFF) @@ -21,6 +21,10 @@ set(MINIMIZE_SIZE OFF CACHE BOOL "If enabled, the SDK will be built via # Save off ${CMAKE_CXX_FLAGS} before modifying compiler settings set(CMAKE_CXX_FLAGS_PREV "${CMAKE_CXX_FLAGS}") +# Configure OpenSSL crypto library +find_package(OpenSSL) +add_library(crypto ALIAS OpenSSL::Crypto) + # Fix CMake errors for internal variables not set include(aws-sdk-cpp/cmake/compiler_settings.cmake) set_msvc_warnings() diff --git a/external/cmake-conan b/external/cmake-conan index b240c809..b0e4d1ec 160000 --- a/external/cmake-conan +++ b/external/cmake-conan @@ -1 +1 @@ -Subproject commit b240c809b5ea097077fc8222cad71d2329288e48 +Subproject commit b0e4d1ec08edb35ef31033938567d621f6643c17 diff --git a/external/date b/external/date index cc4685a2..a5db3aec 160000 --- a/external/date +++ b/external/date @@ -1 +1 @@ -Subproject commit cc4685a21e4a4fdae707ad1233c61bbaff241f93 +Subproject commit a5db3aecec580bc78b6c01c118f2628676769b69 diff --git a/external/date.cmake b/external/date.cmake index a804f68c..7fce7ffc 100644 --- a/external/date.cmake +++ b/external/date.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.24) set(PROJECT_NAME scwx-date) set(USE_SYSTEM_TZ_DB ON) diff --git a/external/glad b/external/glad new file mode 160000 index 00000000..73db193f --- /dev/null +++ b/external/glad @@ -0,0 +1 @@ +Subproject commit 73db193f853e2ee079bf3ca8a64aa2eaf6459043 diff --git a/external/glad.cmake b/external/glad.cmake new file mode 100644 index 00000000..59ceb179 --- /dev/null +++ b/external/glad.cmake @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.24) +set(PROJECT_NAME scwx-glad) + +# Path to glad directory +set(GLAD_SOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/glad/") + +# Path to glad CMake files +add_subdirectory("${GLAD_SOURCES_DIR}/cmake" glad_cmake) + +# Specify glad settings +glad_add_library(glad_gl_core_33 LOADER REPRODUCIBLE API gl:core=3.3) diff --git a/external/hsluv-c b/external/hsluv-c index 59539e04..982217c6 160000 --- a/external/hsluv-c +++ b/external/hsluv-c @@ -1 +1 @@ -Subproject commit 59539e04a6fa648935cbe57c2104041f23136c4a +Subproject commit 982217c65a9ff574302335177d2dc078d9bfa6f5 diff --git a/external/hsluv-c.cmake b/external/hsluv-c.cmake index 0129d39d..eec8f0f2 100644 --- a/external/hsluv-c.cmake +++ b/external/hsluv-c.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.24) set(PROJECT_NAME scwx-hsluv-c) set(HSLUV_C_TESTS OFF) diff --git a/external/imgui b/external/imgui index 6ccc561a..45acd5e0 160000 --- a/external/imgui +++ b/external/imgui @@ -1 +1 @@ -Subproject commit 6ccc561a2ab497ad4ae6ee1dbd3b992ffada35cb +Subproject commit 45acd5e0e82f4c954432533ae9985ff0e1aad6d5 diff --git a/external/imgui-backend-qt b/external/imgui-backend-qt index 0fe974eb..023345ca 160000 --- a/external/imgui-backend-qt +++ b/external/imgui-backend-qt @@ -1 +1 @@ -Subproject commit 0fe974ebd037844c9f23d6325dbcc128e9973749 +Subproject commit 023345ca8abf731fc50568c0197ceebe76bb4324 diff --git a/external/imgui.cmake b/external/imgui.cmake index 443817ef..3eba6ee0 100644 --- a/external/imgui.cmake +++ b/external/imgui.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.24) set(PROJECT_NAME scwx-imgui) find_package(QT NAMES Qt6 @@ -12,7 +12,7 @@ find_package(Qt${QT_VERSION_MAJOR} find_package(Freetype) -set(IMGUI_SOURCES imgui/imconfig.h +set(IMGUI_SOURCES include/scwx/external/imgui/imconfig.h imgui/imgui.cpp imgui/imgui.h imgui/imgui_demo.cpp @@ -33,8 +33,9 @@ set(IMGUI_SOURCES imgui/imconfig.h add_library(imgui STATIC ${IMGUI_SOURCES}) target_include_directories(imgui PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/imgui) +target_include_directories(imgui PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) -target_compile_definitions(imgui PRIVATE IMGUI_ENABLE_FREETYPE) +target_compile_definitions(imgui PUBLIC IMGUI_USER_CONFIG=) target_link_libraries(imgui PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Freetype::Freetype) diff --git a/external/include/scwx/external/imgui/imconfig.h b/external/include/scwx/external/imgui/imconfig.h new file mode 100644 index 00000000..ac954a77 --- /dev/null +++ b/external/include/scwx/external/imgui/imconfig.h @@ -0,0 +1,147 @@ +// clang-format off +//----------------------------------------------------------------------------- +// DEAR IMGUI COMPILE-TIME OPTIONS +// Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. +// You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. +//----------------------------------------------------------------------------- +// A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it) +// B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template. +//----------------------------------------------------------------------------- +// You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp +// files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. +// Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. +// Call IMGUI_CHECKVERSION() from your .cpp file to verify that the data structures your files are using are matching the ones imgui.cpp is using. +//----------------------------------------------------------------------------- + +#pragma once + +//---- Define assertion handler. Defaults to calling assert(). +// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. +//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) +//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts + +//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows +// Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. +// - Windows DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() +// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details. +//#define IMGUI_API __declspec(dllexport) // MSVC Windows: DLL export +//#define IMGUI_API __declspec(dllimport) // MSVC Windows: DLL import +//#define IMGUI_API __attribute__((visibility("default"))) // GCC/Clang: override visibility when set is hidden + +//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names. +#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +//---- Disable all of Dear ImGui or don't implement standard windows/tools. +// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp. +//#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. +//#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. +//#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowIDStackToolWindow() will be empty. + +//---- Don't implement some functions to reduce linkage requirements. +//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a) +//#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW) +//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a) +//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME). +//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default). +//#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")). +//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) +//#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. +//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) +//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. +//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). +//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded font (ProggyClean.ttf), remove ~9.5 KB from output binary. AddFontDefault() will assert. +//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available + +//---- Enable Test Engine / Automation features. +//#define IMGUI_ENABLE_TEST_ENGINE // Enable imgui_test_engine hooks. Generally set automatically by include "imgui_te_config.h", see Test Engine for details. + +//---- Include imgui_user.h at the end of imgui.h as a convenience +// May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included. +//#define IMGUI_INCLUDE_IMGUI_USER_H +//#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h" + +//---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support. +//#define IMGUI_USE_BGRA_PACKED_COLOR + +//---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate. +//#define IMGUI_USE_LEGACY_CRC32_ADLER + +//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...) +//#define IMGUI_USE_WCHAR32 + +//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version +// By default the embedded implementations are declared static and not available outside of Dear ImGui sources files. +//#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" +//#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" +//#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if IMGUI_USE_STB_SPRINTF is defined. +//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION +//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION +//#define IMGUI_DISABLE_STB_SPRINTF_IMPLEMENTATION // only disabled if IMGUI_USE_STB_SPRINTF is defined. + +//---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined) +// Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h. +//#define IMGUI_USE_STB_SPRINTF + +//---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui) +// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided). +// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'. +#define IMGUI_ENABLE_FREETYPE + +//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT) +// Only works in combination with IMGUI_ENABLE_FREETYPE. +// - plutosvg is currently easier to install, as e.g. it is part of vcpkg. It will support more fonts and may load them faster. See misc/freetype/README for instructions. +// - Both require headers to be available in the include path + program to be linked with the library code (not provided). +// - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement) +//#define IMGUI_ENABLE_FREETYPE_PLUTOSVG +//#define IMGUI_ENABLE_FREETYPE_LUNASVG + +//---- Use stb_truetype to build and rasterize the font atlas (default) +// The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend. +//#define IMGUI_ENABLE_STB_TRUETYPE + +//---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. +// This will be inlined as part of ImVec2 and ImVec4 class declarations. +/* +#define IM_VEC2_CLASS_EXTRA \ + constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \ + operator MyVec2() const { return MyVec2(x,y); } + +#define IM_VEC4_CLASS_EXTRA \ + constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ + operator MyVec4() const { return MyVec4(x,y,z,w); } +*/ +//---- ...Or use Dear ImGui's own very basic math operators. +//#define IMGUI_DEFINE_MATH_OPERATORS + +//---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. +// Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices). +// Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer. +// Read about ImGuiBackendFlags_RendererHasVtxOffset for details. +//#define ImDrawIdx unsigned int + +//---- Override ImDrawCallback signature (will need to modify renderer backends accordingly) +//struct ImDrawList; +//struct ImDrawCmd; +//typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data); +//#define ImDrawCallback MyImDrawCallback + +//---- Debug Tools: Macro to break in Debugger (we provide a default implementation of this in the codebase) +// (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.) +//#define IM_DEBUG_BREAK IM_ASSERT(0) +//#define IM_DEBUG_BREAK __debugbreak() + +//---- Debug Tools: Enable highlight ID conflicts _before_ hovering items. When io.ConfigDebugHighlightIdConflicts is set. +// (THIS WILL SLOW DOWN DEAR IMGUI. Only use occasionally and disable after use) +//#define IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + +//---- Debug Tools: Enable slower asserts +//#define IMGUI_DEBUG_PARANOID + +//---- Tip: You can add extra functions within the ImGui:: namespace from anywhere (e.g. your own sources/header files) +/* +namespace ImGui +{ + void MyFunction(const char* name, MyMatrix44* mtx); +} +*/ +// clang-format on diff --git a/external/maplibre-native b/external/maplibre-native index 3d4ca3fd..3654f5fa 160000 --- a/external/maplibre-native +++ b/external/maplibre-native @@ -1 +1 @@ -Subproject commit 3d4ca3fdf07c50db3002b11bff93c81ec380e493 +Subproject commit 3654f5fa9f06534d7fd2d95b810049a82e5953ef diff --git a/external/maplibre-native-qt b/external/maplibre-native-qt index 805ccf62..8b406978 160000 --- a/external/maplibre-native-qt +++ b/external/maplibre-native-qt @@ -1 +1 @@ -Subproject commit 805ccf6204a546e43fed599631ad5d698f68ae86 +Subproject commit 8b40697895c19da4479cd037a76608f4c36935e8 diff --git a/external/maplibre-native-qt.cmake b/external/maplibre-native-qt.cmake index 6a508040..7db42c10 100644 --- a/external/maplibre-native-qt.cmake +++ b/external/maplibre-native-qt.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.24) set(PROJECT_NAME scwx-mln) set(gtest_disable_pthreads ON) @@ -19,11 +19,28 @@ if (MSVC) target_link_options(MLNQtCore PRIVATE "$<$:/DEBUG>") target_link_options(MLNQtCore PRIVATE "$<$:/OPT:REF>") target_link_options(MLNQtCore PRIVATE "$<$:/OPT:ICF>") + + # Enable multi-processor compilation + target_compile_options(MLNQtCore PRIVATE "/MP") + target_compile_options(mbgl-core PRIVATE "/MP") + target_compile_options(mbgl-vendor-csscolorparser PRIVATE "/MP") + target_compile_options(mbgl-vendor-nunicode PRIVATE "/MP") + target_compile_options(mbgl-vendor-parsedate PRIVATE "/MP") + + if (TARGET mbgl-vendor-sqlite) + target_compile_options(mbgl-vendor-sqlite PRIVATE "/MP") + endif() else() target_compile_options(mbgl-core PRIVATE "$<$:-g>") target_compile_options(MLNQtCore PRIVATE "$<$:-g>") endif() +if (APPLE) + # Enable GL check error debug + target_compile_definitions(mbgl-core PRIVATE MLN_GL_CHECK_ERRORS=1) + target_compile_definitions(MLNQtCore PRIVATE MLN_GL_CHECK_ERRORS=1) +endif() + set(MLN_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/maplibre-native/include ${CMAKE_CURRENT_SOURCE_DIR}/maplibre-native-qt/src/core/include ${CMAKE_CURRENT_BINARY_DIR}/maplibre-native-qt/src/core/include PARENT_SCOPE) diff --git a/external/qt6ct b/external/qt6ct new file mode 160000 index 00000000..2c569c6c --- /dev/null +++ b/external/qt6ct @@ -0,0 +1 @@ +Subproject commit 2c569c6c4776ea5a1299030c079b16f70473c9e6 diff --git a/external/qt6ct.cmake b/external/qt6ct.cmake new file mode 100644 index 00000000..cdae0b25 --- /dev/null +++ b/external/qt6ct.cmake @@ -0,0 +1,62 @@ +cmake_minimum_required(VERSION 3.16.0) +set(PROJECT_NAME scwx-qt6ct) + +find_package(QT NAMES Qt6 + COMPONENTS Gui Widgets + REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} + COMPONENTS Gui Widgets + REQUIRED) + +#extract version from qt6ct.h +file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/qt6ct/src/qt6ct-common/qt6ct.h" + QT6CT_VERSION_DATA REGEX "^#define[ \t]+QT6CT_VERSION_[A-Z]+[ \t]+[0-9]+.*$") + +if(QT6CT_VERSION_DATA) + foreach(item IN ITEMS MAJOR MINOR) + string(REGEX REPLACE ".*#define[ \t]+QT6CT_VERSION_${item}[ \t]+([0-9]+).*" + "\\1" QT6CT_VERSION_${item} ${QT6CT_VERSION_DATA}) + endforeach() + set(QT6CT_VERSION "${QT6CT_VERSION_MAJOR}.${QT6CT_VERSION_MINOR}") + set(QT6CT_SOVERSION "${QT6CT_VERSION_MAJOR}") + message(STATUS "qt6ct version: ${QT6CT_VERSION}") +else() + message(FATAL_ERROR "invalid header") +endif() + +set(qt6ct-common-source + qt6ct/src/qt6ct-common/qt6ct.cpp +) + +set(qt6ct-widgets-source + qt6ct/src/qt6ct/paletteeditdialog.cpp + qt6ct/src/qt6ct/paletteeditdialog.ui +) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +include_directories(qt6ct/src/qt6ct-common) + +add_library(qt6ct-common STATIC ${qt6ct-common-source}) +set_target_properties(qt6ct-common PROPERTIES VERSION ${QT6CT_VERSION}) +target_link_libraries(qt6ct-common PRIVATE Qt6::Gui) +target_compile_definitions(qt6ct-common PRIVATE QT6CT_LIBRARY) + +add_library(qt6ct-widgets STATIC ${qt6ct-widgets-source}) +set_target_properties(qt6ct-widgets PROPERTIES VERSION ${QT6CT_VERSION}) +target_link_libraries(qt6ct-widgets PRIVATE Qt6::Widgets qt6ct-common) +target_compile_definitions(qt6ct-widgets PRIVATE QT6CT_LIBRARY) + +if (MSVC) + # Produce PDB file for debug + target_compile_options(qt6ct-common PRIVATE "$<$:/Zi>") + target_compile_options(qt6ct-widgets PRIVATE "$<$:/Zi>") +else() + target_compile_options(qt6ct-common PRIVATE "$<$:-g>") + target_compile_options(qt6ct-widgets PRIVATE "$<$:-g>") +endif() + +target_include_directories( qt6ct-common INTERFACE qt6ct/src ) +target_include_directories( qt6ct-widgets INTERFACE qt6ct/src ) diff --git a/external/stb b/external/stb index beebb24b..f58f558c 160000 --- a/external/stb +++ b/external/stb @@ -1 +1 @@ -Subproject commit beebb24b945efdea3b9bba23affb8eb3ba8982e7 +Subproject commit f58f558c120e9b32c217290b80bad1a0729fbb2c diff --git a/external/stb.cmake b/external/stb.cmake index c26bedaf..570af425 100644 --- a/external/stb.cmake +++ b/external/stb.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.24) set(PROJECT_NAME scwx-stb) set(STB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/stb PARENT_SCOPE) diff --git a/external/textflowcpp.cmake b/external/textflowcpp.cmake index 1e36da18..31020665 100644 --- a/external/textflowcpp.cmake +++ b/external/textflowcpp.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.24) set(PROJECT_NAME scwx-textflowcpp) set(TEXTFLOWCPP_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/textflowcpp PARENT_SCOPE) diff --git a/external/units.cmake b/external/units.cmake index d037ae54..cc70ac1c 100644 --- a/external/units.cmake +++ b/external/units.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.24) set(PROJECT_NAME scwx-units) add_subdirectory(units) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..afdd2a37 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +conan +geopandas +GitPython diff --git a/scwx-qt/CMakeLists.txt b/scwx-qt/CMakeLists.txt index f4e636e7..e47bc3fb 100644 --- a/scwx-qt/CMakeLists.txt +++ b/scwx-qt/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.21) +cmake_minimum_required(VERSION 3.24) set_property(DIRECTORY APPEND diff --git a/scwx-qt/gl/map_color.vert b/scwx-qt/gl/map_color.vert index 6ae98e92..609d1c34 100644 --- a/scwx-qt/gl/map_color.vert +++ b/scwx-qt/gl/map_color.vert @@ -26,6 +26,9 @@ void main() // Always set displayed to true vsOut.displayed = 1; + // Initialize texCoord to default value + vsOut.texCoord = vec3(0.0f, 0.0f, 0.0f); + // Pass the threshold and time range to the geometry shader vsOut.threshold = aThreshold; vsOut.timeRange = aTimeRange; diff --git a/scwx-qt/gl/radar.frag b/scwx-qt/gl/radar.frag index 0c605b8a..b6491e8b 100644 --- a/scwx-qt/gl/radar.frag +++ b/scwx-qt/gl/radar.frag @@ -9,14 +9,14 @@ uniform float uDataMomentScale; uniform bool uCFPEnabled; -flat in uint dataMoment; -flat in uint cfpMoment; +in float dataMoment; +in float cfpMoment; layout (location = 0) out vec4 fragColor; void main() { - float texCoord = float(dataMoment - uDataMomentOffset) / uDataMomentScale; + float texCoord = (dataMoment - float(uDataMomentOffset)) / uDataMomentScale; if (uCFPEnabled && cfpMoment > 8u) { diff --git a/scwx-qt/gl/radar.vert b/scwx-qt/gl/radar.vert index b4da9f17..97754b73 100644 --- a/scwx-qt/gl/radar.vert +++ b/scwx-qt/gl/radar.vert @@ -13,8 +13,8 @@ layout (location = 2) in uint aCfpMoment; uniform mat4 uMVPMatrix; uniform vec2 uMapScreenCoord; -flat out uint dataMoment; -flat out uint cfpMoment; +out float dataMoment; +out float cfpMoment; vec2 latLngToScreenCoordinate(in vec2 latLng) { diff --git a/scwx-qt/gl/threshold.geom b/scwx-qt/gl/threshold.geom index 677a80cd..deead87d 100644 --- a/scwx-qt/gl/threshold.geom +++ b/scwx-qt/gl/threshold.geom @@ -21,7 +21,9 @@ smooth out vec4 color; void main() { if (gsIn[0].displayed != 0 && - (gsIn[0].threshold <= 0 || // If Threshold: 0 was specified, no threshold + (gsIn[0].threshold == 0 || // If Threshold: 0 was specified, no threshold + uMapDistance == 0 || // If uMapDistance is zero, threshold is disabled + (gsIn[0].threshold < 0 && -(gsIn[0].threshold) <= uMapDistance) || // If Threshold is negative and below current map distance gsIn[0].threshold >= uMapDistance || // If Threshold is above current map distance gsIn[0].threshold >= 999) && // If Threshold: 999 was specified (or greater), no threshold (gsIn[0].timeRange[0] == 0 || // If there is no start time specified diff --git a/scwx-qt/res/config/radar_sites.json b/scwx-qt/res/config/radar_sites.json index 8f2a1b90..20d7397c 100644 --- a/scwx-qt/res/config/radar_sites.json +++ b/scwx-qt/res/config/radar_sites.json @@ -67,7 +67,7 @@ { "type": "wsr88d", "id": "KLVX", "lat": 37.9753058, "lon": -85.9438455, "country": "USA", "state": "KY", "place": "Louisville", "tz": "America/New_York", "elevation": 833.0 }, { "type": "wsr88d", "id": "KPAH", "lat": 37.068333, "lon": -88.771944, "country": "USA", "state": "KY", "place": "Paducah", "tz": "America/Chicago", "elevation": 506.0 }, { "type": "wsr88d", "id": "KPOE", "lat": 31.1556923, "lon": -92.9762596, "country": "USA", "state": "LA", "place": "Fort Polk", "tz": "America/Chicago", "elevation": 473.0 }, - { "type": "wsr88d", "id": "KHDC", "lat": 30.519306, "lon": -90.424028, "country": "USA", "state": "LA", "place": "New Orleans (Hammond)", "tz": "America/Chicago", "elevation": 43.0 }, + { "type": "wsr88d", "id": "KHDC", "lat": 30.5196, "lon": -90.4074, "country": "USA", "state": "LA", "place": "New Orleans (Hammond)", "tz": "America/Chicago", "elevation": 43.0 }, { "type": "wsr88d", "id": "KLCH", "lat": 30.125306, "lon": -93.215889, "country": "USA", "state": "LA", "place": "Lake Charles", "tz": "America/Chicago", "elevation": 137.0 }, { "type": "wsr88d", "id": "KSHV", "lat": 32.450833, "lon": -93.84125, "country": "USA", "state": "LA", "place": "Shreveport", "tz": "America/Chicago", "elevation": 387.0 }, { "type": "wsr88d", "id": "KLIX", "lat": 30.3367133, "lon": -89.8256618, "country": "USA", "state": "LA", "place": "New Orleans (Slidell)", "tz": "America/Chicago", "elevation": 179.0 }, diff --git a/scwx-qt/res/icons/font-awesome-6/briefcase-solid.svg b/scwx-qt/res/icons/font-awesome-6/briefcase-solid.svg new file mode 100644 index 00000000..b16bc330 --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/briefcase-solid.svg @@ -0,0 +1 @@ + diff --git a/scwx-qt/res/icons/font-awesome-6/building-columns-solid.svg b/scwx-qt/res/icons/font-awesome-6/building-columns-solid.svg new file mode 100644 index 00000000..cf0df19a --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/building-columns-solid.svg @@ -0,0 +1 @@ + diff --git a/scwx-qt/res/icons/font-awesome-6/building-solid.svg b/scwx-qt/res/icons/font-awesome-6/building-solid.svg new file mode 100644 index 00000000..6f6d3f24 --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/building-solid.svg @@ -0,0 +1 @@ + diff --git a/scwx-qt/res/icons/font-awesome-6/caravan-solid.svg b/scwx-qt/res/icons/font-awesome-6/caravan-solid.svg new file mode 100644 index 00000000..c341214f --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/caravan-solid.svg @@ -0,0 +1 @@ + diff --git a/scwx-qt/res/icons/font-awesome-6/house-solid-white.svg b/scwx-qt/res/icons/font-awesome-6/house-solid-white.svg new file mode 100644 index 00000000..59f65e1e --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/house-solid-white.svg @@ -0,0 +1 @@ + diff --git a/scwx-qt/res/icons/font-awesome-6/location-crosshairs-solid.svg b/scwx-qt/res/icons/font-awesome-6/location-crosshairs-solid.svg new file mode 100644 index 00000000..5bb1ea5c --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/location-crosshairs-solid.svg @@ -0,0 +1 @@ + diff --git a/scwx-qt/res/icons/font-awesome-6/location-pin.svg b/scwx-qt/res/icons/font-awesome-6/location-pin.svg new file mode 100644 index 00000000..4b6182cd --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/location-pin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/scwx-qt/res/icons/font-awesome-6/star-solid-white.svg b/scwx-qt/res/icons/font-awesome-6/star-solid-white.svg new file mode 100644 index 00000000..41bcd103 --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/star-solid-white.svg @@ -0,0 +1 @@ + diff --git a/scwx-qt/res/icons/font-awesome-6/tent-solid.svg b/scwx-qt/res/icons/font-awesome-6/tent-solid.svg new file mode 100644 index 00000000..9f159d60 --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/tent-solid.svg @@ -0,0 +1 @@ + diff --git a/scwx-qt/res/icons/scwx.icns b/scwx-qt/res/icons/scwx.icns new file mode 100644 index 00000000..bda4ea32 Binary files /dev/null and b/scwx-qt/res/icons/scwx.icns differ diff --git a/scwx-qt/res/linux/net.supercellwx.app.desktop b/scwx-qt/res/linux/net.supercellwx.app.desktop new file mode 100644 index 00000000..8e671522 --- /dev/null +++ b/scwx-qt/res/linux/net.supercellwx.app.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Application +Name=Supercell Wx +Comment=Weather Radar and Data Viewer +Exec=supercell-wx +Icon=net.supercellwx.app.png +Categories=Network;Science; diff --git a/scwx-qt/res/scwx-qt.plist.in b/scwx-qt/res/scwx-qt.plist.in new file mode 100644 index 00000000..f9db57c0 --- /dev/null +++ b/scwx-qt/res/scwx-qt.plist.in @@ -0,0 +1,42 @@ + + + + + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + + LSMinimumSystemVersion + ${CMAKE_OSX_DEPLOYMENT_TARGET} + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + + CFBundleDevelopmentRegion + en + CFBundleAllowMixedLocalizations + + + NSPrincipalClass + NSApplication + + NSSupportsAutomaticGraphicsSwitching + + + diff --git a/scwx-qt/res/textures/images/dot.svg b/scwx-qt/res/textures/images/dot.svg new file mode 100644 index 00000000..8f765035 --- /dev/null +++ b/scwx-qt/res/textures/images/dot.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/scwx-qt/res/textures/images/location-marker.svg b/scwx-qt/res/textures/images/location-marker.svg new file mode 100644 index 00000000..3eef9d9e --- /dev/null +++ b/scwx-qt/res/textures/images/location-marker.svg @@ -0,0 +1,11 @@ + + + + + + diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 78d45217..ecd26ef9 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.21) +cmake_minimum_required(VERSION 3.24) project(scwx-qt LANGUAGES CXX) @@ -6,17 +6,19 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) +set(CMAKE_AUTORCC OFF) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +OPTION(SCWX_DISABLE_CONSOLE "Disables the Windows console in release mode" ON) + find_package(Boost) find_package(Fontconfig) find_package(geographiclib) find_package(geos) -find_package(GLEW) find_package(glm) +find_package(OpenGL) find_package(Python COMPONENTS Interpreter) find_package(SQLite3) @@ -30,7 +32,9 @@ find_package(QT NAMES Qt6 Positioning SerialPort Svg - Widgets REQUIRED) + Widgets + Sql + REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Gui @@ -49,9 +53,13 @@ find_package(Qt${QT_VERSION_MAJOR} set(SRC_EXE_MAIN source/scwx/qt/main/main.cpp) set(HDR_MAIN source/scwx/qt/main/application.hpp - source/scwx/qt/main/main_window.hpp) + source/scwx/qt/main/check_privilege.hpp + source/scwx/qt/main/main_window.hpp + source/scwx/qt/main/process_validation.hpp) set(SRC_MAIN source/scwx/qt/main/application.cpp - source/scwx/qt/main/main_window.cpp) + source/scwx/qt/main/check_privilege.cpp + source/scwx/qt/main/main_window.cpp + source/scwx/qt/main/process_validation.cpp) set(UI_MAIN source/scwx/qt/main/main_window.ui) set(HDR_CONFIG source/scwx/qt/config/county_database.hpp source/scwx/qt/config/radar_site.hpp) @@ -95,11 +103,13 @@ set(HDR_MANAGER source/scwx/qt/manager/alert_manager.hpp source/scwx/qt/manager/log_manager.hpp source/scwx/qt/manager/media_manager.hpp source/scwx/qt/manager/placefile_manager.hpp + source/scwx/qt/manager/marker_manager.hpp source/scwx/qt/manager/position_manager.hpp source/scwx/qt/manager/radar_product_manager.hpp source/scwx/qt/manager/radar_product_manager_notifier.hpp source/scwx/qt/manager/resource_manager.hpp source/scwx/qt/manager/settings_manager.hpp + source/scwx/qt/manager/task_manager.hpp source/scwx/qt/manager/text_event_manager.hpp source/scwx/qt/manager/thread_manager.hpp source/scwx/qt/manager/timeline_manager.hpp @@ -111,11 +121,13 @@ set(SRC_MANAGER source/scwx/qt/manager/alert_manager.cpp source/scwx/qt/manager/log_manager.cpp source/scwx/qt/manager/media_manager.cpp source/scwx/qt/manager/placefile_manager.cpp + source/scwx/qt/manager/marker_manager.cpp source/scwx/qt/manager/position_manager.cpp source/scwx/qt/manager/radar_product_manager.cpp source/scwx/qt/manager/radar_product_manager_notifier.cpp source/scwx/qt/manager/resource_manager.cpp source/scwx/qt/manager/settings_manager.cpp + source/scwx/qt/manager/task_manager.cpp source/scwx/qt/manager/text_event_manager.cpp source/scwx/qt/manager/thread_manager.cpp source/scwx/qt/manager/timeline_manager.cpp @@ -132,6 +144,7 @@ set(HDR_MAP source/scwx/qt/map/alert_layer.hpp source/scwx/qt/map/overlay_layer.hpp source/scwx/qt/map/overlay_product_layer.hpp source/scwx/qt/map/placefile_layer.hpp + source/scwx/qt/map/marker_layer.hpp source/scwx/qt/map/radar_product_layer.hpp source/scwx/qt/map/radar_range_layer.hpp source/scwx/qt/map/radar_site_layer.hpp) @@ -146,6 +159,7 @@ set(SRC_MAP source/scwx/qt/map/alert_layer.cpp source/scwx/qt/map/overlay_layer.cpp source/scwx/qt/map/overlay_product_layer.cpp source/scwx/qt/map/placefile_layer.cpp + source/scwx/qt/map/marker_layer.cpp source/scwx/qt/map/radar_product_layer.cpp source/scwx/qt/map/radar_range_layer.cpp source/scwx/qt/map/radar_site_layer.cpp) @@ -154,6 +168,7 @@ set(HDR_MODEL source/scwx/qt/model/alert_model.hpp source/scwx/qt/model/imgui_context_model.hpp source/scwx/qt/model/layer_model.hpp source/scwx/qt/model/placefile_model.hpp + source/scwx/qt/model/marker_model.hpp source/scwx/qt/model/radar_site_model.hpp source/scwx/qt/model/tree_item.hpp source/scwx/qt/model/tree_model.hpp) @@ -162,6 +177,7 @@ set(SRC_MODEL source/scwx/qt/model/alert_model.cpp source/scwx/qt/model/imgui_context_model.cpp source/scwx/qt/model/layer_model.cpp source/scwx/qt/model/placefile_model.cpp + source/scwx/qt/model/marker_model.cpp source/scwx/qt/model/radar_site_model.cpp source/scwx/qt/model/tree_item.cpp source/scwx/qt/model/tree_model.cpp) @@ -169,9 +185,11 @@ set(HDR_REQUEST source/scwx/qt/request/download_request.hpp source/scwx/qt/request/nexrad_file_request.hpp) set(SRC_REQUEST source/scwx/qt/request/download_request.cpp source/scwx/qt/request/nexrad_file_request.cpp) -set(HDR_SETTINGS source/scwx/qt/settings/audio_settings.hpp +set(HDR_SETTINGS source/scwx/qt/settings/alert_palette_settings.hpp + source/scwx/qt/settings/audio_settings.hpp source/scwx/qt/settings/general_settings.hpp source/scwx/qt/settings/hotkey_settings.hpp + source/scwx/qt/settings/line_settings.hpp source/scwx/qt/settings/map_settings.hpp source/scwx/qt/settings/palette_settings.hpp source/scwx/qt/settings/product_settings.hpp @@ -185,9 +203,11 @@ set(HDR_SETTINGS source/scwx/qt/settings/audio_settings.hpp source/scwx/qt/settings/text_settings.hpp source/scwx/qt/settings/ui_settings.hpp source/scwx/qt/settings/unit_settings.hpp) -set(SRC_SETTINGS source/scwx/qt/settings/audio_settings.cpp +set(SRC_SETTINGS source/scwx/qt/settings/alert_palette_settings.cpp + source/scwx/qt/settings/audio_settings.cpp source/scwx/qt/settings/general_settings.cpp source/scwx/qt/settings/hotkey_settings.cpp + source/scwx/qt/settings/line_settings.cpp source/scwx/qt/settings/map_settings.cpp source/scwx/qt/settings/palette_settings.cpp source/scwx/qt/settings/product_settings.cpp @@ -210,6 +230,7 @@ set(HDR_TYPES source/scwx/qt/types/alert_types.hpp source/scwx/qt/types/layer_types.hpp source/scwx/qt/types/location_types.hpp source/scwx/qt/types/map_types.hpp + source/scwx/qt/types/marker_types.hpp source/scwx/qt/types/media_types.hpp source/scwx/qt/types/qt_types.hpp source/scwx/qt/types/radar_product_record.hpp @@ -238,10 +259,13 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp source/scwx/qt/ui/alert_dialog.hpp source/scwx/qt/ui/alert_dock_widget.hpp source/scwx/qt/ui/animation_dock_widget.hpp + source/scwx/qt/ui/api_key_edit_widget.hpp source/scwx/qt/ui/collapsible_group.hpp source/scwx/qt/ui/county_dialog.hpp - source/scwx/qt/ui/wfo_dialog.hpp + source/scwx/qt/ui/custom_layer_dialog.hpp source/scwx/qt/ui/download_dialog.hpp + source/scwx/qt/ui/edit_line_dialog.hpp + source/scwx/qt/ui/edit_marker_dialog.hpp source/scwx/qt/ui/flow_layout.hpp source/scwx/qt/ui/gps_info_dialog.hpp source/scwx/qt/ui/hotkey_edit.hpp @@ -252,22 +276,29 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp source/scwx/qt/ui/level2_products_widget.hpp source/scwx/qt/ui/level2_settings_widget.hpp source/scwx/qt/ui/level3_products_widget.hpp + source/scwx/qt/ui/line_label.hpp source/scwx/qt/ui/open_url_dialog.hpp source/scwx/qt/ui/placefile_dialog.hpp source/scwx/qt/ui/placefile_settings_widget.hpp + source/scwx/qt/ui/marker_dialog.hpp + source/scwx/qt/ui/marker_settings_widget.hpp source/scwx/qt/ui/progress_dialog.hpp source/scwx/qt/ui/radar_site_dialog.hpp source/scwx/qt/ui/serial_port_dialog.hpp source/scwx/qt/ui/settings_dialog.hpp - source/scwx/qt/ui/update_dialog.hpp) + source/scwx/qt/ui/update_dialog.hpp + source/scwx/qt/ui/wfo_dialog.hpp) set(SRC_UI source/scwx/qt/ui/about_dialog.cpp source/scwx/qt/ui/alert_dialog.cpp source/scwx/qt/ui/alert_dock_widget.cpp source/scwx/qt/ui/animation_dock_widget.cpp + source/scwx/qt/ui/api_key_edit_widget.cpp source/scwx/qt/ui/collapsible_group.cpp source/scwx/qt/ui/county_dialog.cpp - source/scwx/qt/ui/wfo_dialog.cpp + source/scwx/qt/ui/custom_layer_dialog.cpp source/scwx/qt/ui/download_dialog.cpp + source/scwx/qt/ui/edit_line_dialog.cpp + source/scwx/qt/ui/edit_marker_dialog.cpp source/scwx/qt/ui/flow_layout.cpp source/scwx/qt/ui/gps_info_dialog.cpp source/scwx/qt/ui/hotkey_edit.cpp @@ -278,36 +309,47 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp source/scwx/qt/ui/level2_products_widget.cpp source/scwx/qt/ui/level2_settings_widget.cpp source/scwx/qt/ui/level3_products_widget.cpp + source/scwx/qt/ui/line_label.cpp source/scwx/qt/ui/open_url_dialog.cpp source/scwx/qt/ui/placefile_dialog.cpp source/scwx/qt/ui/placefile_settings_widget.cpp + source/scwx/qt/ui/marker_dialog.cpp + source/scwx/qt/ui/marker_settings_widget.cpp source/scwx/qt/ui/progress_dialog.cpp source/scwx/qt/ui/radar_site_dialog.cpp source/scwx/qt/ui/settings_dialog.cpp source/scwx/qt/ui/serial_port_dialog.cpp - source/scwx/qt/ui/update_dialog.cpp) + source/scwx/qt/ui/update_dialog.cpp + source/scwx/qt/ui/wfo_dialog.cpp) set(UI_UI source/scwx/qt/ui/about_dialog.ui source/scwx/qt/ui/alert_dialog.ui source/scwx/qt/ui/alert_dock_widget.ui source/scwx/qt/ui/animation_dock_widget.ui source/scwx/qt/ui/collapsible_group.ui source/scwx/qt/ui/county_dialog.ui - source/scwx/qt/ui/wfo_dialog.ui + source/scwx/qt/ui/custom_layer_dialog.ui + source/scwx/qt/ui/edit_line_dialog.ui + source/scwx/qt/ui/edit_marker_dialog.ui source/scwx/qt/ui/gps_info_dialog.ui source/scwx/qt/ui/imgui_debug_dialog.ui source/scwx/qt/ui/layer_dialog.ui source/scwx/qt/ui/open_url_dialog.ui source/scwx/qt/ui/placefile_dialog.ui source/scwx/qt/ui/placefile_settings_widget.ui + source/scwx/qt/ui/marker_dialog.ui + source/scwx/qt/ui/marker_settings_widget.ui source/scwx/qt/ui/progress_dialog.ui source/scwx/qt/ui/radar_site_dialog.ui source/scwx/qt/ui/settings_dialog.ui source/scwx/qt/ui/serial_port_dialog.ui - source/scwx/qt/ui/update_dialog.ui) -set(HDR_UI_SETTINGS source/scwx/qt/ui/settings/hotkey_settings_widget.hpp + source/scwx/qt/ui/update_dialog.ui + source/scwx/qt/ui/wfo_dialog.ui) +set(HDR_UI_SETTINGS source/scwx/qt/ui/settings/alert_palette_settings_widget.hpp + source/scwx/qt/ui/settings/hotkey_settings_widget.hpp source/scwx/qt/ui/settings/settings_page_widget.hpp source/scwx/qt/ui/settings/unit_settings_widget.hpp) -set(SRC_UI_SETTINGS source/scwx/qt/ui/settings/hotkey_settings_widget.cpp +set(SRC_UI_SETTINGS source/scwx/qt/ui/settings/alert_palette_settings_widget.cpp + source/scwx/qt/ui/settings/hotkey_settings_widget.cpp source/scwx/qt/ui/settings/settings_page_widget.cpp source/scwx/qt/ui/settings/unit_settings_widget.cpp) set(HDR_UI_SETUP source/scwx/qt/ui/setup/audio_codec_page.hpp @@ -322,6 +364,9 @@ set(SRC_UI_SETUP source/scwx/qt/ui/setup/audio_codec_page.cpp source/scwx/qt/ui/setup/map_provider_page.cpp source/scwx/qt/ui/setup/setup_wizard.cpp source/scwx/qt/ui/setup/welcome_page.cpp) +set(HDR_UI_WIDGETS source/scwx/qt/ui/widgets/focused_combo_box.hpp + source/scwx/qt/ui/widgets/focused_double_spin_box.hpp + source/scwx/qt/ui/widgets/focused_spin_box.hpp) set(HDR_UTIL source/scwx/qt/util/color.hpp source/scwx/qt/util/file.hpp source/scwx/qt/util/geographic_lib.hpp @@ -331,8 +376,10 @@ set(HDR_UTIL source/scwx/qt/util/color.hpp source/scwx/qt/util/network.hpp source/scwx/qt/util/streams.hpp source/scwx/qt/util/texture_atlas.hpp + source/scwx/qt/util/q_color_modulate.hpp source/scwx/qt/util/q_file_buffer.hpp source/scwx/qt/util/q_file_input_stream.hpp + source/scwx/qt/util/queue_counter.hpp source/scwx/qt/util/time.hpp source/scwx/qt/util/tooltip.hpp) set(SRC_UTIL source/scwx/qt/util/color.cpp @@ -343,8 +390,10 @@ set(SRC_UTIL source/scwx/qt/util/color.cpp source/scwx/qt/util/maplibre.cpp source/scwx/qt/util/network.cpp source/scwx/qt/util/texture_atlas.cpp + source/scwx/qt/util/q_color_modulate.cpp source/scwx/qt/util/q_file_buffer.cpp source/scwx/qt/util/q_file_input_stream.cpp + source/scwx/qt/util/queue_counter.cpp source/scwx/qt/util/time.cpp source/scwx/qt/util/tooltip.cpp) set(HDR_VIEW source/scwx/qt/view/level2_product_view.hpp @@ -385,13 +434,13 @@ set(JSON_FILES res/config/radar_sites.json) set(TS_FILES ts/scwx_en_US.ts) set(RADAR_SITES_FILE ${scwx-qt_SOURCE_DIR}/res/config/radar_sites.json) -set(COUNTY_DBF_FILES ${SCWX_DIR}/data/db/c_05mr24.dbf) -set(ZONE_DBF_FILES ${SCWX_DIR}/data/db/fz05mr24.dbf - ${SCWX_DIR}/data/db/mz05mr24.dbf - ${SCWX_DIR}/data/db/oz05mr24.dbf - ${SCWX_DIR}/data/db/z_05mr24.dbf) -set(STATE_DBF_FILES ${SCWX_DIR}/data/db/s_05mr24.dbf) -set(WFO_DBF_FILES ${SCWX_DIR}/data/db/w_05mr24.dbf) +set(COUNTY_DBF_FILES ${SCWX_DIR}/data/db/c_18mr25.dbf) +set(ZONE_DBF_FILES ${SCWX_DIR}/data/db/fz18mr25.dbf + ${SCWX_DIR}/data/db/mz18mr25.dbf + ${SCWX_DIR}/data/db/oz18mr25.dbf + ${SCWX_DIR}/data/db/z_18mr25.dbf) +set(STATE_DBF_FILES ${SCWX_DIR}/data/db/s_18mr25.dbf) +set(WFO_DBF_FILES ${SCWX_DIR}/data/db/w_18mr25.dbf) set(COUNTIES_SQLITE_DB ${scwx-qt_BINARY_DIR}/res/db/counties.db) set(RESOURCE_INPUT ${scwx-qt_SOURCE_DIR}/res/scwx-qt.rc.in) @@ -429,17 +478,19 @@ set(PROJECT_SOURCES ${HDR_MAIN} ${SRC_UI_SETTINGS} ${HDR_UI_SETUP} ${SRC_UI_SETUP} + ${HDR_UI_WIDGETS} ${HDR_UTIL} ${SRC_UTIL} ${HDR_VIEW} ${SRC_VIEW} ${SHADER_FILES} ${JSON_FILES} - ${RESOURCE_FILES} ${TS_FILES} ${CMAKE_FILES}) set(EXECUTABLE_SOURCES ${SRC_EXE_MAIN}) +qt_add_resources(PROJECT_SOURCES ${RESOURCE_FILES}) + source_group("Header Files\\main" FILES ${HDR_MAIN}) source_group("Source Files\\main" FILES ${SRC_MAIN}) source_group("Header Files\\config" FILES ${HDR_CONFIG}) @@ -468,6 +519,7 @@ source_group("Header Files\\ui\\settings" FILES ${HDR_UI_SETTINGS}) source_group("Source Files\\ui\\settings" FILES ${SRC_UI_SETTINGS}) source_group("Header Files\\ui\\setup" FILES ${HDR_UI_SETUP}) source_group("Source Files\\ui\\setup" FILES ${SRC_UI_SETUP}) +source_group("Header Files\\ui\\widgets" FILES ${HDR_UI_WIDGETS}) source_group("UI Files\\ui" FILES ${UI_UI}) source_group("Header Files\\util" FILES ${HDR_UTIL}) source_group("Source Files\\util" FILES ${SRC_UTIL}) @@ -480,6 +532,7 @@ source_group("I18N Files" FILES ${TS_FILES}) add_library(scwx-qt OBJECT ${PROJECT_SOURCES}) set_property(TARGET scwx-qt PROPERTY AUTOMOC ON) +set_property(TARGET scwx-qt PROPERTY AUTOGEN_ORIGIN_DEPENDS OFF) add_custom_command(OUTPUT ${COUNTIES_SQLITE_DB} COMMAND ${Python_EXECUTABLE} @@ -526,7 +579,8 @@ else() -v ${SCWX_VERSION} -c ${VERSIONS_CACHE} -i ${VERSIONS_INPUT} - -o ${VERSIONS_HEADER}) + -o ${VERSIONS_HEADER} + -b ${SCWX_BUILD_NUM}) endif() add_custom_target(scwx-qt_generate_versions ALL @@ -572,7 +626,29 @@ set_target_properties(scwx-qt_update_radar_sites PROPERTIES FOLDER generate) if (WIN32) set(APP_ICON_RESOURCE_WINDOWS ${RESOURCE_OUTPUT}) qt_add_executable(supercell-wx ${EXECUTABLE_SOURCES} ${APP_ICON_RESOURCE_WINDOWS}) - set_target_properties(supercell-wx PROPERTIES WIN32_EXECUTABLE $,TRUE,FALSE>) + if (SCWX_DISABLE_CONSOLE) + set_target_properties(supercell-wx PROPERTIES WIN32_EXECUTABLE $,TRUE,FALSE>) + endif() +elseif (APPLE) + set(SCWX_ICON "${scwx-qt_SOURCE_DIR}/res/icons/scwx.icns") + + set_source_files_properties(${SCWX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") + + qt_add_executable(supercell-wx ${EXECUTABLE_SOURCES} ${SCWX_ICON}) + + string(TIMESTAMP CURRENT_YEAR "%Y") + + set_target_properties(supercell-wx PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST "${scwx-qt_SOURCE_DIR}/res/scwx-qt.plist.in" + MACOSX_BUNDLE_GUI_IDENTIFIER "net.supercellwx.app" + MACOSX_BUNDLE_BUNDLE_NAME "Supercell Wx" + MACOSX_BUNDLE_BUNDLE_VERSION "${SCWX_VERSION}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${SCWX_VERSION}" + MACOSX_BUNDLE_COPYRIGHT "Copyright ${CURRENT_YEAR} Dan Paulat" + MACOSX_BUNDLE_ICON_FILE "scwx.icns" + MACOSX_BUNDLE_INFO_STRING "Free and open source advanced weather radar" + RESOURCE ${SCWX_ICON}) else() qt_add_executable(supercell-wx ${EXECUTABLE_SOURCES}) endif() @@ -582,12 +658,17 @@ if (WIN32) target_compile_definitions(supercell-wx PUBLIC WIN32_LEAN_AND_MEAN) endif() -if (NOT MSVC) +if (LINUX) # Qt emit keyword is incompatible with TBB target_compile_definitions(scwx-qt PRIVATE QT_NO_EMIT) target_compile_definitions(supercell-wx PRIVATE QT_NO_EMIT) endif() +if (APPLE) + target_compile_definitions(scwx-qt PRIVATE GL_SILENCE_DEPRECATION) + target_compile_definitions(supercell-wx PRIVATE GL_SILENCE_DEPRECATION) +endif() + target_include_directories(scwx-qt PUBLIC ${scwx-qt_SOURCE_DIR}/source ${FTGL_INCLUDE_DIR} ${IMGUI_INCLUDE_DIRS} @@ -643,6 +724,25 @@ else() target_compile_options(supercell-wx PRIVATE "$<$:-g>") endif() +if (LINUX) + # Add wayland client packages + find_package(QT NAMES Qt6 + COMPONENTS WaylandClient + REQUIRED) + + find_package(Qt${QT_VERSION_MAJOR} + COMPONENTS WaylandClient + REQUIRED) + target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::WaylandClient) +endif() + +if (LINUX) + find_package(mesa-glu REQUIRED) + target_link_libraries(scwx-qt PUBLIC mesa-glu::mesa-glu) +else() + target_link_libraries(scwx-qt PUBLIC OpenGL::GLU) +endif() + target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::OpenGLWidgets Qt${QT_VERSION_MAJOR}::Multimedia @@ -651,6 +751,7 @@ target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Svg Boost::json Boost::timer + Boost::atomic QMapLibre::Core $<$:opengl32> $<$:SetupAPI> @@ -658,18 +759,22 @@ target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets GeographicLib::GeographicLib GEOS::geos GEOS::geos_cxx_flags - GLEW::GLEW + glad_gl_core_33 glm::glm imgui + qt6ct-common + qt6ct-widgets SQLite::SQLite3 wxdata) target_link_libraries(supercell-wx PRIVATE scwx-qt wxdata) -# Set DT_RUNPATH for Linux targets -set_target_properties(MLNQtCore PROPERTIES INSTALL_RPATH "\$ORIGIN/../lib") # QMapLibre::Core -set_target_properties(supercell-wx PROPERTIES INSTALL_RPATH "\$ORIGIN/../lib") +if (LINUX) + # Set DT_RUNPATH for Linux targets + set_target_properties(MLNQtCore PROPERTIES INSTALL_RPATH "\$ORIGIN/../lib") # QMapLibre::Core + set_target_properties(supercell-wx PROPERTIES INSTALL_RPATH "\$ORIGIN/../lib") +endif() install(TARGETS supercell-wx MLNQtCore # QMapLibre::Core @@ -679,7 +784,15 @@ install(TARGETS supercell-wx "^(/usr)?/lib/.*\\.so(\\..*)?" RUNTIME COMPONENT supercell-wx + BUNDLE + DESTINATION . + COMPONENT supercell-wx + OPTIONAL LIBRARY + COMPONENT supercell-wx + OPTIONAL + FRAMEWORK + DESTINATION Frameworks COMPONENT supercell-wx OPTIONAL) @@ -701,14 +814,64 @@ install(SCRIPT ${deploy_script_qmaplibre_core} install(SCRIPT ${deploy_script_scwx} COMPONENT supercell-wx) +if (APPLE) + # Install additional script to fix up the bundle + install(CODE [[ + include (BundleUtilities) + + # Define the bundle path + set(BUNDLE_PATH "${CMAKE_INSTALL_PREFIX}/supercell-wx.app") + + file(GLOB_RECURSE PLUGIN_DYLIBS "${BUNDLE_PATH}/Contents/PlugIns/**/*.dylib") + + # Add the correct rpath for plugins to find bundled frameworks + foreach(PLUGIN_DYLIB ${PLUGIN_DYLIBS}) + execute_process( + COMMAND install_name_tool -add_rpath "@loader_path/../../Frameworks" + ${PLUGIN_DYLIB} + ) + endforeach() + + # Fix up the bundle with all dependencies + fixup_bundle( + "${BUNDLE_PATH}" + "" + "${CMAKE_INSTALL_PREFIX}/lib;${CMAKE_INSTALL_PREFIX}/Frameworks" + ) + + # Re-sign the bundle + execute_process( + COMMAND codesign --force --deep --sign - "${BUNDLE_PATH}" + ) + + # Verify the bundle + verify_app("${BUNDLE_PATH}") + + # Rename to "Supercell Wx.app" + file(REMOVE_RECURSE + "${CMAKE_INSTALL_PREFIX}/Supercell Wx.app") + file(RENAME + "${BUNDLE_PATH}" + "${CMAKE_INSTALL_PREFIX}/Supercell Wx.app") + + # Remove extra directories + file(REMOVE_RECURSE + "${CMAKE_INSTALL_PREFIX}/Frameworks") + file(REMOVE_RECURSE + "${CMAKE_INSTALL_PREFIX}/lib") + ]] + COMPONENT supercell-wx) +endif() + +set(CPACK_PACKAGE_NAME "Supercell Wx") +set(CPACK_PACKAGE_VENDOR "Dan Paulat") +set(CPACK_PACKAGE_CHECKSUM SHA256) +set(CPACK_RESOURCE_FILE_LICENSE "${SCWX_DIR}/LICENSE.txt") + if (MSVC) - set(CPACK_PACKAGE_NAME "Supercell Wx") - set(CPACK_PACKAGE_VENDOR "Dan Paulat") set(CPACK_PACKAGE_FILE_NAME "supercell-wx-v${SCWX_VERSION}-windows-x64") set(CPACK_PACKAGE_INSTALL_DIRECTORY "Supercell Wx") set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/res/icons/scwx-256.ico") - set(CPACK_PACKAGE_CHECKSUM SHA256) - set(CPACK_RESOURCE_FILE_LICENSE "${SCWX_DIR}/LICENSE.txt") set(CPACK_GENERATOR WIX) set(CPACK_PACKAGE_EXECUTABLES "supercell-wx;Supercell Wx") set(CPACK_WIX_UPGRADE_GUID 36AD0F51-4D4F-4B5D-AB61-94C6B4E4FE1C) @@ -720,5 +883,15 @@ if (MSVC) set(CPACK_INSTALL_CMAKE_PROJECTS "${CMAKE_CURRENT_BINARY_DIR};${CMAKE_PROJECT_NAME};supercell-wx;/") + include(CPack) +elseif(APPLE) + set(CPACK_PACKAGE_FILE_NAME "supercell-wx-v${SCWX_VERSION}-macos") + set(CPACK_PACKAGE_ICON "${SCWX_ICON}") + set(CPACK_PACKAGE_VERSION "${SCWX_VERSION}") + + set(CPACK_GENERATOR DragNDrop) + + set(CPACK_COMPONENTS_ALL supercell-wx) + include(CPack) endif() diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index c9e00337..ccb2e42a 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -32,6 +32,10 @@ res/icons/font-awesome-6/angles-up-solid.svg res/icons/font-awesome-6/backward-step-solid.svg res/icons/font-awesome-6/book-solid.svg + res/icons/font-awesome-6/briefcase-solid.svg + res/icons/font-awesome-6/building-columns-solid.svg + res/icons/font-awesome-6/building-solid.svg + res/icons/font-awesome-6/caravan-solid.svg res/icons/font-awesome-6/copy-regular.svg res/icons/font-awesome-6/discord.svg res/icons/font-awesome-6/earth-americas-solid.svg @@ -40,8 +44,11 @@ res/icons/font-awesome-6/gears-solid.svg res/icons/font-awesome-6/github.svg res/icons/font-awesome-6/house-solid.svg + res/icons/font-awesome-6/house-solid-white.svg res/icons/font-awesome-6/keyboard-regular.svg res/icons/font-awesome-6/layer-group-solid.svg + res/icons/font-awesome-6/location-crosshairs-solid.svg + res/icons/font-awesome-6/location-pin.svg res/icons/font-awesome-6/palette-solid.svg res/icons/font-awesome-6/pause-solid.svg res/icons/font-awesome-6/play-solid.svg @@ -53,7 +60,9 @@ res/icons/font-awesome-6/square-minus-regular.svg res/icons/font-awesome-6/square-plus-regular.svg res/icons/font-awesome-6/star-solid.svg + res/icons/font-awesome-6/star-solid-white.svg res/icons/font-awesome-6/stop-solid.svg + res/icons/font-awesome-6/tent-solid.svg res/icons/font-awesome-6/volume-high-solid.svg res/palettes/wct/CC.pal res/palettes/wct/Default16.pal @@ -70,11 +79,20 @@ res/palettes/wct/SW.pal res/palettes/wct/VIL.pal res/palettes/wct/ZDR.pal + ../external/qt6ct/colors/airy.conf + ../external/qt6ct/colors/darker.conf + ../external/qt6ct/colors/dusk.conf + ../external/qt6ct/colors/ia_ora.conf + ../external/qt6ct/colors/sand.conf + ../external/qt6ct/colors/simple.conf + ../external/qt6ct/colors/waves.conf res/textures/lines/default-1x7.png res/textures/lines/test-pattern.png res/textures/images/cursor-17.png res/textures/images/crosshairs-24.png res/textures/images/dot-3.png + res/textures/images/dot.svg + res/textures/images/location-marker.svg res/textures/images/mapbox-logo.svg res/textures/images/maptiler-logo.svg diff --git a/scwx-qt/source/scwx/qt/config/radar_site.cpp b/scwx-qt/source/scwx/qt/config/radar_site.cpp index c0cb4636..69815636 100644 --- a/scwx-qt/source/scwx/qt/config/radar_site.cpp +++ b/scwx-qt/source/scwx/qt/config/radar_site.cpp @@ -10,7 +10,7 @@ #include -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) # include #endif @@ -51,6 +51,7 @@ public: std::string state_ {}; std::string place_ {}; std::string tzName_ {}; + double altitude_ {0.0}; const scwx::util::time_zone* timeZone_ {nullptr}; }; @@ -142,6 +143,11 @@ const scwx::util::time_zone* RadarSite::time_zone() const return p->timeZone_; } +units::length::feet RadarSite::altitude() const +{ + return units::length::feet(p->altitude_); +} + std::shared_ptr RadarSite::Get(const std::string& id) { std::shared_lock lock(siteMutex_); @@ -239,7 +245,7 @@ size_t RadarSite::ReadConfig(const std::string& path) bool dataValid = true; size_t sitesAdded = 0; - boost::json::value j = util::json::ReadJsonFile(path); + boost::json::value j = util::json::ReadJsonQFile(path); dataValid = j.is_array(); @@ -268,10 +274,12 @@ size_t RadarSite::ReadConfig(const std::string& path) site->p->state_ = boost::json::value_to(o.at("state")); site->p->place_ = boost::json::value_to(o.at("place")); site->p->tzName_ = boost::json::value_to(o.at("tz")); + site->p->altitude_ = + boost::json::value_to(o.at("elevation")); try { -#if defined(_MSC_VER) +#if (__cpp_lib_chrono >= 201907L) using namespace std::chrono; #else using namespace date; diff --git a/scwx-qt/source/scwx/qt/config/radar_site.hpp b/scwx-qt/source/scwx/qt/config/radar_site.hpp index 16f6e710..cf622d7a 100644 --- a/scwx-qt/source/scwx/qt/config/radar_site.hpp +++ b/scwx-qt/source/scwx/qt/config/radar_site.hpp @@ -6,12 +6,9 @@ #include #include #include +#include -namespace scwx -{ -namespace qt -{ -namespace config +namespace scwx::qt::config { class RadarSiteImpl; @@ -28,18 +25,19 @@ public: RadarSite(RadarSite&&) noexcept; RadarSite& operator=(RadarSite&&) noexcept; - std::string type() const; - std::string type_name() const; - std::string id() const; - double latitude() const; - double longitude() const; - std::string country() const; - std::string state() const; - std::string place() const; - std::string location_name() const; - std::string tz_name() const; + [[nodiscard]] std::string type() const; + [[nodiscard]] std::string type_name() const; + [[nodiscard]] std::string id() const; + [[nodiscard]] double latitude() const; + [[nodiscard]] double longitude() const; + [[nodiscard]] std::string country() const; + [[nodiscard]] std::string state() const; + [[nodiscard]] std::string place() const; + [[nodiscard]] std::string location_name() const; + [[nodiscard]] std::string tz_name() const; + [[nodiscard]] units::length::feet altitude() const; - const scwx::util::time_zone* time_zone() const; + [[nodiscard]] const scwx::util::time_zone* time_zone() const; static std::shared_ptr Get(const std::string& id); static std::vector> GetAll(); @@ -67,6 +65,4 @@ private: std::string GetRadarIdFromSiteId(const std::string& siteId); -} // namespace config -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::config diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp index c6737a10..0f18e40f 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp @@ -30,13 +30,11 @@ static const std::string logPrefix_ = "scwx::qt::gl::draw::draw_item"; class DrawItem::Impl { public: - explicit Impl(OpenGLFunctions& gl) : gl_ {gl} {} - ~Impl() {} - - OpenGLFunctions& gl_; + explicit Impl() = default; + ~Impl() = default; }; -DrawItem::DrawItem(OpenGLFunctions& gl) : p(std::make_unique(gl)) {} +DrawItem::DrawItem() : p(std::make_unique()) {} DrawItem::~DrawItem() = default; DrawItem::DrawItem(DrawItem&&) noexcept = default; @@ -74,7 +72,7 @@ void DrawItem::UseDefaultProjection( 0.0f, static_cast(params.height)); - p->gl_.glUniformMatrix4fv( + glUniformMatrix4fv( uMVPMatrixLocation, 1, GL_FALSE, glm::value_ptr(projection)); } @@ -91,7 +89,7 @@ void DrawItem::UseRotationProjection( glm::radians(params.bearing), glm::vec3(0.0f, 0.0f, 1.0f)); - p->gl_.glUniformMatrix4fv( + glUniformMatrix4fv( uMVPMatrixLocation, 1, GL_FALSE, glm::value_ptr(projection)); } @@ -100,16 +98,14 @@ void DrawItem::UseMapProjection( GLint uMVPMatrixLocation, GLint uMapScreenCoordLocation) { - OpenGLFunctions& gl = p->gl_; - const glm::mat4 uMVPMatrix = util::maplibre::GetMapMatrix(params); - gl.glUniform2fv(uMapScreenCoordLocation, - 1, - glm::value_ptr(util::maplibre::LatLongToScreenCoordinate( - {params.latitude, params.longitude}))); + glUniform2fv(uMapScreenCoordLocation, + 1, + glm::value_ptr(util::maplibre::LatLongToScreenCoordinate( + {params.latitude, params.longitude}))); - gl.glUniformMatrix4fv( + glUniformMatrix4fv( uMVPMatrixLocation, 1, GL_FALSE, glm::value_ptr(uMVPMatrix)); } diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp index f7df44c4..28350190 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp @@ -21,8 +21,8 @@ namespace draw class DrawItem { public: - explicit DrawItem(OpenGLFunctions& gl); - ~DrawItem(); + explicit DrawItem(); + virtual ~DrawItem(); DrawItem(const DrawItem&) = delete; DrawItem& operator=(const DrawItem&) = delete; diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp index 622718e3..05cc26a5 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -38,7 +39,7 @@ static constexpr std::size_t kIntegersPerVertex_ = 4; static constexpr std::size_t kIntegerBufferLength_ = kNumTriangles * kVerticesPerTriangle * kIntegersPerVertex_; -struct GeoIconDrawItem +struct GeoIconDrawItem : types::EventHandler { units::length::nautical_miles threshold_ {}; std::chrono::sys_time startTime_ {}; @@ -144,7 +145,7 @@ public: }; GeoIcons::GeoIcons(const std::shared_ptr& context) : - DrawItem(context->gl()), p(std::make_unique(context)) + DrawItem(), p(std::make_unique(context)) { } GeoIcons::~GeoIcons() = default; @@ -165,8 +166,6 @@ void GeoIcons::set_thresholded(bool thresholded) void GeoIcons::Initialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - p->shaderProgram_ = p->context_->GetShaderProgram( {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, @@ -181,88 +180,95 @@ void GeoIcons::Initialize() p->uSelectedTimeLocation_ = p->shaderProgram_->GetUniformLocation("uSelectedTime"); - gl.glGenVertexArrays(1, &p->vao_); - gl.glGenBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); + glGenVertexArrays(1, &p->vao_); + glGenBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); - gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindVertexArray(p->vao_); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // NOLINTBEGIN(modernize-use-nullptr) + // NOLINTBEGIN(performance-no-int-to-ptr) + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) // aLatLong - gl.glVertexAttribPointer(0, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - static_cast(0)); - gl.glEnableVertexAttribArray(0); + glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + static_cast(0)); + glEnableVertexAttribArray(0); // aXYOffset - gl.glVertexAttribPointer(1, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(2 * sizeof(float))); - gl.glEnableVertexAttribArray(1); + glVertexAttribPointer(1, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(2 * sizeof(float))); + glEnableVertexAttribArray(1); // aModulate - gl.glVertexAttribPointer(3, - 4, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(4 * sizeof(float))); - gl.glEnableVertexAttribArray(3); + glVertexAttribPointer(3, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + glEnableVertexAttribArray(3); // aAngle - gl.glVertexAttribPointer(4, - 1, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(8 * sizeof(float))); - gl.glEnableVertexAttribArray(4); + glVertexAttribPointer(4, + 1, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(8 * sizeof(float))); + glEnableVertexAttribArray(4); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); // aTexCoord - gl.glVertexAttribPointer(2, - 3, - GL_FLOAT, - GL_FALSE, - kPointsPerTexCoord * sizeof(float), - static_cast(0)); - gl.glEnableVertexAttribArray(2); + glVertexAttribPointer(2, + 3, + GL_FLOAT, + GL_FALSE, + kPointsPerTexCoord * sizeof(float), + static_cast(0)); + glEnableVertexAttribArray(2); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[2]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[2]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); // aThreshold - gl.glVertexAttribIPointer(5, // - 1, - GL_INT, - 0, - static_cast(0)); - gl.glEnableVertexAttribArray(5); + glVertexAttribIPointer(5, // + 1, + GL_INT, + 0, + static_cast(0)); + glEnableVertexAttribArray(5); // aTimeRange - gl.glVertexAttribIPointer(6, // - 2, - GL_INT, - kIntegersPerVertex_ * sizeof(GLint), - reinterpret_cast(1 * sizeof(GLint))); - gl.glEnableVertexAttribArray(6); + glVertexAttribIPointer(6, // + 2, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + reinterpret_cast(1 * sizeof(GLint))); + glEnableVertexAttribArray(6); // aDisplayed - gl.glVertexAttribPointer(7, - 1, - GL_INT, - GL_FALSE, - kIntegersPerVertex_ * sizeof(GLint), - reinterpret_cast(3 * sizeof(float))); - gl.glEnableVertexAttribArray(7); + glVertexAttribIPointer(7, + 1, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + reinterpret_cast(3 * sizeof(GLint))); + glEnableVertexAttribArray(7); + + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + // NOLINTEND(performance-no-int-to-ptr) + // NOLINTEND(modernize-use-nullptr) p->dirty_ = true; } @@ -284,9 +290,7 @@ void GeoIcons::Render(const QMapLibre::CustomLayerRenderParameters& params, if (!p->currentIconList_.empty()) { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glBindVertexArray(p->vao_); + glBindVertexArray(p->vao_); p->Update(textureAtlasChanged); p->shaderProgram_->Use(); @@ -299,40 +303,38 @@ void GeoIcons::Render(const QMapLibre::CustomLayerRenderParameters& params, // If thresholding is enabled, set the map distance units::length::nautical_miles mapDistance = util::maplibre::GetMapDistance(params); - gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); + glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); } else { // If thresholding is disabled, set the map distance to 0 - gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); + glUniform1f(p->uMapDistanceLocation_, 0.0f); } // Selected time std::chrono::system_clock::time_point selectedTime = (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? - std::chrono::system_clock::now() : + scwx::util::time::now() : p->selectedTime_; - gl.glUniform1i( + glUniform1i( p->uSelectedTimeLocation_, static_cast(std::chrono::duration_cast( selectedTime.time_since_epoch()) .count())); // Interpolate texture coordinates - gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Draw icons - gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); } } void GeoIcons::Deinitialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glDeleteVertexArrays(1, &p->vao_); - gl.glDeleteBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); + glDeleteVertexArrays(1, &p->vao_); + glDeleteBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); std::unique_lock lock {p->iconMutex_}; @@ -692,7 +694,7 @@ void GeoIcons::Impl::UpdateSingleBuffer( hoverIcons.end(), [&di](auto& entry) { return entry.di_ == di; }); - if (di->visible_ && !di->hoverText_.empty()) + if (di->visible_ && (!di->hoverText_.empty() || di->event_ != nullptr)) { const units::angle::radians radians = angle; @@ -848,8 +850,6 @@ void GeoIcons::Impl::UpdateModifiedIconBuffers() void GeoIcons::Impl::Update(bool textureAtlasChanged) { - gl::OpenGLFunctions& gl = context_->gl(); - UpdateModifiedIconBuffers(); // If the texture atlas has changed @@ -865,11 +865,12 @@ void GeoIcons::Impl::Update(bool textureAtlasChanged) UpdateTextureBuffer(); // Buffer texture data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(float) * textureBuffer_.size(), - textureBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(float) * textureBuffer_.size()), + textureBuffer_.data(), + GL_DYNAMIC_DRAW); lastTextureAtlasChanged_ = false; } @@ -878,18 +879,20 @@ void GeoIcons::Impl::Update(bool textureAtlasChanged) if (dirty_) { // Buffer vertex data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(float) * currentIconBuffer_.size(), - currentIconBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(float) * currentIconBuffer_.size()), + currentIconBuffer_.data(), + GL_DYNAMIC_DRAW); // Buffer threshold data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[2]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLint) * currentIntegerBuffer_.size(), - currentIntegerBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[2]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(GLint) * currentIntegerBuffer_.size()), + currentIntegerBuffer_.data(), + GL_DYNAMIC_DRAW); numVertices_ = static_cast(currentIconBuffer_.size() / kPointsPerVertex); @@ -904,7 +907,7 @@ bool GeoIcons::RunMousePicking( const QPointF& mouseGlobalPos, const glm::vec2& mouseCoords, const common::Coordinate& /* mouseGeoCoords */, - std::shared_ptr& /* eventHandler */) + std::shared_ptr& eventHandler) { std::unique_lock lock {p->iconMutex_}; @@ -928,7 +931,7 @@ bool GeoIcons::RunMousePicking( // If no time has been selected, use the current time std::chrono::system_clock::time_point selectedTime = (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? - std::chrono::system_clock::now() : + scwx::util::time::now() : p->selectedTime_; // For each pickable icon @@ -947,8 +950,10 @@ bool GeoIcons::RunMousePicking( units::length::nautical_miles {icon.di_->threshold_} .value())) < 999 && - // Map distance is beyond the threshold - icon.di_->threshold_ < mapDistance) || + // Map distance is beyond/within the threshold + icon.di_->threshold_ < mapDistance && + (icon.di_->threshold_.value() >= 0.0 || + -(icon.di_->threshold_) > mapDistance)) || ( // Geo icon has a start time @@ -992,12 +997,27 @@ bool GeoIcons::RunMousePicking( if (it != p->currentHoverIcons_.crend()) { itemPicked = true; - util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos); + if (!it->di_->hoverText_.empty()) + { + // Show tooltip + util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos); + } + if (it->di_->event_ != nullptr) + { + eventHandler = it->di_; + } } return itemPicked; } +void GeoIcons::RegisterEventHandler( + const std::shared_ptr& di, + const std::function& eventHandler) +{ + di->event_ = eventHandler; +} + } // namespace draw } // namespace gl } // namespace qt diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_icons.hpp b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.hpp index 4d819681..073fc118 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/geo_icons.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.hpp @@ -183,6 +183,16 @@ public: */ void FinishIcons(); + /** + * Registers an event handler for an icon. + * + * @param [in] di Icon draw item + * @param [in] eventHandler Event handler function + */ + static void + RegisterEventHandler(const std::shared_ptr& di, + const std::function& eventHandler); + private: class Impl; diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/geo_lines.cpp index d9f2c023..e35cca4f 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/geo_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_lines.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -50,6 +51,7 @@ struct GeoLineDrawItem : types::EventHandler units::angle::degrees angle_ {}; std::string hoverText_ {}; GeoLines::HoverCallback hoverCallback_ {nullptr}; + size_t lineIndex_ {0}; }; class GeoLines::Impl @@ -87,10 +89,10 @@ public: void UpdateBuffers(); void UpdateModifiedLineBuffers(); void UpdateSingleBuffer(const std::shared_ptr& di, - std::size_t lineIndex, std::vector& linesBuffer, - std::vector& integerBuffer, - std::vector& hoverLines); + std::vector& integerBuffer, + std::unordered_map, + LineHoverEntry>& hoverLines); std::shared_ptr context_; @@ -112,8 +114,10 @@ public: std::vector newLinesBuffer_ {}; std::vector newIntegerBuffer_ {}; - std::vector currentHoverLines_ {}; - std::vector newHoverLines_ {}; + std::unordered_map, LineHoverEntry> + currentHoverLines_ {}; + std::unordered_map, LineHoverEntry> + newHoverLines_ {}; std::shared_ptr shaderProgram_; GLint uMVPMatrixLocation_; @@ -127,7 +131,7 @@ public: }; GeoLines::GeoLines(std::shared_ptr context) : - DrawItem(context->gl()), p(std::make_unique(context)) + DrawItem(), p(std::make_unique(context)) { } GeoLines::~GeoLines() = default; @@ -148,8 +152,6 @@ void GeoLines::set_thresholded(bool thresholded) void GeoLines::Initialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - p->shaderProgram_ = p->context_->GetShaderProgram( {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, @@ -164,79 +166,86 @@ void GeoLines::Initialize() p->uSelectedTimeLocation_ = p->shaderProgram_->GetUniformLocation("uSelectedTime"); - gl.glGenVertexArrays(1, &p->vao_); - gl.glGenBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); + glGenVertexArrays(1, &p->vao_); + glGenBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); - gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(float) * kLineBufferLength_, - nullptr, - GL_DYNAMIC_DRAW); + glBindVertexArray(p->vao_); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + glBufferData(GL_ARRAY_BUFFER, + sizeof(float) * kLineBufferLength_, + nullptr, + GL_DYNAMIC_DRAW); + + // NOLINTBEGIN(modernize-use-nullptr) + // NOLINTBEGIN(performance-no-int-to-ptr) + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) // aLatLong - gl.glVertexAttribPointer(0, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - static_cast(0)); - gl.glEnableVertexAttribArray(0); + glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + static_cast(0)); + glEnableVertexAttribArray(0); // aXYOffset - gl.glVertexAttribPointer(1, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(2 * sizeof(float))); - gl.glEnableVertexAttribArray(1); + glVertexAttribPointer(1, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(2 * sizeof(float))); + glEnableVertexAttribArray(1); // aModulate - gl.glVertexAttribPointer(3, - 4, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(4 * sizeof(float))); - gl.glEnableVertexAttribArray(3); + glVertexAttribPointer(3, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + glEnableVertexAttribArray(3); // aAngle - gl.glVertexAttribPointer(4, - 1, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(8 * sizeof(float))); - gl.glEnableVertexAttribArray(4); + glVertexAttribPointer(4, + 1, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(8 * sizeof(float))); + glEnableVertexAttribArray(4); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); // aThreshold - gl.glVertexAttribIPointer(5, // - 1, - GL_INT, - kIntegersPerVertex_ * sizeof(GLint), - static_cast(0)); - gl.glEnableVertexAttribArray(5); + glVertexAttribIPointer(5, // + 1, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + static_cast(0)); + glEnableVertexAttribArray(5); // aTimeRange - gl.glVertexAttribIPointer(6, // - 2, - GL_INT, - kIntegersPerVertex_ * sizeof(GLint), - reinterpret_cast(1 * sizeof(GLint))); - gl.glEnableVertexAttribArray(6); + glVertexAttribIPointer(6, // + 2, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + reinterpret_cast(1 * sizeof(GLint))); + glEnableVertexAttribArray(6); // aDisplayed - gl.glVertexAttribPointer(7, - 1, - GL_INT, - GL_FALSE, - kIntegersPerVertex_ * sizeof(GLint), - reinterpret_cast(3 * sizeof(float))); - gl.glEnableVertexAttribArray(7); + glVertexAttribIPointer(7, + 1, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + reinterpret_cast(3 * sizeof(GLint))); + glEnableVertexAttribArray(7); + + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + // NOLINTEND(performance-no-int-to-ptr) + // NOLINTEND(modernize-use-nullptr) p->dirty_ = true; } @@ -252,9 +261,7 @@ void GeoLines::Render(const QMapLibre::CustomLayerRenderParameters& params) if (p->newLineList_.size() > 0) { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glBindVertexArray(p->vao_); + glBindVertexArray(p->vao_); p->Update(); p->shaderProgram_->Use(); @@ -267,39 +274,37 @@ void GeoLines::Render(const QMapLibre::CustomLayerRenderParameters& params) // If thresholding is enabled, set the map distance units::length::nautical_miles mapDistance = util::maplibre::GetMapDistance(params); - gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); + glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); } else { // If thresholding is disabled, set the map distance to 0 - gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); + glUniform1f(p->uMapDistanceLocation_, 0.0f); } // Selected time std::chrono::system_clock::time_point selectedTime = (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? - std::chrono::system_clock::now() : + scwx::util::time::now() : p->selectedTime_; - gl.glUniform1i( + glUniform1i( p->uSelectedTimeLocation_, static_cast(std::chrono::duration_cast( selectedTime.time_since_epoch()) .count())); // Draw icons - gl.glDrawArrays(GL_TRIANGLES, - 0, - static_cast(p->currentLineList_.size() * - kVerticesPerRectangle)); + glDrawArrays(GL_TRIANGLES, + 0, + static_cast(p->currentLineList_.size() * + kVerticesPerRectangle)); } } void GeoLines::Deinitialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glDeleteVertexArrays(1, &p->vao_); - gl.glDeleteBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); + glDeleteVertexArrays(1, &p->vao_); + glDeleteBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); std::unique_lock lock {p->lineMutex_}; @@ -324,7 +329,9 @@ void GeoLines::StartLines() std::shared_ptr GeoLines::AddLine() { - return p->newLineList_.emplace_back(std::make_shared()); + auto& di = p->newLineList_.emplace_back(std::make_shared()); + di->lineIndex_ = p->newLineList_.size() - 1; + return di; } void GeoLines::SetLineLocation(const std::shared_ptr& di, @@ -471,7 +478,7 @@ void GeoLines::Impl::UpdateBuffers() // Update line buffer UpdateSingleBuffer( - di, i, newLinesBuffer_, newIntegerBuffer_, newHoverLines_); + di, newLinesBuffer_, newIntegerBuffer_, newHoverLines_); } // All lines have been updated @@ -489,23 +496,15 @@ void GeoLines::Impl::UpdateModifiedLineBuffers() // Update buffers for modified lines for (auto& di : dirtyLines_) { - // Find modified line in the current list - auto it = - std::find(currentLineList_.cbegin(), currentLineList_.cend(), di); - - // Ignore invalid lines - if (it == currentLineList_.cend()) + // Check if modified line is in the current list + if (di->lineIndex_ >= currentLineList_.size() || + currentLineList_[di->lineIndex_] != di) { continue; } - auto lineIndex = std::distance(currentLineList_.cbegin(), it); - - UpdateSingleBuffer(di, - lineIndex, - currentLinesBuffer_, - currentIntegerBuffer_, - currentHoverLines_); + UpdateSingleBuffer( + di, currentLinesBuffer_, currentIntegerBuffer_, currentHoverLines_); } // Clear list of modified lines @@ -518,10 +517,10 @@ void GeoLines::Impl::UpdateModifiedLineBuffers() void GeoLines::Impl::UpdateSingleBuffer( const std::shared_ptr& di, - std::size_t lineIndex, std::vector& lineBuffer, std::vector& integerBuffer, - std::vector& hoverLines) + std::unordered_map, LineHoverEntry>& + hoverLines) { // Threshold value units::length::nautical_miles threshold = di->threshold_; @@ -589,10 +588,10 @@ void GeoLines::Impl::UpdateSingleBuffer( // Buffer position data auto lineBufferPosition = lineBuffer.end(); - auto lineBufferOffset = lineIndex * kLineBufferLength_; + auto lineBufferOffset = di->lineIndex_ * kLineBufferLength_; auto integerBufferPosition = integerBuffer.end(); - auto integerBufferOffset = lineIndex * kIntegerBufferLength_; + auto integerBufferOffset = di->lineIndex_ * kIntegerBufferLength_; if (lineBufferOffset < lineBuffer.size()) { @@ -621,9 +620,7 @@ void GeoLines::Impl::UpdateSingleBuffer( std::copy(integerData.begin(), integerData.end(), integerBufferPosition); } - auto hoverIt = std::find_if(hoverLines.begin(), - hoverLines.end(), - [&di](auto& entry) { return entry.di_ == di; }); + auto hoverIt = hoverLines.find(di); if (di->visible_ && (!di->hoverText_.empty() || di->hoverCallback_ != nullptr || di->event_ != nullptr)) @@ -645,17 +642,23 @@ void GeoLines::Impl::UpdateSingleBuffer( if (hoverIt == hoverLines.end()) { - hoverLines.emplace_back( - LineHoverEntry {di, sc1, sc2, otl, otr, obl, obr}); + hoverLines.emplace(di, + LineHoverEntry {.di_ = di, + .p1_ = sc1, + .p2_ = sc2, + .otl_ = otl, + .otr_ = otr, + .obl_ = obl, + .obr_ = obr}); } else { - hoverIt->p1_ = sc1; - hoverIt->p2_ = sc2; - hoverIt->otl_ = otl; - hoverIt->otr_ = otr; - hoverIt->obl_ = obl; - hoverIt->obr_ = obr; + hoverIt->second.p1_ = sc1; + hoverIt->second.p2_ = sc2; + hoverIt->second.otl_ = otl; + hoverIt->second.otr_ = otr; + hoverIt->second.obl_ = obl; + hoverIt->second.obr_ = obr; } } else if (hoverIt != hoverLines.end()) @@ -671,21 +674,21 @@ void GeoLines::Impl::Update() // If the lines have been updated if (dirty_) { - gl::OpenGLFunctions& gl = context_->gl(); - // Buffer lines data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(float) * currentLinesBuffer_.size(), - currentLinesBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(float) * currentLinesBuffer_.size()), + currentLinesBuffer_.data(), + GL_DYNAMIC_DRAW); // Buffer threshold data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLint) * currentIntegerBuffer_.size(), - currentIntegerBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(GLint) * currentIntegerBuffer_.size()), + currentIntegerBuffer_.data(), + GL_DYNAMIC_DRAW); } dirty_ = false; @@ -721,16 +724,18 @@ bool GeoLines::RunMousePicking( // If no time has been selected, use the current time std::chrono::system_clock::time_point selectedTime = (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? - std::chrono::system_clock::now() : + scwx::util::time::now() : p->selectedTime_; // For each pickable line auto it = std::find_if( std::execution::par_unseq, - p->currentHoverLines_.rbegin(), - p->currentHoverLines_.rend(), - [&mapDistance, &selectedTime, &mapMatrix, &mouseCoords](const auto& line) + p->currentHoverLines_.cbegin(), + p->currentHoverLines_.cend(), + [&mapDistance, &selectedTime, &mapMatrix, &mouseCoords]( + const auto& lineIt) { + const auto& line = lineIt.second; if (( // Placefile is thresholded mapDistance > units::length::meters {0.0} && @@ -740,8 +745,10 @@ bool GeoLines::RunMousePicking( units::length::nautical_miles {line.di_->threshold_} .value())) < 999 && - // Map distance is beyond the threshold - line.di_->threshold_ < mapDistance) || + // Map distance is beyond/within the threshold + line.di_->threshold_ < mapDistance && + (line.di_->threshold_.value() >= 0.0 || + -(line.di_->threshold_) > mapDistance)) || ( // Line has a start time @@ -784,24 +791,24 @@ bool GeoLines::RunMousePicking( return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mouseCoords); }); - if (it != p->currentHoverLines_.crend()) + if (it != p->currentHoverLines_.cend()) { itemPicked = true; - if (!it->di_->hoverText_.empty()) + if (!it->second.di_->hoverText_.empty()) { // Show tooltip - util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos); + util::tooltip::Show(it->second.di_->hoverText_, mouseGlobalPos); } - else if (it->di_->hoverCallback_ != nullptr) + else if (it->second.di_->hoverCallback_ != nullptr) { - it->di_->hoverCallback_(it->di_, mouseGlobalPos); + it->second.di_->hoverCallback_(it->second.di_, mouseGlobalPos); } - if (it->di_->event_ != nullptr) + if (it->second.di_->event_ != nullptr) { // Register event handler - eventHandler = it->di_; + eventHandler = it->second.di_; } } diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_lines.hpp b/scwx-qt/source/scwx/qt/gl/draw/geo_lines.hpp index b7b792d0..d6727110 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/geo_lines.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_lines.hpp @@ -19,9 +19,8 @@ struct GeoLineDrawItem; class GeoLines : public DrawItem { public: - typedef std::function&, - const QPointF&)> - HoverCallback; + using HoverCallback = std::function&, const QPointF&)>; explicit GeoLines(std::shared_ptr context); ~GeoLines(); diff --git a/scwx-qt/source/scwx/qt/gl/draw/icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/icons.cpp index 473b8a79..b72f4443 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/icons.cpp @@ -21,12 +21,11 @@ namespace draw static const std::string logPrefix_ = "scwx::qt::gl::draw::icons"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -static constexpr std::size_t kNumRectangles = 1; -static constexpr std::size_t kNumTriangles = kNumRectangles * 2; -static constexpr std::size_t kVerticesPerTriangle = 3; -static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; -static constexpr std::size_t kPointsPerVertex = 10; -static constexpr std::size_t kPointsPerTexCoord = 3; +static constexpr std::size_t kNumRectangles = 1; +static constexpr std::size_t kNumTriangles = kNumRectangles * 2; +static constexpr std::size_t kVerticesPerTriangle = 3; +static constexpr std::size_t kPointsPerVertex = 10; +static constexpr std::size_t kPointsPerTexCoord = 3; static constexpr std::size_t kIconBufferLength = kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; static constexpr std::size_t kTextureBufferLength = @@ -117,7 +116,7 @@ public: }; Icons::Icons(const std::shared_ptr& context) : - DrawItem(context->gl()), p(std::make_unique(context)) + DrawItem(), p(std::make_unique(context)) { } Icons::~Icons() = default; @@ -127,8 +126,6 @@ Icons& Icons::operator=(Icons&&) noexcept = default; void Icons::Initialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - p->shaderProgram_ = p->context_->GetShaderProgram( {{GL_VERTEX_SHADER, ":/gl/texture2d_array.vert"}, {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, @@ -136,69 +133,77 @@ void Icons::Initialize() p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); - gl.glGenVertexArrays(1, &p->vao_); - gl.glGenBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); + glGenVertexArrays(1, &p->vao_); + glGenBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); - gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindVertexArray(p->vao_); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // NOLINTBEGIN(modernize-use-nullptr) + // NOLINTBEGIN(performance-no-int-to-ptr) + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) // aVertex - gl.glVertexAttribPointer(0, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(0)); - gl.glEnableVertexAttribArray(0); + glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(0)); + glEnableVertexAttribArray(0); // aXYOffset - gl.glVertexAttribPointer(1, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(2 * sizeof(float))); - gl.glEnableVertexAttribArray(1); + glVertexAttribPointer(1, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(2 * sizeof(float))); + glEnableVertexAttribArray(1); // aModulate - gl.glVertexAttribPointer(3, - 4, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(4 * sizeof(float))); - gl.glEnableVertexAttribArray(3); + glVertexAttribPointer(3, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + glEnableVertexAttribArray(3); // aAngle - gl.glVertexAttribPointer(4, - 1, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(8 * sizeof(float))); - gl.glEnableVertexAttribArray(4); + glVertexAttribPointer(4, + 1, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(8 * sizeof(float))); + glEnableVertexAttribArray(4); // aDisplayed - gl.glVertexAttribPointer(5, - 1, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(9 * sizeof(float))); - gl.glEnableVertexAttribArray(5); + glVertexAttribPointer(5, + 1, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(9 * sizeof(float))); + glEnableVertexAttribArray(5); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); // aTexCoord - gl.glVertexAttribPointer(2, - 3, - GL_FLOAT, - GL_FALSE, - kPointsPerTexCoord * sizeof(float), - static_cast(0)); - gl.glEnableVertexAttribArray(2); + glVertexAttribPointer(2, + 3, + GL_FLOAT, + GL_FALSE, + kPointsPerTexCoord * sizeof(float), + static_cast(0)); + glEnableVertexAttribArray(2); + + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + // NOLINTEND(performance-no-int-to-ptr) + // NOLINTEND(modernize-use-nullptr) p->dirty_ = true; } @@ -220,29 +225,25 @@ void Icons::Render(const QMapLibre::CustomLayerRenderParameters& params, if (!p->currentIconList_.empty()) { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glBindVertexArray(p->vao_); + glBindVertexArray(p->vao_); p->Update(textureAtlasChanged); p->shaderProgram_->Use(); UseDefaultProjection(params, p->uMVPMatrixLocation_); // Interpolate texture coordinates - gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Draw icons - gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); } } void Icons::Deinitialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glDeleteVertexArrays(1, &p->vao_); - gl.glDeleteBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); + glDeleteVertexArrays(1, &p->vao_); + glDeleteBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); std::unique_lock lock {p->iconMutex_}; @@ -680,8 +681,6 @@ void Icons::Impl::UpdateModifiedIconBuffers() void Icons::Impl::Update(bool textureAtlasChanged) { - gl::OpenGLFunctions& gl = context_->gl(); - UpdateModifiedIconBuffers(); // If the texture atlas has changed @@ -697,11 +696,12 @@ void Icons::Impl::Update(bool textureAtlasChanged) UpdateTextureBuffer(); // Buffer texture data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(float) * textureBuffer_.size(), - textureBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(float) * textureBuffer_.size()), + textureBuffer_.data(), + GL_DYNAMIC_DRAW); lastTextureAtlasChanged_ = false; } @@ -710,11 +710,12 @@ void Icons::Impl::Update(bool textureAtlasChanged) if (dirty_) { // Buffer vertex data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(float) * currentIconBuffer_.size(), - currentIconBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(float) * currentIconBuffer_.size()), + currentIconBuffer_.data(), + GL_DYNAMIC_DRAW); numVertices_ = static_cast(currentIconBuffer_.size() / kPointsPerVertex); @@ -741,7 +742,7 @@ bool Icons::RunMousePicking( // For each pickable icon auto it = std::find_if( // - std::execution::par_unseq, + std::execution::par, p->currentHoverIcons_.crbegin(), p->currentHoverIcons_.crend(), [&mouseLocalCoords](const auto& icon) diff --git a/scwx-qt/source/scwx/qt/gl/draw/linked_vectors.cpp b/scwx-qt/source/scwx/qt/gl/draw/linked_vectors.cpp index 3dbab117..dfc3dd07 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/linked_vectors.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/linked_vectors.cpp @@ -63,14 +63,12 @@ class LinkedVectors::Impl { public: explicit Impl(std::shared_ptr context) : - context_ {context}, geoLines_ {std::make_shared(context)} + geoLines_ {std::make_shared(context)} { } ~Impl() {} - std::shared_ptr context_; - bool borderEnabled_ {true}; bool visible_ {true}; @@ -79,7 +77,7 @@ public: }; LinkedVectors::LinkedVectors(std::shared_ptr context) : - DrawItem(context->gl()), p(std::make_unique(context)) + DrawItem(), p(std::make_unique(context)) { } LinkedVectors::~LinkedVectors() = default; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp index abc852f8..21dd25c2 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -140,7 +141,7 @@ public: }; PlacefileIcons::PlacefileIcons(const std::shared_ptr& context) : - DrawItem(context->gl()), p(std::make_unique(context)) + DrawItem(), p(std::make_unique(context)) { } PlacefileIcons::~PlacefileIcons() = default; @@ -161,9 +162,6 @@ void PlacefileIcons::set_thresholded(bool thresholded) void PlacefileIcons::Initialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - auto& gl30 = p->context_->gl30(); - p->shaderProgram_ = p->context_->GetShaderProgram( {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, @@ -178,82 +176,90 @@ void PlacefileIcons::Initialize() p->uSelectedTimeLocation_ = p->shaderProgram_->GetUniformLocation("uSelectedTime"); - gl.glGenVertexArrays(1, &p->vao_); - gl.glGenBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); + glGenVertexArrays(1, &p->vao_); + glGenBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); - gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindVertexArray(p->vao_); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // NOLINTBEGIN(modernize-use-nullptr) + // NOLINTBEGIN(performance-no-int-to-ptr) + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) // aLatLong - gl.glVertexAttribPointer(0, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - static_cast(0)); - gl.glEnableVertexAttribArray(0); + glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + static_cast(0)); + glEnableVertexAttribArray(0); // aXYOffset - gl.glVertexAttribPointer(1, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(2 * sizeof(float))); - gl.glEnableVertexAttribArray(1); + glVertexAttribPointer(1, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(2 * sizeof(float))); + glEnableVertexAttribArray(1); // aModulate - gl.glVertexAttribPointer(3, - 4, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(4 * sizeof(float))); - gl.glEnableVertexAttribArray(3); + glVertexAttribPointer(3, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + glEnableVertexAttribArray(3); // aAngle - gl.glVertexAttribPointer(4, - 1, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(8 * sizeof(float))); - gl.glEnableVertexAttribArray(4); + glVertexAttribPointer(4, + 1, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(8 * sizeof(float))); + glEnableVertexAttribArray(4); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); // aTexCoord - gl.glVertexAttribPointer(2, - 3, - GL_FLOAT, - GL_FALSE, - kPointsPerTexCoord * sizeof(float), - static_cast(0)); - gl.glEnableVertexAttribArray(2); + glVertexAttribPointer(2, + 3, + GL_FLOAT, + GL_FALSE, + kPointsPerTexCoord * sizeof(float), + static_cast(0)); + glEnableVertexAttribArray(2); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[2]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[2]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); // aThreshold - gl.glVertexAttribIPointer(5, // - 1, - GL_INT, - 0, - static_cast(0)); - gl.glEnableVertexAttribArray(5); + glVertexAttribIPointer(5, // + 1, + GL_INT, + 0, + static_cast(0)); + glEnableVertexAttribArray(5); // aTimeRange - gl.glVertexAttribIPointer(6, // - 2, - GL_INT, - kIntegersPerVertex_ * sizeof(GLint), - reinterpret_cast(1 * sizeof(GLint))); - gl.glEnableVertexAttribArray(6); + glVertexAttribIPointer(6, // + 2, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + reinterpret_cast(1 * sizeof(GLint))); + glEnableVertexAttribArray(6); // aDisplayed - gl30.glVertexAttribI1i(7, 1); + glVertexAttribI1i(7, 1); + + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + // NOLINTEND(performance-no-int-to-ptr) + // NOLINTEND(modernize-use-nullptr) p->dirty_ = true; } @@ -266,9 +272,7 @@ void PlacefileIcons::Render( if (!p->currentIconList_.empty()) { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glBindVertexArray(p->vao_); + glBindVertexArray(p->vao_); p->Update(textureAtlasChanged); p->shaderProgram_->Use(); @@ -281,40 +285,38 @@ void PlacefileIcons::Render( // If thresholding is enabled, set the map distance units::length::nautical_miles mapDistance = util::maplibre::GetMapDistance(params); - gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); + glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); } else { // If thresholding is disabled, set the map distance to 0 - gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); + glUniform1f(p->uMapDistanceLocation_, 0.0f); } // Selected time std::chrono::system_clock::time_point selectedTime = (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? - std::chrono::system_clock::now() : + scwx::util::time::now() : p->selectedTime_; - gl.glUniform1i( + glUniform1i( p->uSelectedTimeLocation_, static_cast(std::chrono::duration_cast( selectedTime.time_since_epoch()) .count())); // Interpolate texture coordinates - gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Draw icons - gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); } } void PlacefileIcons::Deinitialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glDeleteVertexArrays(1, &p->vao_); - gl.glDeleteBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); + glDeleteVertexArrays(1, &p->vao_); + glDeleteBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); std::unique_lock lock {p->iconMutex_}; @@ -642,8 +644,6 @@ void PlacefileIcons::Impl::UpdateTextureBuffer() void PlacefileIcons::Impl::Update(bool textureAtlasChanged) { - gl::OpenGLFunctions& gl = context_->gl(); - // If the texture atlas has changed if (dirty_ || textureAtlasChanged) { @@ -657,29 +657,32 @@ void PlacefileIcons::Impl::Update(bool textureAtlasChanged) UpdateTextureBuffer(); // Buffer texture data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(float) * textureBuffer_.size(), - textureBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(float) * textureBuffer_.size()), + textureBuffer_.data(), + GL_DYNAMIC_DRAW); } // If buffers need updating if (dirty_) { // Buffer vertex data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(float) * currentIconBuffer_.size(), - currentIconBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(float) * currentIconBuffer_.size()), + currentIconBuffer_.data(), + GL_DYNAMIC_DRAW); // Buffer threshold data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[2]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLint) * currentIntegerBuffer_.size(), - currentIntegerBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[2]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(GLint) * currentIntegerBuffer_.size()), + currentIntegerBuffer_.data(), + GL_DYNAMIC_DRAW); numVertices_ = static_cast(currentIconBuffer_.size() / kPointsPerVertex); @@ -718,7 +721,7 @@ bool PlacefileIcons::RunMousePicking( // If no time has been selected, use the current time std::chrono::system_clock::time_point selectedTime = (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? - std::chrono::system_clock::now() : + scwx::util::time::now() : p->selectedTime_; // For each pickable icon @@ -737,8 +740,10 @@ bool PlacefileIcons::RunMousePicking( units::length::nautical_miles {icon.di_->threshold_} .value())) < 999 && - // Map distance is beyond the threshold - icon.di_->threshold_ < mapDistance) || + // Map distance is beyond/within the threshold + icon.di_->threshold_ < mapDistance && + (icon.di_->threshold_.value() >= 0.0 || + -(icon.di_->threshold_) > mapDistance)) || ( // Line has a start time diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp index aafaef8d..b14a2723 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_images.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -117,7 +118,7 @@ public: }; PlacefileImages::PlacefileImages(const std::shared_ptr& context) : - DrawItem(context->gl()), p(std::make_unique(context)) + DrawItem(), p(std::make_unique(context)) { } PlacefileImages::~PlacefileImages() = default; @@ -139,9 +140,6 @@ void PlacefileImages::set_thresholded(bool thresholded) void PlacefileImages::Initialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - auto& gl30 = p->context_->gl30(); - p->shaderProgram_ = p->context_->GetShaderProgram( {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, @@ -156,73 +154,81 @@ void PlacefileImages::Initialize() p->uSelectedTimeLocation_ = p->shaderProgram_->GetUniformLocation("uSelectedTime"); - gl.glGenVertexArrays(1, &p->vao_); - gl.glGenBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); + glGenVertexArrays(1, &p->vao_); + glGenBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); - gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindVertexArray(p->vao_); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // NOLINTBEGIN(modernize-use-nullptr) + // NOLINTBEGIN(performance-no-int-to-ptr) + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) // aLatLong - gl.glVertexAttribPointer(0, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - static_cast(0)); - gl.glEnableVertexAttribArray(0); + glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + static_cast(0)); + glEnableVertexAttribArray(0); // aXYOffset - gl.glVertexAttribPointer(1, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(2 * sizeof(float))); - gl.glEnableVertexAttribArray(1); + glVertexAttribPointer(1, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(2 * sizeof(float))); + glEnableVertexAttribArray(1); // aModulate - gl.glVertexAttribPointer(3, - 4, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(4 * sizeof(float))); - gl.glEnableVertexAttribArray(3); + glVertexAttribPointer(3, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + glEnableVertexAttribArray(3); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); // aTexCoord - gl.glVertexAttribPointer(2, - 3, - GL_FLOAT, - GL_FALSE, - kPointsPerTexCoord * sizeof(float), - static_cast(0)); - gl.glEnableVertexAttribArray(2); + glVertexAttribPointer(2, + 3, + GL_FLOAT, + GL_FALSE, + kPointsPerTexCoord * sizeof(float), + static_cast(0)); + glEnableVertexAttribArray(2); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[2]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[2]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); // aThreshold - gl.glVertexAttribIPointer(5, // - 1, - GL_INT, - kIntegersPerVertex_ * sizeof(GLint), - static_cast(0)); - gl.glEnableVertexAttribArray(5); + glVertexAttribIPointer(5, // + 1, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + static_cast(0)); + glEnableVertexAttribArray(5); // aTimeRange - gl.glVertexAttribIPointer(6, // - 2, - GL_INT, - kIntegersPerVertex_ * sizeof(GLint), - reinterpret_cast(1 * sizeof(GLint))); - gl.glEnableVertexAttribArray(6); + glVertexAttribIPointer(6, // + 2, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + reinterpret_cast(1 * sizeof(GLint))); + glEnableVertexAttribArray(6); // aDisplayed - gl30.glVertexAttribI1i(7, 1); + glVertexAttribI1i(7, 1); + + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + // NOLINTEND(performance-no-int-to-ptr) + // NOLINTEND(modernize-use-nullptr) p->dirty_ = true; } @@ -235,9 +241,7 @@ void PlacefileImages::Render( if (!p->currentImageList_.empty()) { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glBindVertexArray(p->vao_); + glBindVertexArray(p->vao_); p->Update(textureAtlasChanged); p->shaderProgram_->Use(); @@ -250,40 +254,38 @@ void PlacefileImages::Render( // If thresholding is enabled, set the map distance units::length::nautical_miles mapDistance = util::maplibre::GetMapDistance(params); - gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); + glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); } else { // If thresholding is disabled, set the map distance to 0 - gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); + glUniform1f(p->uMapDistanceLocation_, 0.0f); } // Selected time std::chrono::system_clock::time_point selectedTime = (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? - std::chrono::system_clock::now() : + scwx::util::time::now() : p->selectedTime_; - gl.glUniform1i( + glUniform1i( p->uSelectedTimeLocation_, static_cast(std::chrono::duration_cast( selectedTime.time_since_epoch()) .count())); // Interpolate texture coordinates - gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Draw images - gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); } } void PlacefileImages::Deinitialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glDeleteVertexArrays(1, &p->vao_); - gl.glDeleteBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); + glDeleteVertexArrays(1, &p->vao_); + glDeleteBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); std::unique_lock lock {p->imageMutex_}; @@ -439,8 +441,6 @@ void PlacefileImages::Impl::UpdateTextureBuffer() void PlacefileImages::Impl::Update(bool textureAtlasChanged) { - gl::OpenGLFunctions& gl = context_->gl(); - // If the texture atlas has changed if (dirty_ || textureAtlasChanged) { @@ -454,29 +454,32 @@ void PlacefileImages::Impl::Update(bool textureAtlasChanged) UpdateTextureBuffer(); // Buffer texture data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(float) * textureBuffer_.size(), - textureBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(float) * textureBuffer_.size()), + textureBuffer_.data(), + GL_DYNAMIC_DRAW); } // If buffers need updating if (dirty_) { // Buffer vertex data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(float) * currentImageBuffer_.size(), - currentImageBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(float) * currentImageBuffer_.size()), + currentImageBuffer_.data(), + GL_DYNAMIC_DRAW); // Buffer threshold data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[2]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLint) * currentIntegerBuffer_.size(), - currentIntegerBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[2]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(GLint) * currentIntegerBuffer_.size()), + currentIntegerBuffer_.data(), + GL_DYNAMIC_DRAW); numVertices_ = static_cast(currentImageBuffer_.size() / kPointsPerVertex); diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp index 8fdce9f1..b908389c 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -18,13 +19,9 @@ namespace draw static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_lines"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -static constexpr std::size_t kNumRectangles = 1; -static constexpr std::size_t kNumTriangles = kNumRectangles * 2; static constexpr std::size_t kVerticesPerTriangle = 3; static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; static constexpr std::size_t kPointsPerVertex = 9; -static constexpr std::size_t kBufferLength = - kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; // Threshold, start time, end time static constexpr std::size_t kIntegersPerVertex_ = 3; @@ -110,7 +107,7 @@ public: }; PlacefileLines::PlacefileLines(const std::shared_ptr& context) : - DrawItem(context->gl()), p(std::make_unique(context)) + DrawItem(), p(std::make_unique(context)) { } PlacefileLines::~PlacefileLines() = default; @@ -131,9 +128,6 @@ void PlacefileLines::set_thresholded(bool thresholded) void PlacefileLines::Initialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - auto& gl30 = p->context_->gl30(); - p->shaderProgram_ = p->context_->GetShaderProgram( {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, @@ -148,70 +142,78 @@ void PlacefileLines::Initialize() p->uSelectedTimeLocation_ = p->shaderProgram_->GetUniformLocation("uSelectedTime"); - gl.glGenVertexArrays(1, &p->vao_); - gl.glGenBuffers(2, p->vbo_.data()); + glGenVertexArrays(1, &p->vao_); + glGenBuffers(2, p->vbo_.data()); - gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindVertexArray(p->vao_); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // NOLINTBEGIN(modernize-use-nullptr) + // NOLINTBEGIN(performance-no-int-to-ptr) + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) // aLatLong - gl.glVertexAttribPointer(0, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - static_cast(0)); - gl.glEnableVertexAttribArray(0); + glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + static_cast(0)); + glEnableVertexAttribArray(0); // aXYOffset - gl.glVertexAttribPointer(1, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(2 * sizeof(float))); - gl.glEnableVertexAttribArray(1); + glVertexAttribPointer(1, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(2 * sizeof(float))); + glEnableVertexAttribArray(1); // aModulate - gl.glVertexAttribPointer(3, - 4, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(4 * sizeof(float))); - gl.glEnableVertexAttribArray(3); + glVertexAttribPointer(3, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + glEnableVertexAttribArray(3); // aAngle - gl.glVertexAttribPointer(4, - 1, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(8 * sizeof(float))); - gl.glEnableVertexAttribArray(4); + glVertexAttribPointer(4, + 1, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(8 * sizeof(float))); + glEnableVertexAttribArray(4); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); // aThreshold - gl.glVertexAttribIPointer(5, // - 1, - GL_INT, - kIntegersPerVertex_ * sizeof(GLint), - static_cast(0)); - gl.glEnableVertexAttribArray(5); + glVertexAttribIPointer(5, // + 1, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + static_cast(0)); + glEnableVertexAttribArray(5); // aTimeRange - gl.glVertexAttribIPointer(6, // - 2, - GL_INT, - kIntegersPerVertex_ * sizeof(GLint), - reinterpret_cast(1 * sizeof(GLint))); - gl.glEnableVertexAttribArray(6); + glVertexAttribIPointer(6, // + 2, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + reinterpret_cast(1 * sizeof(GLint))); + glEnableVertexAttribArray(6); // aDisplayed - gl30.glVertexAttribI1i(7, 1); + glVertexAttribI1i(7, 1); + + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + // NOLINTEND(performance-no-int-to-ptr) + // NOLINTEND(modernize-use-nullptr) p->dirty_ = true; } @@ -223,9 +225,7 @@ void PlacefileLines::Render( if (p->currentNumLines_ > 0) { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glBindVertexArray(p->vao_); + glBindVertexArray(p->vao_); p->Update(); p->shaderProgram_->Use(); @@ -238,36 +238,34 @@ void PlacefileLines::Render( // If thresholding is enabled, set the map distance units::length::nautical_miles mapDistance = util::maplibre::GetMapDistance(params); - gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); + glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); } else { // If thresholding is disabled, set the map distance to 0 - gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); + glUniform1f(p->uMapDistanceLocation_, 0.0f); } // Selected time std::chrono::system_clock::time_point selectedTime = (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? - std::chrono::system_clock::now() : + scwx::util::time::now() : p->selectedTime_; - gl.glUniform1i( + glUniform1i( p->uSelectedTimeLocation_, static_cast(std::chrono::duration_cast( selectedTime.time_since_epoch()) .count())); // Draw icons - gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); } } void PlacefileLines::Deinitialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glDeleteVertexArrays(1, &p->vao_); - gl.glDeleteBuffers(2, p->vbo_.data()); + glDeleteVertexArrays(1, &p->vao_); + glDeleteBuffers(2, p->vbo_.data()); std::unique_lock lock {p->lineMutex_}; @@ -479,21 +477,21 @@ void PlacefileLines::Impl::Update() // If the placefile has been updated if (dirty_) { - gl::OpenGLFunctions& gl = context_->gl(); - // Buffer lines data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(float) * currentLinesBuffer_.size(), - currentLinesBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(float) * currentLinesBuffer_.size()), + currentLinesBuffer_.data(), + GL_DYNAMIC_DRAW); // Buffer threshold data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLint) * currentIntegerBuffer_.size(), - currentIntegerBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(GLint) * currentIntegerBuffer_.size()), + currentIntegerBuffer_.data(), + GL_DYNAMIC_DRAW); } dirty_ = false; @@ -529,7 +527,7 @@ bool PlacefileLines::RunMousePicking( // If no time has been selected, use the current time std::chrono::system_clock::time_point selectedTime = (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? - std::chrono::system_clock::now() : + scwx::util::time::now() : p->selectedTime_; // For each pickable line @@ -548,8 +546,10 @@ bool PlacefileLines::RunMousePicking( units::length::nautical_miles {line.di_->threshold_} .value())) < 999 && - // Map distance is beyond the threshold - line.di_->threshold_ < mapDistance) || + // Map distance is beyond/within the threshold + line.di_->threshold_ < mapDistance && + (line.di_->threshold_.value() >= 0.0 || + -(line.di_->threshold_) > mapDistance)) || ( // Line has a start time diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp index 2944fa20..a30991f9 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp @@ -1,13 +1,19 @@ #include #include #include +#include #include -#include #include -#if defined(_WIN32) +#if !defined(__APPLE__) +# include +#else +# include +#endif + +#if defined(_WIN32) || defined(__APPLE__) typedef void (*_GLUfuncptr)(void); #endif @@ -31,7 +37,6 @@ static constexpr std::size_t kIntegersPerVertex_ = 3; static constexpr std::size_t kTessVertexScreenX_ = 0; static constexpr std::size_t kTessVertexScreenY_ = 1; -static constexpr std::size_t kTessVertexScreenZ_ = 2; static constexpr std::size_t kTessVertexXOffset_ = 3; static constexpr std::size_t kTessVertexYOffset_ = 4; static constexpr std::size_t kTessVertexR_ = 5; @@ -126,7 +131,7 @@ public: PlacefilePolygons::PlacefilePolygons( const std::shared_ptr& context) : - DrawItem(context->gl()), p(std::make_unique(context)) + DrawItem(), p(std::make_unique(context)) { } PlacefilePolygons::~PlacefilePolygons() = default; @@ -148,8 +153,6 @@ void PlacefilePolygons::set_thresholded(bool thresholded) void PlacefilePolygons::Initialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - p->shaderProgram_ = p->context_->GetShaderProgram( {{GL_VERTEX_SHADER, ":/gl/map_color.vert"}, {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, @@ -164,58 +167,66 @@ void PlacefilePolygons::Initialize() p->uSelectedTimeLocation_ = p->shaderProgram_->GetUniformLocation("uSelectedTime"); - gl.glGenVertexArrays(1, &p->vao_); - gl.glGenBuffers(2, p->vbo_.data()); + glGenVertexArrays(1, &p->vao_); + glGenBuffers(2, p->vbo_.data()); - gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindVertexArray(p->vao_); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // NOLINTBEGIN(modernize-use-nullptr) + // NOLINTBEGIN(performance-no-int-to-ptr) + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) // aScreenCoord - gl.glVertexAttribPointer(0, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - static_cast(0)); - gl.glEnableVertexAttribArray(0); + glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + static_cast(0)); + glEnableVertexAttribArray(0); // aXYOffset - gl.glVertexAttribPointer(1, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(2 * sizeof(float))); - gl.glEnableVertexAttribArray(1); + glVertexAttribPointer(1, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(2 * sizeof(float))); + glEnableVertexAttribArray(1); // aColor - gl.glVertexAttribPointer(2, - 4, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(4 * sizeof(float))); - gl.glEnableVertexAttribArray(2); + glVertexAttribPointer(2, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + glEnableVertexAttribArray(2); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); // aThreshold - gl.glVertexAttribIPointer(3, // - 1, - GL_INT, - kIntegersPerVertex_ * sizeof(GLint), - static_cast(0)); - gl.glEnableVertexAttribArray(3); + glVertexAttribIPointer(3, // + 1, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + static_cast(0)); + glEnableVertexAttribArray(3); // aTimeRange - gl.glVertexAttribIPointer(4, // - 2, - GL_INT, - kIntegersPerVertex_ * sizeof(GLint), - reinterpret_cast(1 * sizeof(GLint))); - gl.glEnableVertexAttribArray(4); + glVertexAttribIPointer(4, // + 2, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + reinterpret_cast(1 * sizeof(GLint))); + glEnableVertexAttribArray(4); + + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + // NOLINTEND(performance-no-int-to-ptr) + // NOLINTEND(modernize-use-nullptr) p->dirty_ = true; } @@ -225,9 +236,7 @@ void PlacefilePolygons::Render( { if (!p->currentBuffer_.empty()) { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glBindVertexArray(p->vao_); + glBindVertexArray(p->vao_); p->Update(); p->shaderProgram_->Use(); @@ -240,36 +249,34 @@ void PlacefilePolygons::Render( // If thresholding is enabled, set the map distance units::length::nautical_miles mapDistance = util::maplibre::GetMapDistance(params); - gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); + glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); } else { // If thresholding is disabled, set the map distance to 0 - gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); + glUniform1f(p->uMapDistanceLocation_, 0.0f); } // Selected time std::chrono::system_clock::time_point selectedTime = (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? - std::chrono::system_clock::now() : + scwx::util::time::now() : p->selectedTime_; - gl.glUniform1i( + glUniform1i( p->uSelectedTimeLocation_, static_cast(std::chrono::duration_cast( selectedTime.time_since_epoch()) .count())); // Draw icons - gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); } } void PlacefilePolygons::Deinitialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glDeleteVertexArrays(1, &p->vao_); - gl.glDeleteBuffers(2, p->vbo_.data()); + glDeleteVertexArrays(1, &p->vao_); + glDeleteBuffers(2, p->vbo_.data()); std::unique_lock lock {p->bufferMutex_}; @@ -314,23 +321,23 @@ void PlacefilePolygons::Impl::Update() { if (dirty_) { - gl::OpenGLFunctions& gl = context_->gl(); - std::unique_lock lock {bufferMutex_}; // Buffer vertex data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLfloat) * currentBuffer_.size(), - currentBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(GLfloat) * currentBuffer_.size()), + currentBuffer_.data(), + GL_DYNAMIC_DRAW); // Buffer threshold data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLint) * currentIntegerBuffer_.size(), - currentIntegerBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(GLint) * currentIntegerBuffer_.size()), + currentIntegerBuffer_.data(), + GL_DYNAMIC_DRAW); numVertices_ = static_cast(currentBuffer_.size() / kPointsPerVertex); diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp index b8faa8f8..0ce6ab47 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp @@ -1,22 +1,16 @@ #include #include -#include #include #include #include #include +#include #include #include #include -namespace scwx -{ -namespace qt -{ -namespace gl -{ -namespace draw +namespace scwx::qt::gl::draw { static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_text"; @@ -25,13 +19,16 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class PlacefileText::Impl { public: - explicit Impl(const std::shared_ptr& context, - const std::string& placefileName) : - context_ {context}, placefileName_ {placefileName} + explicit Impl(std::string placefileName) : + placefileName_ {std::move(placefileName)} { } + ~Impl() = default; - ~Impl() {} + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; void RenderTextDrawItem( const QMapLibre::CustomLayerRenderParameters& params, @@ -43,8 +40,6 @@ public: float x, float y); - std::shared_ptr context_; - std::string placefileName_; bool thresholded_ {false}; @@ -66,13 +61,16 @@ public: std::vector> textList_ {}; std::vector> newList_ {}; - std::vector> fonts_ {}; - std::vector> newFonts_ {}; + std::vector, + units::font_size::pixels>> + fonts_ {}; + std::vector, + units::font_size::pixels>> + newFonts_ {}; }; -PlacefileText::PlacefileText(const std::shared_ptr& context, - const std::string& placefileName) : - DrawItem(context->gl()), p(std::make_unique(context, placefileName)) +PlacefileText::PlacefileText(const std::string& placefileName) : + DrawItem(), p(std::make_unique(placefileName)) { } PlacefileText::~PlacefileText() = default; @@ -133,10 +131,14 @@ void PlacefileText::Impl::RenderTextDrawItem( // If no time has been selected, use the current time std::chrono::system_clock::time_point selectedTime = (selectedTime_ == std::chrono::system_clock::time_point {}) ? - std::chrono::system_clock::now() : + scwx::util::time::now() : selectedTime_; - if ((!thresholded_ || mapDistance_ <= di->threshold_) && + const bool thresholdMet = + !thresholded_ || mapDistance_ <= di->threshold_ || + (di->threshold_.value() < 0.0 && mapDistance_ >= -(di->threshold_)); + + if (thresholdMet && (di->startTime_ == std::chrono::system_clock::time_point {} || (di->startTime_ <= selectedTime && selectedTime < di->endTime_))) { @@ -160,7 +162,8 @@ void PlacefileText::Impl::RenderTextDrawItem( std::size_t fontNumber = std::clamp(di->fontNumber_, 0, 8); // Set the font for the drop shadow and text - ImGui::PushFont(fonts_[fontNumber]->font()); + ImGui::PushFont(fonts_[fontNumber].first->font(), + fonts_[fontNumber].second.value()); if (settings::TextSettings::Instance() .placefile_text_drop_shadow_enabled() @@ -262,9 +265,7 @@ void PlacefileText::StartText() p->newList_.clear(); } -void PlacefileText::SetFonts( - const boost::unordered_flat_map>& fonts) +void PlacefileText::SetFonts(const manager::PlacefileManager::FontMap& fonts) { auto defaultFont = manager::FontManager::Instance().GetImGuiFont( types::FontCategory::Default); @@ -306,7 +307,4 @@ void PlacefileText::FinishText() p->newFonts_.clear(); } -} // namespace draw -} // namespace gl -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::gl::draw diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp index 4cbaf0af..e4e46755 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -19,8 +20,7 @@ namespace draw class PlacefileText : public DrawItem { public: - explicit PlacefileText(const std::shared_ptr& context, - const std::string& placefileName); + explicit PlacefileText(const std::string& placefileName); ~PlacefileText(); PlacefileText(const PlacefileText&) = delete; @@ -55,10 +55,7 @@ public: * * @param [in] fonts A map of ImGui fonts */ - void - SetFonts(const boost::unordered_flat_map>& - fonts); + void SetFonts(const manager::PlacefileManager::FontMap& fonts); /** * Adds placefile text to the internal draw list. diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp index 0b5f9c30..222713ae 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include @@ -74,7 +75,7 @@ public: PlacefileTriangles::PlacefileTriangles( const std::shared_ptr& context) : - DrawItem(context->gl()), p(std::make_unique(context)) + DrawItem(), p(std::make_unique(context)) { } PlacefileTriangles::~PlacefileTriangles() = default; @@ -96,8 +97,6 @@ void PlacefileTriangles::set_thresholded(bool thresholded) void PlacefileTriangles::Initialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - p->shaderProgram_ = p->context_->GetShaderProgram( {{GL_VERTEX_SHADER, ":/gl/map_color.vert"}, {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, @@ -112,58 +111,66 @@ void PlacefileTriangles::Initialize() p->uSelectedTimeLocation_ = p->shaderProgram_->GetUniformLocation("uSelectedTime"); - gl.glGenVertexArrays(1, &p->vao_); - gl.glGenBuffers(2, p->vbo_.data()); + glGenVertexArrays(1, &p->vao_); + glGenBuffers(2, p->vbo_.data()); - gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindVertexArray(p->vao_); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // NOLINTBEGIN(modernize-use-nullptr) + // NOLINTBEGIN(performance-no-int-to-ptr) + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) // aScreenCoord - gl.glVertexAttribPointer(0, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - static_cast(0)); - gl.glEnableVertexAttribArray(0); + glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + static_cast(0)); + glEnableVertexAttribArray(0); // aXYOffset - gl.glVertexAttribPointer(1, - 2, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(2 * sizeof(float))); - gl.glEnableVertexAttribArray(1); + glVertexAttribPointer(1, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(2 * sizeof(float))); + glEnableVertexAttribArray(1); // aColor - gl.glVertexAttribPointer(2, - 4, - GL_FLOAT, - GL_FALSE, - kPointsPerVertex * sizeof(float), - reinterpret_cast(4 * sizeof(float))); - gl.glEnableVertexAttribArray(2); + glVertexAttribPointer(2, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + glEnableVertexAttribArray(2); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); // aThreshold - gl.glVertexAttribIPointer(3, // - 1, - GL_INT, - kIntegersPerVertex_ * sizeof(GLint), - static_cast(0)); - gl.glEnableVertexAttribArray(3); + glVertexAttribIPointer(3, // + 1, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + static_cast(0)); + glEnableVertexAttribArray(3); // aTimeRange - gl.glVertexAttribIPointer(4, // - 2, - GL_INT, - kIntegersPerVertex_ * sizeof(GLint), - reinterpret_cast(1 * sizeof(GLint))); - gl.glEnableVertexAttribArray(4); + glVertexAttribIPointer(4, // + 2, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + reinterpret_cast(1 * sizeof(GLint))); + glEnableVertexAttribArray(4); + + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + // NOLINTEND(performance-no-int-to-ptr) + // NOLINTEND(modernize-use-nullptr) p->dirty_ = true; } @@ -173,9 +180,7 @@ void PlacefileTriangles::Render( { if (!p->currentBuffer_.empty()) { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glBindVertexArray(p->vao_); + glBindVertexArray(p->vao_); p->Update(); p->shaderProgram_->Use(); @@ -188,36 +193,34 @@ void PlacefileTriangles::Render( // If thresholding is enabled, set the map distance units::length::nautical_miles mapDistance = util::maplibre::GetMapDistance(params); - gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); + glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); } else { // If thresholding is disabled, set the map distance to 0 - gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); + glUniform1f(p->uMapDistanceLocation_, 0.0f); } // Selected time std::chrono::system_clock::time_point selectedTime = (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? - std::chrono::system_clock::now() : + scwx::util::time::now() : p->selectedTime_; - gl.glUniform1i( + glUniform1i( p->uSelectedTimeLocation_, static_cast(std::chrono::duration_cast( selectedTime.time_since_epoch()) .count())); // Draw icons - gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); } } void PlacefileTriangles::Deinitialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glDeleteVertexArrays(1, &p->vao_); - gl.glDeleteBuffers(2, p->vbo_.data()); + glDeleteVertexArrays(1, &p->vao_); + glDeleteBuffers(2, p->vbo_.data()); std::unique_lock lock {p->bufferMutex_}; @@ -320,23 +323,23 @@ void PlacefileTriangles::Impl::Update() { if (dirty_) { - gl::OpenGLFunctions& gl = context_->gl(); - std::unique_lock lock {bufferMutex_}; // Buffer vertex data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLfloat) * currentBuffer_.size(), - currentBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(GLfloat) * currentBuffer_.size()), + currentBuffer_.data(), + GL_DYNAMIC_DRAW); // Buffer threshold data - gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(GLint) * currentIntegerBuffer_.size(), - currentIntegerBuffer_.data(), - GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + glBufferData( + GL_ARRAY_BUFFER, + static_cast(sizeof(GLint) * currentIntegerBuffer_.size()), + currentIntegerBuffer_.data(), + GL_DYNAMIC_DRAW); numVertices_ = static_cast(currentBuffer_.size() / kPointsPerVertex); diff --git a/scwx-qt/source/scwx/qt/gl/draw/rectangle.cpp b/scwx-qt/source/scwx/qt/gl/draw/rectangle.cpp index 800e199f..d86ba163 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/rectangle.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/rectangle.cpp @@ -3,13 +3,7 @@ #include -namespace scwx -{ -namespace qt -{ -namespace gl -{ -namespace draw +namespace scwx::qt::gl::draw { static const std::string logPrefix_ = "scwx::qt::gl::draw::rectangle"; @@ -27,7 +21,7 @@ class Rectangle::Impl { public: explicit Impl(std::shared_ptr context) : - context_ {context}, + context_ {std::move(context)}, dirty_ {false}, visible_ {true}, x_ {0.0f}, @@ -44,8 +38,12 @@ public: vbo_ {GL_INVALID_INDEX} { } + ~Impl() = default; - ~Impl() {} + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; std::shared_ptr context_; @@ -73,7 +71,7 @@ public: }; Rectangle::Rectangle(std::shared_ptr context) : - DrawItem(context->gl()), p(std::make_unique(context)) + DrawItem(), p(std::make_unique(context)) { } Rectangle::~Rectangle() = default; @@ -83,41 +81,45 @@ Rectangle& Rectangle::operator=(Rectangle&&) noexcept = default; void Rectangle::Initialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - p->shaderProgram_ = p->context_->GetShaderProgram(":/gl/color.vert", ":/gl/color.frag"); p->uMVPMatrixLocation_ = - gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); + glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); if (p->uMVPMatrixLocation_ == -1) { logger_->warn("Could not find uMVPMatrix"); } - gl.glGenVertexArrays(1, &p->vao_); - gl.glGenBuffers(1, &p->vbo_); + glGenVertexArrays(1, &p->vao_); + glGenBuffers(1, &p->vbo_); - gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); - gl.glBufferData( + glBindVertexArray(p->vao_); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); + glBufferData( GL_ARRAY_BUFFER, sizeof(float) * BUFFER_LENGTH, nullptr, GL_DYNAMIC_DRAW); - gl.glVertexAttribPointer(0, - 3, - GL_FLOAT, - GL_FALSE, - POINTS_PER_VERTEX * sizeof(float), - static_cast(0)); - gl.glEnableVertexAttribArray(0); + // NOLINTBEGIN(modernize-use-nullptr) + // NOLINTBEGIN(performance-no-int-to-ptr) - gl.glVertexAttribPointer(1, - 4, - GL_FLOAT, - GL_FALSE, - POINTS_PER_VERTEX * sizeof(float), - reinterpret_cast(3 * sizeof(float))); - gl.glEnableVertexAttribArray(1); + glVertexAttribPointer(0, + 3, + GL_FLOAT, + GL_FALSE, + POINTS_PER_VERTEX * sizeof(float), + static_cast(0)); + glEnableVertexAttribArray(0); + + glVertexAttribPointer(1, + 4, + GL_FLOAT, + GL_FALSE, + POINTS_PER_VERTEX * sizeof(float), + reinterpret_cast(3 * sizeof(float))); + glEnableVertexAttribArray(1); + + // NOLINTEND(performance-no-int-to-ptr) + // NOLINTEND(modernize-use-nullptr) p->dirty_ = true; } @@ -126,10 +128,8 @@ void Rectangle::Render(const QMapLibre::CustomLayerRenderParameters& params) { if (p->visible_) { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); + glBindVertexArray(p->vao_); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); p->Update(); p->shaderProgram_->Use(); @@ -138,23 +138,23 @@ void Rectangle::Render(const QMapLibre::CustomLayerRenderParameters& params) if (p->fillColor_.has_value()) { // Draw fill - gl.glDrawArrays(GL_TRIANGLES, 24, 6); + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) + glDrawArrays(GL_TRIANGLES, 24, 6); } if (p->borderWidth_ > 0.0f) { // Draw border - gl.glDrawArrays(GL_TRIANGLES, 0, 24); + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) + glDrawArrays(GL_TRIANGLES, 0, 24); } } } void Rectangle::Deinitialize() { - gl::OpenGLFunctions& gl = p->context_->gl(); - - gl.glDeleteVertexArrays(1, &p->vao_); - gl.glDeleteBuffers(1, &p->vbo_); + glDeleteVertexArrays(1, &p->vao_); + glDeleteBuffers(1, &p->vbo_); } void Rectangle::SetBorder(float width, boost::gil::rgba8_pixel_t color) @@ -206,8 +206,6 @@ void Rectangle::Impl::Update() { if (dirty_) { - gl::OpenGLFunctions& gl = context_->gl(); - const float lox = x_; const float rox = x_ + width_; const float boy = y_; @@ -289,16 +287,13 @@ void Rectangle::Impl::Update() {lox, toy, z_, fc0, fc1, fc2, fc3} // TL }}; - gl.glBufferData(GL_ARRAY_BUFFER, - sizeof(float) * BUFFER_LENGTH, - buffer, - GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, + sizeof(float) * BUFFER_LENGTH, + static_cast(buffer), + GL_DYNAMIC_DRAW); dirty_ = false; } } -} // namespace draw -} // namespace gl -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::gl::draw diff --git a/scwx-qt/source/scwx/qt/gl/gl.hpp b/scwx-qt/source/scwx/qt/gl/gl.hpp index e87454c8..ef5d0053 100644 --- a/scwx-qt/source/scwx/qt/gl/gl.hpp +++ b/scwx-qt/source/scwx/qt/gl/gl.hpp @@ -1,25 +1,12 @@ #pragma once -#include +#include #define SCWX_GL_CHECK_ERROR() \ { \ GLenum err; \ - while ((err = gl.glGetError()) != GL_NO_ERROR) \ + while ((err = glGetError()) != GL_NO_ERROR) \ { \ logger_->error("GL Error: {}, {}: {}", err, __FILE__, __LINE__); \ } \ } - -namespace scwx -{ -namespace qt -{ -namespace gl -{ - -using OpenGLFunctions = QOpenGLFunctions_3_3_Core; - -} -} // namespace qt -} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/gl_context.cpp b/scwx-qt/source/scwx/qt/gl/gl_context.cpp index d927aef3..8cdd08b4 100644 --- a/scwx-qt/source/scwx/qt/gl/gl_context.cpp +++ b/scwx-qt/source/scwx/qt/gl/gl_context.cpp @@ -3,45 +3,38 @@ #include #include +#include -namespace scwx -{ -namespace qt -{ -namespace gl +namespace scwx::qt::gl { static const std::string logPrefix_ = "scwx::qt::gl::gl_context"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class GlContext::Impl { public: - explicit Impl() : - gl_ {}, - shaderProgramMap_ {}, - shaderProgramMutex_ {}, - textureAtlas_ {GL_INVALID_INDEX}, - textureMutex_ {} - { - } - ~Impl() {} + explicit Impl() = default; + ~Impl() = default; + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; void InitializeGL(); static std::size_t GetShaderKey(std::initializer_list> shaders); - gl::OpenGLFunctions gl_; - QOpenGLFunctions_3_0 gl30_; - bool glInitialized_ {false}; std::unordered_map> - shaderProgramMap_; - std::mutex shaderProgramMutex_; + shaderProgramMap_ {}; + std::mutex shaderProgramMutex_ {}; - GLuint textureAtlas_; - std::mutex textureMutex_; + GLuint textureAtlas_ {GL_INVALID_INDEX}; + std::mutex textureMutex_ {}; std::uint64_t textureBufferCount_ {}; }; @@ -52,16 +45,6 @@ GlContext::~GlContext() = default; GlContext::GlContext(GlContext&&) noexcept = default; GlContext& GlContext::operator=(GlContext&&) noexcept = default; -gl::OpenGLFunctions& GlContext::gl() -{ - return p->gl_; -} - -QOpenGLFunctions_3_0& GlContext::gl30() -{ - return p->gl30_; -} - std::uint64_t GlContext::texture_buffer_count() const { return p->textureBufferCount_; @@ -74,10 +57,54 @@ void GlContext::Impl::InitializeGL() return; } - gl_.initializeOpenGLFunctions(); - gl30_.initializeOpenGLFunctions(); + const int gladVersion = gladLoaderLoadGL(); + if (!gladVersion) + { + logger_->error("gladLoaderLoadGL failed"); - gl_.glGenTextures(1, &textureAtlas_); + QMessageBox::critical( + nullptr, "Supercell Wx", "Unable to initialize OpenGL"); + + throw std::runtime_error("Unable to initialize OpenGL"); + } + + logger_->info("GLAD initialization complete: OpenGL {}.{}", + GLAD_VERSION_MAJOR(gladVersion), + GLAD_VERSION_MINOR(gladVersion)); + + auto glVersion = reinterpret_cast(glGetString(GL_VERSION)); + auto glVendor = reinterpret_cast(glGetString(GL_VENDOR)); + auto glRenderer = reinterpret_cast(glGetString(GL_RENDERER)); + + logger_->info("OpenGL Version: {}", glVersion); + logger_->info("OpenGL Vendor: {}", glVendor); + logger_->info("OpenGL Renderer: {}", glRenderer); + + // Get OpenGL version + GLint major = 0; + GLint minor = 0; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + + if (major < 3 || (major == 3 && minor < 3) || !GLAD_GL_VERSION_3_3) + { + logger_->error( + "OpenGL 3.3 or greater is required, found {}.{}", major, minor); + + QMessageBox::critical( + nullptr, + "Supercell Wx", + QString("OpenGL 3.3 or greater is required, found %1.%2\n\n%3\n%4\n%5") + .arg(major) + .arg(minor) + .arg(glVersion) + .arg(glVendor) + .arg(glRenderer)); + + throw std::runtime_error("OpenGL version too low"); + } + + glGenTextures(1, &textureAtlas_); glInitialized_ = true; } @@ -102,7 +129,7 @@ std::shared_ptr GlContext::GetShaderProgram( if (it == p->shaderProgramMap_.end()) { - shaderProgram = std::make_shared(p->gl_); + shaderProgram = std::make_shared(); shaderProgram->Load(shaders); p->shaderProgramMap_[key] = shaderProgram; } @@ -125,7 +152,7 @@ GLuint GlContext::GetTextureAtlas() if (p->textureBufferCount_ != textureAtlas.BuildCount()) { p->textureBufferCount_ = textureAtlas.BuildCount(); - textureAtlas.BufferAtlas(p->gl_, p->textureAtlas_); + textureAtlas.BufferAtlas(p->textureAtlas_); } return p->textureAtlas_; @@ -138,10 +165,8 @@ void GlContext::Initialize() void GlContext::StartFrame() { - auto& gl = p->gl_; - - gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - gl.glClear(GL_COLOR_BUFFER_BIT); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); } std::size_t GlContext::Impl::GetShaderKey( @@ -156,6 +181,4 @@ std::size_t GlContext::Impl::GetShaderKey( return seed; } -} // namespace gl -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::gl diff --git a/scwx-qt/source/scwx/qt/gl/gl_context.hpp b/scwx-qt/source/scwx/qt/gl/gl_context.hpp index b4a6a866..1af68b1f 100644 --- a/scwx-qt/source/scwx/qt/gl/gl_context.hpp +++ b/scwx-qt/source/scwx/qt/gl/gl_context.hpp @@ -3,8 +3,6 @@ #include #include -#include - namespace scwx { namespace qt @@ -24,9 +22,6 @@ public: GlContext(GlContext&&) noexcept; GlContext& operator=(GlContext&&) noexcept; - gl::OpenGLFunctions& gl(); - QOpenGLFunctions_3_0& gl30(); - std::uint64_t texture_buffer_count() const; std::shared_ptr diff --git a/scwx-qt/source/scwx/qt/gl/shader_program.cpp b/scwx-qt/source/scwx/qt/gl/shader_program.cpp index 4da07a32..e1b791c0 100644 --- a/scwx-qt/source/scwx/qt/gl/shader_program.cpp +++ b/scwx-qt/source/scwx/qt/gl/shader_program.cpp @@ -2,12 +2,9 @@ #include #include +#include -namespace scwx -{ -namespace qt -{ -namespace gl +namespace scwx::qt::gl { static const std::string logPrefix_ = "scwx::qt::gl::shader_program"; @@ -23,29 +20,25 @@ static const std::unordered_map kShaderNames_ { class ShaderProgram::Impl { public: - explicit Impl(OpenGLFunctions& gl) : gl_(gl), id_ {GL_INVALID_INDEX} - { - // Create shader program - id_ = gl_.glCreateProgram(); - } + explicit Impl() : id_ {glCreateProgram()} {} ~Impl() { // Delete shader program - gl_.glDeleteProgram(id_); + glDeleteProgram(id_); } - static std::string ShaderName(GLenum type); + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; - OpenGLFunctions& gl_; + static std::string ShaderName(GLenum type); GLuint id_; }; -ShaderProgram::ShaderProgram(OpenGLFunctions& gl) : - p(std::make_unique(gl)) -{ -} +ShaderProgram::ShaderProgram() : p(std::make_unique()) {} ShaderProgram::~ShaderProgram() = default; ShaderProgram::ShaderProgram(ShaderProgram&&) noexcept = default; @@ -58,7 +51,7 @@ GLuint ShaderProgram::id() const GLint ShaderProgram::GetUniformLocation(const std::string& name) { - GLint location = p->gl_.glGetUniformLocation(p->id_, name.c_str()); + const GLint location = glGetUniformLocation(p->id_, name.c_str()); if (location == -1) { logger_->warn("Could not find {}", name); @@ -88,8 +81,6 @@ bool ShaderProgram::Load( { logger_->debug("Load()"); - OpenGLFunctions& gl = p->gl_; - GLint glSuccess; bool success = true; char infoLog[kInfoLogBufSize]; @@ -120,16 +111,17 @@ bool ShaderProgram::Load( const char* shaderSourceC = shaderSource.c_str(); // Create a shader - GLuint shaderId = gl.glCreateShader(shader.first); + const GLuint shaderId = glCreateShader(shader.first); shaderIds.push_back(shaderId); // Attach the shader source code and compile the shader - gl.glShaderSource(shaderId, 1, &shaderSourceC, NULL); - gl.glCompileShader(shaderId); + glShaderSource(shaderId, 1, &shaderSourceC, nullptr); + glCompileShader(shaderId); // Check for errors - gl.glGetShaderiv(shaderId, GL_COMPILE_STATUS, &glSuccess); - gl.glGetShaderInfoLog(shaderId, kInfoLogBufSize, &logLength, infoLog); + glGetShaderiv(shaderId, GL_COMPILE_STATUS, &glSuccess); + glGetShaderInfoLog( + shaderId, kInfoLogBufSize, &logLength, static_cast(infoLog)); if (!glSuccess) { logger_->error("Shader compilation failed: {}", infoLog); @@ -138,7 +130,7 @@ bool ShaderProgram::Load( } else if (logLength > 0) { - logger_->error("Shader compiled with warnings: {}", infoLog); + logger_->warn("Shader compiled with warnings: {}", infoLog); } } @@ -146,13 +138,14 @@ bool ShaderProgram::Load( { for (auto& shaderId : shaderIds) { - gl.glAttachShader(p->id_, shaderId); + glAttachShader(p->id_, shaderId); } - gl.glLinkProgram(p->id_); + glLinkProgram(p->id_); // Check for errors - gl.glGetProgramiv(p->id_, GL_LINK_STATUS, &glSuccess); - gl.glGetProgramInfoLog(p->id_, kInfoLogBufSize, &logLength, infoLog); + glGetProgramiv(p->id_, GL_LINK_STATUS, &glSuccess); + glGetProgramInfoLog( + p->id_, kInfoLogBufSize, &logLength, static_cast(infoLog)); if (!glSuccess) { logger_->error("Shader program link failed: {}", infoLog); @@ -160,14 +153,14 @@ bool ShaderProgram::Load( } else if (logLength > 0) { - logger_->error("Shader program linked with warnings: {}", infoLog); + logger_->warn("Shader program linked with warnings: {}", infoLog); } } // Delete shaders for (auto& shaderId : shaderIds) { - gl.glDeleteShader(shaderId); + glDeleteShader(shaderId); } return success; @@ -175,9 +168,7 @@ bool ShaderProgram::Load( void ShaderProgram::Use() const { - p->gl_.glUseProgram(p->id_); + glUseProgram(p->id_); } -} // namespace gl -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::gl diff --git a/scwx-qt/source/scwx/qt/gl/shader_program.hpp b/scwx-qt/source/scwx/qt/gl/shader_program.hpp index a2b887d8..13fe043b 100644 --- a/scwx-qt/source/scwx/qt/gl/shader_program.hpp +++ b/scwx-qt/source/scwx/qt/gl/shader_program.hpp @@ -19,7 +19,7 @@ namespace gl class ShaderProgram { public: - explicit ShaderProgram(OpenGLFunctions& gl); + explicit ShaderProgram(); virtual ~ShaderProgram(); ShaderProgram(const ShaderProgram&) = delete; diff --git a/scwx-qt/source/scwx/qt/main/application.cpp b/scwx-qt/source/scwx/qt/main/application.cpp index d851c8ec..3f62120a 100644 --- a/scwx-qt/source/scwx/qt/main/application.cpp +++ b/scwx-qt/source/scwx/qt/main/application.cpp @@ -44,6 +44,14 @@ void WaitForInitialization() } } +// Only use for test cases +void ResetInitilization() +{ + logger_->debug("Application initialization reset"); + std::unique_lock lock(initializationMutex_); + initialized_ = false; +} + } // namespace Application } // namespace main } // namespace qt diff --git a/scwx-qt/source/scwx/qt/main/application.hpp b/scwx-qt/source/scwx/qt/main/application.hpp index 9b63ffad..0237f13c 100644 --- a/scwx-qt/source/scwx/qt/main/application.hpp +++ b/scwx-qt/source/scwx/qt/main/application.hpp @@ -11,6 +11,8 @@ namespace Application void FinishInitialization(); void WaitForInitialization(); +// Only use for test cases +void ResetInitilization(); } // namespace Application } // namespace main diff --git a/scwx-qt/source/scwx/qt/main/check_privilege.cpp b/scwx-qt/source/scwx/qt/main/check_privilege.cpp new file mode 100644 index 00000000..e6402af5 --- /dev/null +++ b/scwx-qt/source/scwx/qt/main/check_privilege.cpp @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include +#else +# include +#endif + +namespace scwx::qt::main +{ + +bool is_high_privilege() +{ +#if defined(_WIN32) + bool isAdmin = false; + HANDLE token = NULL; + TOKEN_ELEVATION elevation; + DWORD elevationSize = sizeof(TOKEN_ELEVATION); + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) + { + return false; + } + if (!GetTokenInformation( + token, TokenElevation, &elevation, elevationSize, &elevationSize)) + { + CloseHandle(token); + return false; + } + isAdmin = elevation.TokenIsElevated; + CloseHandle(token); + return isAdmin; +#elif defined(Q_OS_UNIX) + // On UNIX root is always uid 0. On Linux this is enforced by the kernel. + return geteuid() == 0; +#else + return false; +#endif +} + +#if defined(_WIN32) +static const QString message = QObject::tr( + "Supercell Wx has been run with administrator permissions. It is " + "recommended to run it without administrator permissions Do you wish to " + "continue?"); +#elif defined(Q_OS_UNIX) +static const QString message = QObject::tr( + "Supercell Wx has been run as root. It is recommended to run it as a normal " + "user. Do you wish to continue?"); +#else +static const QString message = QObject::tr(""); +#endif + +static const QString title = QObject::tr("Supercell Wx"); +static const QString checkBoxText = + QObject::tr("Do not show this warning again."); + +class PrivilegeChecker::Impl +{ +public: + explicit Impl() : + highPrivilege_ {is_high_privilege()}, + dialog_ {QMessageBox::Icon::Warning, title, message}, + checkBox_(new QCheckBox(checkBoxText, &dialog_)) + { + const std::string appDataPath { + QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + .toStdString()}; + hasAppData_ = std::filesystem::exists(appDataPath); + + dialog_.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + dialog_.setDefaultButton(QMessageBox::No); + dialog_.setCheckBox(checkBox_); + }; + + bool hasAppData_; + bool firstCheckCheckBoxState_ {true}; + bool highPrivilege_; + + QMessageBox dialog_; + QCheckBox* checkBox_; +}; + +PrivilegeChecker::PrivilegeChecker() : + p(std::make_unique()) +{ +} + +PrivilegeChecker::~PrivilegeChecker() = default; + +bool PrivilegeChecker::pre_settings_check() +{ + if (p->hasAppData_ || !p->highPrivilege_) + { + return false; + } + + const int result = p->dialog_.exec(); + p->firstCheckCheckBoxState_ = p->checkBox_->isChecked(); + + return result != QMessageBox::Yes; +} + +bool PrivilegeChecker::post_settings_check() +{ + auto& highPrivilegeWarningEnabled = + settings::GeneralSettings::Instance().high_privilege_warning_enabled(); + if (!highPrivilegeWarningEnabled.GetValue() || !p->highPrivilege_) + { + return false; + } + else if (!p->hasAppData_) + { + highPrivilegeWarningEnabled.StageValue(!p->firstCheckCheckBoxState_); + return false; + } + + switch (p->dialog_.exec()) + { + case QMessageBox::Yes: + highPrivilegeWarningEnabled.StageValue(!p->checkBox_->isChecked()); + return false; + case QMessageBox::No: + default: + return true; + } +} + +} // namespace scwx::qt::main diff --git a/scwx-qt/source/scwx/qt/main/check_privilege.hpp b/scwx-qt/source/scwx/qt/main/check_privilege.hpp new file mode 100644 index 00000000..8ec3f8e2 --- /dev/null +++ b/scwx-qt/source/scwx/qt/main/check_privilege.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +namespace scwx::qt::main +{ + +bool is_high_privilege(); + +class PrivilegeChecker +{ +public: + explicit PrivilegeChecker(); + ~PrivilegeChecker(); + + PrivilegeChecker(const PrivilegeChecker&) = delete; + PrivilegeChecker& operator=(const PrivilegeChecker&) = delete; + PrivilegeChecker(const PrivilegeChecker&&) = delete; + PrivilegeChecker& operator=(const PrivilegeChecker&&) = delete; + + // returning true means check failed. + bool pre_settings_check(); + bool post_settings_check(); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace scwx::qt::main diff --git a/scwx-qt/source/scwx/qt/main/main.cpp b/scwx-qt/source/scwx/qt/main/main.cpp index 566e9772..a2416cec 100644 --- a/scwx-qt/source/scwx/qt/main/main.cpp +++ b/scwx-qt/source/scwx/qt/main/main.cpp @@ -3,15 +3,18 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -25,11 +28,21 @@ #include #include #include +#include +#include #include +#include +#include + +#define QT6CT_LIBRARY +#include +#undef QT6CT_LIBRARY static const std::string logPrefix_ = "scwx::main"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); +static void ConfigureTheme(const std::vector& args); +static void InitializeOpenGL(); static void OverrideDefaultStyle(const std::vector& args); int main(int argc, char* argv[]) @@ -41,19 +54,28 @@ int main(int argc, char* argv[]) args.push_back(argv[i]); } + if (!scwx::util::GetEnvironment("SCWX_TEST").empty()) + { + QStandardPaths::setTestModeEnabled(true); + } + // Initialize logger auto& logManager = scwx::qt::manager::LogManager::Instance(); logManager.Initialize(); - logger_->info("Supercell Wx v{} ({})", + QCoreApplication::setApplicationName("Supercell Wx"); + + logManager.InitializeLogFile(); + + logger_->info("Supercell Wx v{}.{} ({})", scwx::qt::main::kVersionString_, + scwx::qt::main::kBuildNumber_, scwx::qt::main::kCommitString_); - QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); + InitializeOpenGL(); QApplication a(argc, argv); - QCoreApplication::setApplicationName("Supercell Wx"); scwx::network::cpr::SetUserAgent( fmt::format("SupercellWx/{}", scwx::qt::main::kVersionString_)); @@ -64,9 +86,11 @@ int main(int argc, char* argv[]) QCoreApplication::installTranslator(&translator); } - if (!scwx::util::GetEnvironment("SCWX_TEST").empty()) + // Test to see if scwx was run with high privilege + scwx::qt::main::PrivilegeChecker privilegeChecker; + if (privilegeChecker.pre_settings_check()) { - QStandardPaths::setTestModeEnabled(true); + return 0; } // Start the io_context main loop @@ -96,40 +120,54 @@ int main(int argc, char* argv[]) Aws::InitAPI(awsSdkOptions); // Initialize application - logManager.InitializeLogFile(); scwx::qt::config::RadarSite::Initialize(); scwx::qt::config::CountyDatabase::Initialize(); + scwx::qt::manager::TaskManager::Initialize(); scwx::qt::manager::SettingsManager::Instance().Initialize(); scwx::qt::manager::ResourceManager::Initialize(); // Theme - auto uiStyle = scwx::qt::types::GetUiStyle( - scwx::qt::settings::GeneralSettings::Instance().theme().GetValue()); + ConfigureTheme(args); - if (uiStyle == scwx::qt::types::UiStyle::Default) + // Check process modules for compatibility + scwx::qt::main::CheckProcessModules(); + + int result = 0; + if (privilegeChecker.post_settings_check()) { - OverrideDefaultStyle(args); + result = 1; } else { - QApplication::setStyle( - QString::fromStdString(scwx::qt::types::GetUiStyleName(uiStyle))); - } + // Run initial setup if required + if (scwx::qt::ui::setup::SetupWizard::IsSetupRequired()) + { + scwx::qt::ui::setup::SetupWizard w; + w.show(); + a.exec(); + } - // Run initial setup if required - if (scwx::qt::ui::setup::SetupWizard::IsSetupRequired()) - { - scwx::qt::ui::setup::SetupWizard w; - w.show(); - a.exec(); - } + // Run Qt main loop + { + scwx::qt::main::MainWindow w; - // Run Qt main loop - int result; - { - scwx::qt::main::MainWindow w; - w.show(); - result = a.exec(); + bool initialized = false; + + try + { + w.show(); + initialized = true; + } + catch (const std::exception& ex) + { + logger_->critical(ex.what()); + } + + if (initialized) + { + result = a.exec(); + } + } } // Deinitialize application @@ -145,6 +183,7 @@ int main(int argc, char* argv[]) // Shutdown application scwx::qt::manager::ResourceManager::Shutdown(); scwx::qt::manager::SettingsManager::Instance().Shutdown(); + scwx::qt::manager::TaskManager::Shutdown(); // Shutdown AWS SDK Aws::ShutdownAPI(awsSdkOptions); @@ -152,6 +191,73 @@ int main(int argc, char* argv[]) return result; } +static void ConfigureTheme(const std::vector& args) +{ + auto& generalSettings = scwx::qt::settings::GeneralSettings::Instance(); + + auto uiStyle = + scwx::qt::types::GetUiStyle(generalSettings.theme().GetValue()); + auto qtColorScheme = scwx::qt::types::GetQtColorScheme(uiStyle); + + if (uiStyle == scwx::qt::types::UiStyle::Default) + { + OverrideDefaultStyle(args); + } + else + { + QApplication::setStyle( + QString::fromStdString(scwx::qt::types::GetQtStyleName(uiStyle))); + } + + QGuiApplication::styleHints()->setColorScheme(qtColorScheme); + + std::optional paletteFile; + if (uiStyle == scwx::qt::types::UiStyle::FusionCustom) + { + paletteFile = generalSettings.theme_file().GetValue(); + } + else + { + paletteFile = scwx::qt::types::GetQtPaletteFile(uiStyle); + } + + if (paletteFile) + { + QPalette defaultPalette = QApplication::style()->standardPalette(); + QPalette palette = Qt6CT::loadColorScheme( + QString::fromStdString(*paletteFile), defaultPalette); + + if (defaultPalette == palette) + { + logger_->warn("Failed to load palette file '{}'", *paletteFile); + } + else + { + logger_->info("Loaded palette file '{}'", *paletteFile); + } + + QApplication::setPalette(palette); + } +} + +static void InitializeOpenGL() +{ + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); + + QSurfaceFormat surfaceFormat = QSurfaceFormat::defaultFormat(); + surfaceFormat.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); + surfaceFormat.setRenderableType(QSurfaceFormat::RenderableType::OpenGL); + +#if defined(__APPLE__) + // For macOS, we must choose between OpenGL 4.1 Core and OpenGL 2.1 + // Compatibility. OpenGL 2.1 does not meet requirements for shaders used by + // Supercell Wx. + surfaceFormat.setVersion(4, 1); +#endif + + QSurfaceFormat::setDefaultFormat(surfaceFormat); +} + static void OverrideDefaultStyle([[maybe_unused]] const std::vector& args) { diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 4046e3bd..4fa2f28a 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -1,11 +1,13 @@ #include "main_window.hpp" #include "./ui_main_window.h" +#include #include #include #include #include #include +#include #include #include #include @@ -30,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -52,10 +55,6 @@ #include #include -#if !defined(_MSC_VER) -# include -#endif - namespace scwx { namespace qt @@ -90,11 +89,13 @@ public: imGuiDebugDialog_ {nullptr}, layerDialog_ {nullptr}, placefileDialog_ {nullptr}, + markerDialog_ {nullptr}, radarSiteDialog_ {nullptr}, settingsDialog_ {nullptr}, updateDialog_ {nullptr}, alertManager_ {manager::AlertManager::Instance()}, placefileManager_ {manager::PlacefileManager::Instance()}, + markerManager_ {manager::MarkerManager::Instance()}, positionManager_ {manager::PositionManager::Instance()}, textEventManager_ {manager::TextEventManager::Instance()}, timelineManager_ {manager::TimelineManager::Instance()}, @@ -139,6 +140,9 @@ public: } ~MainWindowImpl() { + homeRadarConnection_.disconnect(); + defaultTimeZoneConnection_.disconnect(); + auto& generalSettings = settings::GeneralSettings::Instance(); auto& customStyleUrl = generalSettings.custom_style_url(); @@ -187,6 +191,8 @@ public: map::MapProvider mapProvider_; map::MapWidget* activeMap_; + std::shared_ptr glContext_ {nullptr}; + ui::CollapsibleGroup* mapSettingsGroup_; ui::CollapsibleGroup* level2ProductsGroup_; ui::CollapsibleGroup* level2SettingsGroup_; @@ -207,6 +213,7 @@ public: ui::ImGuiDebugDialog* imGuiDebugDialog_; ui::LayerDialog* layerDialog_; ui::PlacefileDialog* placefileDialog_; + ui::MarkerDialog* markerDialog_; ui::RadarSiteDialog* radarSiteDialog_; ui::SettingsDialog* settingsDialog_; ui::UpdateDialog* updateDialog_; @@ -221,6 +228,7 @@ public: std::shared_ptr hotkeyManager_ { manager::HotkeyManager::Instance()}; std::shared_ptr placefileManager_; + std::shared_ptr markerManager_; std::shared_ptr positionManager_; std::shared_ptr textEventManager_; std::shared_ptr timelineManager_; @@ -237,9 +245,12 @@ public: layerActions_ {}; bool layerActionsInitialized_ {false}; + boost::signals2::scoped_connection homeRadarConnection_ {}; + boost::signals2::scoped_connection defaultTimeZoneConnection_ {}; + std::vector maps_; - std::chrono::system_clock::time_point volumeTime_ {}; + std::chrono::system_clock::time_point selectedTime_ {}; public slots: void UpdateMapParameters(double latitude, @@ -265,6 +276,7 @@ MainWindow::MainWindow(QWidget* parent) : ui->vcpLabel->setVisible(false); ui->vcpValueLabel->setVisible(false); ui->vcpDescriptionLabel->setVisible(false); + ui->saveRadarProductsButton->setVisible(true); p->radarSitePresetsMenu_ = new QMenu(this); ui->radarSitePresetsButton->setMenu(p->radarSitePresetsMenu_); @@ -279,7 +291,6 @@ MainWindow::MainWindow(QWidget* parent) : // Configure Alert Dock p->alertDockWidget_ = new ui::AlertDockWidget(this); - p->alertDockWidget_->setVisible(false); addDockWidget(Qt::BottomDockWidgetArea, p->alertDockWidget_); // GPS Info Dialog @@ -308,18 +319,25 @@ MainWindow::MainWindow(QWidget* parent) : // Placefile Manager Dialog p->placefileDialog_ = new ui::PlacefileDialog(this); + // Marker Manager Dialog + p->markerDialog_ = new ui::MarkerDialog(this); + // Layer Dialog p->layerDialog_ = new ui::LayerDialog(this); // Settings Dialog - p->settingsDialog_ = new ui::SettingsDialog(this); + p->settingsDialog_ = new ui::SettingsDialog(p->settings_, this); // Map Settings p->mapSettingsGroup_ = new ui::CollapsibleGroup(tr("Map Settings"), this); p->mapSettingsGroup_->GetContentsLayout()->addWidget(ui->mapStyleLabel); p->mapSettingsGroup_->GetContentsLayout()->addWidget(ui->mapStyleComboBox); + p->mapSettingsGroup_->GetContentsLayout()->addWidget( + ui->smoothRadarDataCheckBox); p->mapSettingsGroup_->GetContentsLayout()->addWidget( ui->trackLocationCheckBox); + p->mapSettingsGroup_->GetContentsLayout()->addWidget( + ui->saveRadarProductsButton); ui->radarToolboxScrollAreaContents->layout()->replaceWidget( ui->mapSettingsGroupBox, p->mapSettingsGroup_); ui->mapSettingsGroupBox->setVisible(false); @@ -361,6 +379,7 @@ MainWindow::MainWindow(QWidget* parent) : p->animationDockWidget_ = new ui::AnimationDockWidget(this); p->timelineGroup_->GetContentsLayout()->addWidget(p->animationDockWidget_); ui->radarToolboxScrollAreaContents->layout()->addWidget(p->timelineGroup_); + p->animationDockWidget_->UpdateTimeZone(p->activeMap_->GetDefaultTimeZone()); // Reset toolbox spacer at the bottom ui->radarToolboxScrollAreaContents->layout()->removeItem( @@ -444,8 +463,37 @@ void MainWindow::keyReleaseEvent(QKeyEvent* ev) void MainWindow::showEvent(QShowEvent* event) { QMainWindow::showEvent(event); + auto& uiSettings = settings::UiSettings::Instance(); - resizeDocks({ui->radarToolboxDock}, {194}, Qt::Horizontal); + // restore the geometry state + std::string uiGeometry = uiSettings.main_ui_geometry().GetValue(); + restoreGeometry( + QByteArray::fromBase64(QByteArray::fromStdString(uiGeometry))); + + // restore the UI state + std::string uiState = uiSettings.main_ui_state().GetValue(); + + bool restored = + restoreState(QByteArray::fromBase64(QByteArray::fromStdString(uiState))); + if (!restored) + { + resizeDocks({ui->radarToolboxDock}, {194}, Qt::Horizontal); + } +} + +void MainWindow::closeEvent(QCloseEvent* event) +{ + auto& uiSettings = settings::UiSettings::Instance(); + + // save the UI geometry + QByteArray uiGeometry = saveGeometry().toBase64(); + uiSettings.main_ui_geometry().StageValue(uiGeometry.data()); + + // save the UI state + QByteArray uiState = saveState().toBase64(); + uiSettings.main_ui_state().StageValue(uiState.data()); + + QMainWindow::closeEvent(event); } void MainWindow::on_actionOpenNexrad_triggered() @@ -461,12 +509,11 @@ void MainWindow::on_actionOpenNexrad_triggered() map::MapWidget* currentMap = p->activeMap_; // Make sure the parent window properly repaints on close - connect( - dialog, - &QFileDialog::finished, - this, - [this]() { update(); }, - Qt::QueuedConnection); + connect(dialog, + &QFileDialog::finished, + this, + static_cast(&MainWindow::update), + Qt::QueuedConnection); connect( dialog, @@ -527,12 +574,11 @@ void MainWindow::on_actionOpenTextEvent_triggered() dialog->setAttribute(Qt::WA_DeleteOnClose); // Make sure the parent window properly repaints on close - connect( - dialog, - &QFileDialog::finished, - this, - [this]() { update(); }, - Qt::QueuedConnection); + connect(dialog, + &QFileDialog::finished, + this, + static_cast(&MainWindow::update), + Qt::QueuedConnection); connect(dialog, &QFileDialog::fileSelected, @@ -586,6 +632,11 @@ void MainWindow::on_actionPlacefileManager_triggered() p->placefileDialog_->show(); } +void MainWindow::on_actionMarkerManager_triggered() +{ + p->markerDialog_->show(); +} + void MainWindow::on_actionLayerManager_triggered() { p->layerDialog_->show(); @@ -606,6 +657,11 @@ void MainWindow::on_actionDumpRadarProductRecords_triggered() manager::RadarProductManager::DumpRecords(); } +void MainWindow::on_actionRadarWireframe_triggered(bool checked) +{ + p->activeMap_->SetRadarWireframeEnabled(checked); +} + void MainWindow::on_actionUserManual_triggered() { QDesktopServices::openUrl(QUrl {"https://supercell-wx.readthedocs.io/"}); @@ -726,6 +782,8 @@ void MainWindowImpl::ConfigureMapLayout() } }; + glContext_ = std::make_shared(); + for (int64_t y = 0; y < gridHeight; y++) { QSplitter* hs = new QSplitter(vs); @@ -735,7 +793,9 @@ void MainWindowImpl::ConfigureMapLayout() { if (maps_.at(mapIndex) == nullptr) { - maps_[mapIndex] = new map::MapWidget(mapIndex, settings_); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory): Owned by parent + maps_[mapIndex] = + new map::MapWidget(mapIndex, settings_, glContext_); } hs->addWidget(maps_[mapIndex]); @@ -768,9 +828,9 @@ void MainWindowImpl::ConfigureMapStyles() if ((customStyleAvailable_ && styleName == "Custom") || std::find_if(mapProviderInfo.mapStyles_.cbegin(), mapProviderInfo.mapStyles_.cend(), - [&](const auto& mapStyle) { - return mapStyle.name_ == styleName; - }) != mapProviderInfo.mapStyles_.cend()) + [&](const auto& mapStyle) + { return mapStyle.name_ == styleName; }) != + mapProviderInfo.mapStyles_.cend()) { // Initialize map style from settings maps_.at(i)->SetInitialMapStyle(styleName); @@ -913,11 +973,28 @@ void MainWindowImpl::ConnectMapSignals() } }, Qt::QueuedConnection); + connect( + mapWidget, + &map::MapWidget::IncomingLevel2ElevationChanged, + this, + [this](std::optional) + { level2SettingsWidget_->UpdateSettings(activeMap_); }, + Qt::QueuedConnection); } } void MainWindowImpl::ConnectAnimationSignals() { + defaultTimeZoneConnection_ = settings::GeneralSettings::Instance() + .default_time_zone() + .changed_signal() + .connect( + [this]() + { + animationDockWidget_->UpdateTimeZone( + activeMap_->GetDefaultTimeZone()); + }); + connect(animationDockWidget_, &ui::AnimationDockWidget::DateTimeChanged, timelineManager_.get(), @@ -961,21 +1038,16 @@ void MainWindowImpl::ConnectAnimationSignals() connect(timelineManager_.get(), &manager::TimelineManager::SelectedTimeUpdated, - [this]() - { - for (auto map : maps_) - { - map->update(); - } - }); - connect(timelineManager_.get(), - &manager::TimelineManager::VolumeTimeUpdated, [this](std::chrono::system_clock::time_point dateTime) { - volumeTime_ = dateTime; + selectedTime_ = dateTime; + for (auto map : maps_) { map->SelectTime(dateTime); + textEventManager_->SelectTime(dateTime); + QMetaObject::invokeMethod( + map, static_cast(&QWidget::update)); } }); @@ -1055,10 +1127,29 @@ void MainWindowImpl::ConnectOtherSignals() } } }); + connect( + mainWindow_->ui->smoothRadarDataCheckBox, + &QCheckBox::checkStateChanged, + mainWindow_, + [this](Qt::CheckState state) + { + const bool smoothingEnabled = (state == Qt::CheckState::Checked); + + auto it = std::find(maps_.cbegin(), maps_.cend(), activeMap_); + if (it != maps_.cend()) + { + const std::size_t i = std::distance(maps_.cbegin(), it); + settings::MapSettings::Instance().smoothing_enabled(i).StageValue( + smoothingEnabled); + } + + // Turn on smoothing + activeMap_->SetSmoothingEnabled(smoothingEnabled); + }); connect(mainWindow_->ui->trackLocationCheckBox, - &QCheckBox::stateChanged, + &QCheckBox::checkStateChanged, mainWindow_, - [this](int state) + [this](Qt::CheckState state) { bool trackingEnabled = (state == Qt::CheckState::Checked); @@ -1068,22 +1159,37 @@ void MainWindowImpl::ConnectOtherSignals() // Turn on location tracking positionManager_->TrackLocation(trackingEnabled); }); - connect(level2ProductsWidget_, - &ui::Level2ProductsWidget::RadarProductSelected, - mainWindow_, - [&](common::RadarProductGroup group, - const std::string& productName, - int16_t productCode) { - SelectRadarProduct(activeMap_, group, productName, productCode); - }); - connect(level3ProductsWidget_, - &ui::Level3ProductsWidget::RadarProductSelected, - mainWindow_, - [&](common::RadarProductGroup group, - const std::string& productName, - int16_t productCode) { - SelectRadarProduct(activeMap_, group, productName, productCode); - }); + connect( + mainWindow_->ui->saveRadarProductsButton, + &QAbstractButton::clicked, + mainWindow_, + [this]() + { + auto& mapSettings = settings::MapSettings::Instance(); + for (std::size_t i = 0; i < maps_.size(); i++) + { + const auto& map = maps_.at(i); + mapSettings.radar_product_group(i).StageValue( + common::GetRadarProductGroupName(map->GetRadarProductGroup())); + mapSettings.radar_product(i).StageValue(map->GetRadarProductName()); + } + }); + connect( + level2ProductsWidget_, + &ui::Level2ProductsWidget::RadarProductSelected, + mainWindow_, + [&](common::RadarProductGroup group, + const std::string& productName, + int16_t productCode) + { SelectRadarProduct(activeMap_, group, productName, productCode); }); + connect( + level3ProductsWidget_, + &ui::Level3ProductsWidget::RadarProductSelected, + mainWindow_, + [&](common::RadarProductGroup group, + const std::string& productName, + int16_t productCode) + { SelectRadarProduct(activeMap_, group, productName, productCode); }); connect(level2SettingsWidget_, &ui::Level2SettingsWidget::ElevationSelected, mainWindow_, @@ -1194,11 +1300,33 @@ void MainWindowImpl::ConnectOtherSignals() this, [this]() { - timeLabel_->setText(QString::fromStdString( - util::TimeString(std::chrono::system_clock::now()))); + timeLabel_->setText( + QString::fromStdString(util::TimeString(util::time::now()))); timeLabel_->setVisible(true); }); clockTimer_.start(1000); + + auto& generalSettings = settings::GeneralSettings::Instance(); + homeRadarConnection_ = + generalSettings.default_radar_site().changed_signal().connect( + [this]() + { + const std::shared_ptr radarSite = + activeMap_->GetRadarSite(); + const std::string homeRadarSite = + settings::GeneralSettings::Instance() + .default_radar_site() + .GetValue(); + if (radarSite == nullptr) + { + mainWindow_->ui->saveRadarProductsButton->setVisible(false); + } + else + { + mainWindow_->ui->saveRadarProductsButton->setVisible( + radarSite->id() == homeRadarSite); + } + }); } void MainWindowImpl::InitializeLayerDisplayActions() @@ -1363,7 +1491,8 @@ void MainWindowImpl::SelectRadarProduct(map::MapWidget* mapWidget, UpdateRadarProductSettings(); } - mapWidget->SelectRadarProduct(group, productName, productCode, volumeTime_); + mapWidget->SelectRadarProduct( + group, productName, productCode, selectedTime_); } void MainWindowImpl::SetActiveMap(map::MapWidget* mapWidget) @@ -1433,18 +1562,28 @@ void MainWindowImpl::UpdateRadarProductSettings() { if (activeMap_->GetRadarProductGroup() == common::RadarProductGroup::Level2) { - level2SettingsWidget_->UpdateSettings(activeMap_); level2SettingsGroup_->setVisible(true); + // This should be done after setting visible for correct sizing + level2SettingsWidget_->UpdateSettings(activeMap_); } else { level2SettingsGroup_->setVisible(false); } + + mainWindow_->ui->smoothRadarDataCheckBox->setCheckState( + activeMap_->GetSmoothingEnabled() ? Qt::CheckState::Checked : + Qt::CheckState::Unchecked); + + mainWindow_->ui->actionRadarWireframe->setChecked( + activeMap_->GetRadarWireframeEnabled()); } void MainWindowImpl::UpdateRadarSite() { std::shared_ptr radarSite = activeMap_->GetRadarSite(); + const std::string homeRadarSite = + settings::GeneralSettings::Instance().default_radar_site().GetValue(); if (radarSite != nullptr) { @@ -1459,6 +1598,9 @@ void MainWindowImpl::UpdateRadarSite() radarSite->location_name().c_str()); timelineManager_->SetRadarSite(radarSite->id()); + + mainWindow_->ui->saveRadarProductsButton->setVisible(radarSite->id() == + homeRadarSite); } else { @@ -1466,12 +1608,15 @@ void MainWindowImpl::UpdateRadarSite() mainWindow_->ui->radarSiteValueLabel->setVisible(false); mainWindow_->ui->radarLocationLabel->setVisible(false); + mainWindow_->ui->saveRadarProductsButton->setVisible(false); timelineManager_->SetRadarSite("?"); } alertManager_->SetRadarSite(radarSite); placefileManager_->SetRadarSite(radarSite); + + animationDockWidget_->UpdateTimeZone(activeMap_->GetDefaultTimeZone()); } void MainWindowImpl::UpdateVcp() diff --git a/scwx-qt/source/scwx/qt/main/main_window.hpp b/scwx-qt/source/scwx/qt/main/main_window.hpp index 33043308..6eb7fee2 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.hpp +++ b/scwx-qt/source/scwx/qt/main/main_window.hpp @@ -29,6 +29,7 @@ public: void keyPressEvent(QKeyEvent* ev) override final; void keyReleaseEvent(QKeyEvent* ev) override final; void showEvent(QShowEvent* event) override; + void closeEvent(QCloseEvent* event) override; signals: void ActiveMapMoved(double latitude, double longitude); @@ -43,10 +44,12 @@ private slots: void on_actionRadarRange_triggered(bool checked); void on_actionRadarSites_triggered(bool checked); void on_actionPlacefileManager_triggered(); + void on_actionMarkerManager_triggered(); void on_actionLayerManager_triggered(); void on_actionImGuiDebug_triggered(); void on_actionDumpLayerList_triggered(); void on_actionDumpRadarProductRecords_triggered(); + void on_actionRadarWireframe_triggered(bool checked); void on_actionUserManual_triggered(); void on_actionDiscord_triggered(); void on_actionGitHubRepository_triggered(); diff --git a/scwx-qt/source/scwx/qt/main/main_window.ui b/scwx-qt/source/scwx/qt/main/main_window.ui index 9fab1adf..e760bab2 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.ui +++ b/scwx-qt/source/scwx/qt/main/main_window.ui @@ -39,7 +39,7 @@ 0 0 1024 - 33 + 21 @@ -97,6 +97,8 @@ + + @@ -104,6 +106,7 @@ + @@ -152,8 +155,8 @@ 0 0 - 190 - 686 + 191 + 703 @@ -171,39 +174,25 @@ - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - 0 - 0 - - + + - KLSX + 35 - - - - - 0 - 0 - - - - Volume Coverage Pattern - + + - VCP + Clear Air Mode + + + + + + + St. Louis, MO @@ -268,34 +257,6 @@ - - - - Radar Site - - - - - - - St. Louis, MO - - - - - - - 35 - - - - - - - Clear Air Mode - - - @@ -309,6 +270,42 @@ + + + + + 0 + 0 + + + + Volume Coverage Pattern + + + VCP + + + + + + + Radar Site + + + + + + + + 0 + 0 + + + + KLSX + + + @@ -328,6 +325,13 @@ + + + + Smooth Radar Data + + + @@ -335,6 +339,13 @@ + + + + Set As Default Products + + + @@ -487,6 +498,23 @@ &GPS Info + + + + :/res/icons/font-awesome-6/house-solid.svg:/res/icons/font-awesome-6/house-solid.svg + + + Location &Marker Manager + + + + + true + + + Radar &Wireframe + + diff --git a/scwx-qt/source/scwx/qt/main/process_validation.cpp b/scwx-qt/source/scwx/qt/main/process_validation.cpp new file mode 100644 index 00000000..61f2cb29 --- /dev/null +++ b/scwx-qt/source/scwx/qt/main/process_validation.cpp @@ -0,0 +1,109 @@ +#include +#include + +#if defined(_WIN32) +# include + +# include +# include + +# include +# include +# include +# include +# include +#endif + +namespace scwx::qt::main +{ + +static const std::string logPrefix_ = "scwx::qt::main::process_validation"; +static const auto logger_ = util::Logger::Create(logPrefix_); + +void CheckProcessModules() +{ +#if defined(_WIN32) + HANDLE process = GetCurrentProcess(); + HMODULE modules[1024]; + DWORD cbNeeded = 0; + + std::vector incompatibleDlls {}; + std::vector descriptions {}; + + auto& processModuleWarningsEnabled = + settings::GeneralSettings::Instance().process_module_warnings_enabled(); + + if (EnumProcessModules(process, modules, sizeof(modules), &cbNeeded)) + { + std::uint32_t numModules = cbNeeded / sizeof(HMODULE); + for (std::uint32_t i = 0; i < numModules; ++i) + { + char modulePath[MAX_PATH]; + if (GetModuleFileNameExA(process, modules[i], modulePath, MAX_PATH)) + { + std::string path = modulePath; + + logger_->trace("DLL Found: {}", path); + + if (boost::algorithm::iends_with(path, "NahimicOSD.dll")) + { + std::string description = + QObject::tr( + "ASUS Sonic Studio injects a Nahimic driver, which causes " + "Supercell Wx to hang. It is suggested to disable the " + "Nahimic service, or to uninstall ASUS Sonic Studio and " + "the Nahimic driver.") + .toStdString(); + + logger_->warn("Incompatible DLL found: {}", path); + logger_->warn("{}", description); + + // Only populate vectors for the message box if warnings are + // enabled + if (processModuleWarningsEnabled.GetValue()) + { + incompatibleDlls.push_back(path); + descriptions.push_back(description); + } + } + } + } + } + + if (!incompatibleDlls.empty()) + { + const std::string header = + QObject::tr( + "The following DLLs have been injected into the Supercell Wx " + "process:") + .toStdString(); + const std::string defaultMessage = + QObject::tr( + "Supercell Wx is known to not run correctly with these DLLs " + "injected. We suggest stopping or uninstalling these services if " + "you experience crashes or unexpected behavior while using " + "Supercell Wx.") + .toStdString(); + + std::string message = fmt::format("{}\n\n{}\n\n{}\n\n{}", + header, + fmt::join(incompatibleDlls, "\n"), + defaultMessage, + fmt::join(descriptions, "\n")); + + QMessageBox dialog(QMessageBox::Icon::Warning, + QObject::tr("Supercell Wx"), + QString::fromStdString(message)); + QCheckBox* checkBox = + new QCheckBox(QObject::tr("Don't show this message again"), &dialog); + dialog.setCheckBox(checkBox); + dialog.exec(); + + // Stage the result of the checkbox. This value will be committed on + // shutdown. + processModuleWarningsEnabled.StageValue(!checkBox->isChecked()); + } +#endif +} + +} // namespace scwx::qt::main diff --git a/scwx-qt/source/scwx/qt/main/process_validation.hpp b/scwx-qt/source/scwx/qt/main/process_validation.hpp new file mode 100644 index 00000000..fb8fabaf --- /dev/null +++ b/scwx-qt/source/scwx/qt/main/process_validation.hpp @@ -0,0 +1,8 @@ +#pragma once + +namespace scwx::qt::main +{ + +void CheckProcessModules(); + +} // namespace scwx::qt::main diff --git a/scwx-qt/source/scwx/qt/main/versions.hpp.in b/scwx-qt/source/scwx/qt/main/versions.hpp.in index 32a69c59..bdf9cd62 100644 --- a/scwx-qt/source/scwx/qt/main/versions.hpp.in +++ b/scwx-qt/source/scwx/qt/main/versions.hpp.in @@ -3,17 +3,12 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace main +namespace scwx::qt::main { +const std::uint32_t kBuildNumber_ {${build_number}u}; const std::string kCommitString_ {"${commit_string}"}; const std::uint16_t kCopyrightYear_ {${copyright_year}u}; const std::string kVersionString_ {"${version_string}"}; -} // namespace main -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::main diff --git a/scwx-qt/source/scwx/qt/manager/alert_manager.cpp b/scwx-qt/source/scwx/qt/manager/alert_manager.cpp index 757754a9..41e74d7a 100644 --- a/scwx-qt/source/scwx/qt/manager/alert_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/alert_manager.cpp @@ -2,12 +2,13 @@ #include #include #include +#include #include +#include #include #include #include -#include -#include +#include #include #include @@ -138,8 +139,10 @@ common::Coordinate AlertManager::Impl::CurrentCoordinate( void AlertManager::Impl::HandleAlert(const types::TextEventKey& key, size_t messageIndex) const { + auto messages = textEventManager_->message_list(key); + // Skip alert if there are more messages to be processed - if (messageIndex + 1 < textEventManager_->message_count(key)) + if (messages.empty() || messageIndex + 1 < messages.size()) { return; } @@ -153,7 +156,7 @@ void AlertManager::Impl::HandleAlert(const types::TextEventKey& key, audioSettings.alert_radius().GetValue()); std::string alertWFO = audioSettings.alert_wfo().GetValue(); - auto message = textEventManager_->message_list(key).at(messageIndex); + auto message = messages.at(messageIndex); for (auto& segment : message->segments()) { @@ -170,7 +173,7 @@ void AlertManager::Impl::HandleAlert(const types::TextEventKey& key, // If the event has ended or is inactive, or if the alert is not enabled, // skip it - if (eventEnd < std::chrono::system_clock::now() || !alertActive || + if (eventEnd < scwx::util::time::now() || !alertActive || !audioSettings.alert_enabled(phenomenon).GetValue()) { continue; diff --git a/scwx-qt/source/scwx/qt/manager/download_manager.cpp b/scwx-qt/source/scwx/qt/manager/download_manager.cpp index b0585b35..54dd4371 100644 --- a/scwx-qt/source/scwx/qt/manager/download_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/download_manager.cpp @@ -155,7 +155,7 @@ void DownloadManager::Impl::DownloadSync( return !request->IsCanceled(); }), cpr::WriteCallback( - [&](std::string data, std::intptr_t /* userdata */) + [&](const std::string_view& data, std::intptr_t /* userdata */) { // Write file ofs << data; diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.cpp b/scwx-qt/source/scwx/qt/manager/font_manager.cpp index 20f5f9a6..fc645388 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.cpp @@ -15,19 +15,17 @@ #include #include #include +#include #include -namespace scwx -{ -namespace qt -{ -namespace manager +namespace scwx::qt::manager { static const std::string logPrefix_ = "scwx::qt::manager::font_manager"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); static const std::string kFcTrueType_ {"TrueType"}; +static const std::string kFcOpenType_ {"CFF"}; struct FontRecord { @@ -36,15 +34,13 @@ struct FontRecord std::string filename_ {}; }; -typedef std::pair> FontRecordPair; - template struct FontRecordHash; template<> -struct FontRecordHash +struct FontRecordHash { - size_t operator()(const FontRecordPair& x) const; + size_t operator()(const FontRecord& x) const; }; class FontManager::Impl @@ -69,6 +65,7 @@ public: const std::vector& GetRawFontData(const std::string& filename); + static bool CheckFontFormat(const FcChar8* format); static FontRecord MatchFontFile(const std::string& family, const std::vector& styles); @@ -78,20 +75,20 @@ public: std::shared_mutex imguiFontAtlasMutex_ {}; - std::uint64_t imguiFontsBuildCount_ {}; - - boost::unordered_flat_map, - FontRecordHash> + FontRecordHash> imguiFonts_ {}; std::shared_mutex imguiFontsMutex_ {}; boost::unordered_flat_map> rawFontData_ {}; std::mutex rawFontDataMutex_ {}; - std::shared_ptr defaultFont_ {}; + std::pair, units::font_size::pixels> + defaultFont_ {}; boost::unordered_flat_map> + std::pair, + units::font_size::pixels>> fontCategoryImguiFontMap_ {}; boost::unordered_flat_map fontCategoryQFontMap_ {}; @@ -136,22 +133,22 @@ void FontManager::Impl::ConnectSignals() }); } - QObject::connect( - &SettingsManager::Instance(), - &SettingsManager::SettingsSaved, - self_, - [this]() - { - std::scoped_lock lock {dirtyFontsMutex_, fontCategoryMutex_}; + QObject::connect(&SettingsManager::Instance(), + &SettingsManager::SettingsSaved, + self_, + [this]() + { + const std::scoped_lock lock {dirtyFontsMutex_, + fontCategoryMutex_}; - for (auto fontCategory : dirtyFonts_) - { - UpdateImGuiFont(fontCategory); - UpdateQFont(fontCategory); - } + for (auto fontCategory : dirtyFonts_) + { + UpdateImGuiFont(fontCategory); + UpdateQFont(fontCategory); + } - dirtyFonts_.clear(); - }); + dirtyFonts_.clear(); + }); } void FontManager::InitializeFonts() @@ -163,6 +160,22 @@ void FontManager::InitializeFonts() } } +units::font_size::pixels +FontManager::ImFontSize(units::font_size::pixels size) +{ + static constexpr units::font_size::pixels kMinFontSize_ {8}; + static constexpr units::font_size::pixels kMaxFontSize_ {96}; + + // Only allow whole pixels, and clamp to 6-72 pt + const units::font_size::pixels pixels {size}; + const units::font_size::pixels imFontSize { + std::clamp(static_cast(pixels.value()), + kMinFontSize_.value(), + kMaxFontSize_.value())}; + + return imFontSize; +} + void FontManager::Impl::UpdateImGuiFont(types::FontCategory fontCategory) { auto& textSettings = settings::TextSettings::Instance(); @@ -173,7 +186,8 @@ void FontManager::Impl::UpdateImGuiFont(types::FontCategory fontCategory) textSettings.font_point_size(fontCategory).GetValue()}; fontCategoryImguiFontMap_.insert_or_assign( - fontCategory, self_->LoadImGuiFont(family, {styles}, size)); + fontCategory, + std::make_pair(self_->LoadImGuiFont(family, {styles}), ImFontSize(size))); } void FontManager::Impl::UpdateQFont(types::FontCategory fontCategory) @@ -188,7 +202,13 @@ void FontManager::Impl::UpdateQFont(types::FontCategory fontCategory) QFont font = QFontDatabase::font(QString::fromStdString(family), QString::fromStdString(styles), static_cast(size.value())); + +#if !defined(__APPLE__) font.setPointSizeF(size.value()); +#else + const units::font_size::pixels pixelSize {size}; + font.setPixelSize(static_cast(pixelSize.value())); +#endif fontCategoryQFontMap_.insert_or_assign(fontCategory, font); } @@ -198,11 +218,6 @@ std::shared_mutex& FontManager::imgui_font_atlas_mutex() return p->imguiFontAtlasMutex_; } -std::uint64_t FontManager::imgui_fonts_build_count() const -{ - return p->imguiFontsBuildCount_; -} - int FontManager::GetFontId(types::Font font) const { auto it = p->fontIds_.find(font); @@ -213,7 +228,7 @@ int FontManager::GetFontId(types::Font font) const return -1; } -std::shared_ptr +std::pair, units::font_size::pixels> FontManager::GetImGuiFont(types::FontCategory fontCategory) { std::unique_lock lock {p->fontCategoryMutex_}; @@ -241,31 +256,23 @@ QFont FontManager::GetQFont(types::FontCategory fontCategory) } std::shared_ptr -FontManager::LoadImGuiFont(const std::string& family, - const std::vector& styles, - units::font_size::points size, - bool loadIfNotFound) +FontManager::LoadImGuiFont(const std::string& family, + const std::vector& styles, + bool loadIfNotFound) { const std::string styleString = fmt::format("{}", fmt::join(styles, " ")); - const std::string fontString = - fmt::format("{}-{}:{}", family, size.value(), styleString); + const std::string fontString = fmt::format("{}:{}", family, styleString); logger_->debug("LoadFontResource: {}", fontString); FontRecord fontRecord = Impl::MatchFontFile(family, styles); - // Only allow whole pixels, and clamp to 6-72 pt - units::font_size::pixels pixels {size}; - units::font_size::pixels imFontSize { - std::clamp(static_cast(pixels.value()), 8, 96)}; - auto imguiFontKey = std::make_pair(fontRecord, imFontSize); - // Search for a loaded ImGui font { std::shared_lock imguiFontLock {p->imguiFontsMutex_}; // Search for the associated ImGui font - auto it = p->imguiFonts_.find(imguiFontKey); + auto it = p->imguiFonts_.find(fontRecord); if (it != p->imguiFonts_.end()) { return it->second; @@ -290,7 +297,7 @@ FontManager::LoadImGuiFont(const std::string& family, // Search for the associated ImGui font again, to prevent loading the same // font twice - auto it = p->imguiFonts_.find(imguiFontKey); + auto it = p->imguiFonts_.find(fontRecord); if (it != p->imguiFonts_.end()) { return it->second; @@ -301,25 +308,20 @@ FontManager::LoadImGuiFont(const std::string& family, try { fontName = fmt::format( - "{}:{}", - std::filesystem::path(fontRecord.filename_).filename().string(), - imFontSize.value()); + "{}", std::filesystem::path(fontRecord.filename_).filename().string()); } catch (const std::exception& ex) { logger_->warn(ex.what()); - fontName = fmt::format("{}:{}", fontRecord.filename_, imFontSize.value()); + fontName = fmt::format("{}", fontRecord.filename_); } // Create an ImGui font std::shared_ptr imguiFont = - std::make_shared(fontName, rawFontData, imFontSize); + std::make_shared(fontName, rawFontData); // Store the ImGui font - p->imguiFonts_.insert_or_assign(imguiFontKey, imguiFont); - - // Increment ImGui font build count - ++p->imguiFontsBuildCount_; + p->imguiFonts_.insert_or_assign(fontRecord, imguiFont); // Return the ImGui font return imguiFont; @@ -456,6 +458,13 @@ void FontManager::Impl::FinalizeFontconfig() FcFini(); } +bool FontManager::Impl::CheckFontFormat(const FcChar8* format) +{ + const std::string stdFormat = reinterpret_cast(format); + + return stdFormat == kFcTrueType_ || stdFormat == kFcOpenType_; +} + FontRecord FontManager::Impl::MatchFontFile(const std::string& family, const std::vector& styles) @@ -468,9 +477,7 @@ FontManager::Impl::MatchFontFile(const std::string& family, FcPatternAddString( pattern, FC_FAMILY, reinterpret_cast(family.c_str())); - FcPatternAddString(pattern, - FC_FONTFORMAT, - reinterpret_cast(kFcTrueType_.c_str())); + FcPatternAddBool(pattern, FC_SYMBOL, FcFalse); if (!styles.empty()) { @@ -484,29 +491,55 @@ FontManager::Impl::MatchFontFile(const std::string& family, FcDefaultSubstitute(pattern); // Find matching font - FcResult result; - FcPattern* match = FcFontMatch(nullptr, pattern, &result); + FcResult result {}; + FcFontSet* matches = FcFontSort(nullptr, pattern, FcFalse, nullptr, &result); FontRecord record {}; - if (match != nullptr) + if (matches != nullptr) { - FcChar8* fcFamily; - FcChar8* fcStyle; - FcChar8* fcFile; - - // Match was found, get properties - if (FcPatternGetString(match, FC_FAMILY, 0, &fcFamily) == FcResultMatch && - FcPatternGetString(match, FC_STYLE, 0, &fcStyle) == FcResultMatch && - FcPatternGetString(match, FC_FILE, 0, &fcFile) == FcResultMatch) + for (int i = 0; i < matches->nfont; i++) { - record.family_ = reinterpret_cast(fcFamily); - record.style_ = reinterpret_cast(fcStyle); - record.filename_ = reinterpret_cast(fcFile); + FcPattern* match = + // Using C code requires pointer arithmetic + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + FcFontRenderPrepare(nullptr, pattern, matches->fonts[i]); + if (match == nullptr) + { + continue; + } + FcChar8* fcFamily = nullptr; + FcChar8* fcStyle = nullptr; + FcChar8* fcFile = nullptr; + FcChar8* fcFormat = nullptr; + FcBool fcSymbol = FcFalse; - logger_->debug("Found matching font: {}:{} ({})", - record.family_, - record.style_, - record.filename_); + // Match was found, get properties + if (FcPatternGetString(match, FC_FAMILY, 0, &fcFamily) == + FcResultMatch && + FcPatternGetString(match, FC_STYLE, 0, &fcStyle) == + FcResultMatch && + FcPatternGetString(match, FC_FILE, 0, &fcFile) == FcResultMatch && + FcPatternGetBool(match, FC_SYMBOL, 0, &fcSymbol) == + FcResultMatch && + FcPatternGetString(match, FC_FONTFORMAT, 0, &fcFormat) == + FcResultMatch && + fcSymbol == FcFalse /*Must check fcSymbol manually*/ && + CheckFontFormat(fcFormat)) + { + record.family_ = reinterpret_cast(fcFamily); + record.style_ = reinterpret_cast(fcStyle); + record.filename_ = reinterpret_cast(fcFile); + + logger_->debug("Found matching font: {}:{} ({}) {}", + record.family_, + record.style_, + record.filename_, + fcSymbol); + FcPatternDestroy(match); + break; + } + + FcPatternDestroy(match); } } @@ -516,7 +549,7 @@ FontManager::Impl::MatchFontFile(const std::string& family, } // Cleanup - FcPatternDestroy(match); + FcFontSetDestroy(matches); FcPatternDestroy(pattern); return record; @@ -528,13 +561,12 @@ FontManager& FontManager::Instance() return instance_; } -size_t FontRecordHash::operator()(const FontRecordPair& x) const +size_t FontRecordHash::operator()(const FontRecord& x) const { size_t seed = 0; - boost::hash_combine(seed, x.first.family_); - boost::hash_combine(seed, x.first.style_); - boost::hash_combine(seed, x.first.filename_); - boost::hash_combine(seed, x.second.value()); + boost::hash_combine(seed, x.family_); + boost::hash_combine(seed, x.style_); + boost::hash_combine(seed, x.filename_); return seed; } @@ -545,6 +577,4 @@ bool operator==(const FontRecord& lhs, const FontRecord& rhs) lhs.filename_ == rhs.filename_; } -} // namespace manager -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::manager diff --git a/scwx-qt/source/scwx/qt/manager/font_manager.hpp b/scwx-qt/source/scwx/qt/manager/font_manager.hpp index e52d0d16..50e05d9d 100644 --- a/scwx-qt/source/scwx/qt/manager/font_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/font_manager.hpp @@ -26,21 +26,22 @@ public: ~FontManager(); std::shared_mutex& imgui_font_atlas_mutex(); - std::uint64_t imgui_fonts_build_count() const; int GetFontId(types::Font font) const; - std::shared_ptr + std::pair, units::font_size::pixels> GetImGuiFont(types::FontCategory fontCategory); QFont GetQFont(types::FontCategory fontCategory); std::shared_ptr - LoadImGuiFont(const std::string& family, - const std::vector& styles, - units::font_size::points size, - bool loadIfNotFound = true); + LoadImGuiFont(const std::string& family, + const std::vector& styles, + bool loadIfNotFound = true); void LoadApplicationFont(types::Font font, const std::string& filename); void InitializeFonts(); + static units::font_size::pixels + ImFontSize(units::font_size::pixels size); + static FontManager& Instance(); private: diff --git a/scwx-qt/source/scwx/qt/manager/log_manager.cpp b/scwx-qt/source/scwx/qt/manager/log_manager.cpp index 457c8d28..34facaa4 100644 --- a/scwx-qt/source/scwx/qt/manager/log_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/log_manager.cpp @@ -1,12 +1,14 @@ #include #include +#include +#include #include #include #include #include -#include +#include #include #include #include @@ -57,6 +59,14 @@ void LogManager::InitializeLogFile() QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) .toStdString(); p->pid_ = boost::this_process::get_id(); + if (p->pid_ == 2) + { + // The pid == 2 means that this is likely a flatpak. We assign a random + // number in this case to avoid overlap, scince it is always 2 in a + // flatpak + std::srand(std::time({})); + p->pid_ = std::rand(); + } p->logFile_ = fmt::format("{}/supercell-wx.{}.log", p->logPath_, p->pid_); // Create log directory if it doesn't exist diff --git a/scwx-qt/source/scwx/qt/manager/marker_manager.cpp b/scwx-qt/source/scwx/qt/manager/marker_manager.cpp new file mode 100644 index 00000000..8af310ec --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/marker_manager.cpp @@ -0,0 +1,524 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +static const std::string logPrefix_ = "scwx::qt::manager::marker_manager"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static const std::string kNameName_ = "name"; +static const std::string kLatitudeName_ = "latitude"; +static const std::string kLongitudeName_ = "longitude"; +static const std::string kIconName_ = "icon"; +static const std::string kIconColorName_ = "icon-color"; + +static const std::string defaultIconName = "images/location-marker"; + +class MarkerManager::Impl +{ +public: + class MarkerRecord; + + explicit Impl(MarkerManager* self) : self_ {self} {} + ~Impl() { threadPool_.join(); } + + std::string markerSettingsPath_ {""}; + std::vector> markerRecords_ {}; + std::unordered_map idToIndex_ {}; + std::unordered_map markerIcons_ {}; + + MarkerManager* self_; + + boost::asio::thread_pool threadPool_ {1u}; + std::shared_mutex markerRecordLock_ {}; + std::shared_mutex markerIconsLock_ {}; + + void InitializeMarkerSettings(); + void ReadMarkerSettings(); + void WriteMarkerSettings(); + std::shared_ptr GetMarkerByName(const std::string& name); + + bool markerFileRead_ {false}; + + void InitalizeIds(); + types::MarkerId NewId(); + types::MarkerId lastId_ {0}; +}; + +class MarkerManager::Impl::MarkerRecord +{ +public: + MarkerRecord(types::MarkerInfo info) : markerInfo_ {std::move(info)} {} + + const types::MarkerInfo& toMarkerInfo() { return markerInfo_; } + + types::MarkerInfo markerInfo_; + + friend void tag_invoke(boost::json::value_from_tag, + boost::json::value& jv, + const std::shared_ptr& record) + { + jv = {{kNameName_, record->markerInfo_.name}, + {kLatitudeName_, record->markerInfo_.latitude}, + {kLongitudeName_, record->markerInfo_.longitude}, + {kIconName_, record->markerInfo_.iconName}, + {kIconColorName_, + util::color::ToArgbString(record->markerInfo_.iconColor)}}; + } + + friend MarkerRecord tag_invoke(boost::json::value_to_tag, + const boost::json::value& jv) + { + static const boost::gil::rgba8_pixel_t defaultIconColor = + util::color::ToRgba8PixelT("#ffff0000"); + + const boost::json::object& jo = jv.as_object(); + + std::string iconName = defaultIconName; + boost::gil::rgba8_pixel_t iconColor = defaultIconColor; + + if (jo.contains(kIconName_) && jo.at(kIconName_).is_string()) + { + iconName = boost::json::value_to(jv.at(kIconName_)); + } + + if (jo.contains(kIconColorName_) && jo.at(kIconName_).is_string()) + { + try + { + iconColor = util::color::ToRgba8PixelT( + boost::json::value_to(jv.at(kIconColorName_))); + } + catch (const std::exception& ex) + { + logger_->warn( + "Could not parse color value in location-markers.json with the " + "following exception: {}", + ex.what()); + } + } + + return {types::MarkerInfo( + boost::json::value_to(jv.at(kNameName_)), + boost::json::value_to(jv.at(kLatitudeName_)), + boost::json::value_to(jv.at(kLongitudeName_)), + iconName, + iconColor)}; + } +}; + +void MarkerManager::Impl::InitalizeIds() +{ + lastId_ = 0; +} + +types::MarkerId MarkerManager::Impl::NewId() +{ + return ++lastId_; +} + +void MarkerManager::Impl::InitializeMarkerSettings() +{ + std::string appDataPath { + QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + .toStdString()}; + + if (!std::filesystem::exists(appDataPath)) + { + if (!std::filesystem::create_directories(appDataPath)) + { + logger_->error("Unable to create application data directory: \"{}\"", + appDataPath); + } + } + + markerSettingsPath_ = appDataPath + "/location-markers.json"; +} + +void MarkerManager::Impl::ReadMarkerSettings() +{ + logger_->info("Reading location marker settings"); + InitalizeIds(); + + boost::json::value markerJson = nullptr; + { + const std::unique_lock lock(markerRecordLock_); + + // Determine if marker settings exists + if (std::filesystem::exists(markerSettingsPath_)) + { + markerJson = scwx::util::json::ReadJsonFile(markerSettingsPath_); + } + + if (markerJson != nullptr && markerJson.is_array()) + { + // For each marker entry + auto& markerArray = markerJson.as_array(); + markerRecords_.reserve(markerArray.size()); + idToIndex_.reserve(markerArray.size()); + for (auto& markerEntry : markerArray) + { + try + { + auto record = boost::json::value_to(markerEntry); + + const types::MarkerId id = NewId(); + const size_t index = markerRecords_.size(); + record.markerInfo_.id = id; + markerRecords_.emplace_back( + std::make_shared(record.markerInfo_)); + idToIndex_.emplace(id, index); + + self_->add_icon(record.markerInfo_.iconName, true); + } + catch (const std::exception& ex) + { + logger_->warn("Invalid location marker entry: {}", ex.what()); + } + } + + ResourceManager::BuildAtlas(); + + logger_->debug("{} location marker entries", markerRecords_.size()); + } + } + + markerFileRead_ = true; + Q_EMIT self_->MarkersUpdated(); +} + +void MarkerManager::Impl::WriteMarkerSettings() +{ + if (!markerFileRead_) + { + return; + } + logger_->info("Saving location marker settings"); + + const std::shared_lock lock(markerRecordLock_); + auto markerJson = boost::json::value_from(markerRecords_); + scwx::util::json::WriteJsonFile(markerSettingsPath_, markerJson); +} + +std::shared_ptr +MarkerManager::Impl::GetMarkerByName(const std::string& name) +{ + for (auto& markerRecord : markerRecords_) + { + if (markerRecord->markerInfo_.name == name) + { + return markerRecord; + } + } + + return nullptr; +} + +MarkerManager::MarkerManager() : p(std::make_unique(this)) +{ + static const std::vector defaultMarkerIcons_ { + types::MarkerIconInfo(types::ImageTexture::LocationMarker, -1, -1), + types::MarkerIconInfo(types::ImageTexture::LocationPin, 6, 16), + types::MarkerIconInfo(types::ImageTexture::LocationCrosshair, -1, -1), + types::MarkerIconInfo(types::ImageTexture::LocationStar, -1, -1), + types::MarkerIconInfo(types::ImageTexture::LocationBriefcase, -1, -1), + types::MarkerIconInfo( + types::ImageTexture::LocationBuildingColumns, -1, -1), + types::MarkerIconInfo(types::ImageTexture::LocationBuilding, -1, -1), + types::MarkerIconInfo(types::ImageTexture::LocationCaravan, -1, -1), + types::MarkerIconInfo(types::ImageTexture::LocationHouse, -1, -1), + types::MarkerIconInfo(types::ImageTexture::LocationTent, -1, -1), + }; + + p->InitializeMarkerSettings(); + + boost::asio::post(p->threadPool_, + [this]() + { + try + { + // Read Marker settings on startup + main::Application::WaitForInitialization(); + { + const std::unique_lock lock(p->markerIconsLock_); + p->markerIcons_.reserve( + defaultMarkerIcons_.size()); + for (auto& icon : defaultMarkerIcons_) + { + p->markerIcons_.emplace(icon.name, icon); + } + } + p->ReadMarkerSettings(); + + Q_EMIT IconsReady(); + Q_EMIT MarkersInitialized(p->markerRecords_.size()); + } + catch (const std::exception& ex) + { + logger_->error(ex.what()); + } + }); +} + +MarkerManager::~MarkerManager() +{ + p->WriteMarkerSettings(); +} + +size_t MarkerManager::marker_count() +{ + return p->markerRecords_.size(); +} + +std::optional MarkerManager::get_marker(types::MarkerId id) +{ + const std::shared_lock lock(p->markerRecordLock_); + if (!p->idToIndex_.contains(id)) + { + return {}; + } + size_t index = p->idToIndex_[id]; + if (index >= p->markerRecords_.size()) + { + logger_->warn("id in idToIndex_ but out of range!"); + return {}; + } + std::shared_ptr& markerRecord = + p->markerRecords_[index]; + return markerRecord->toMarkerInfo(); +} + +std::optional MarkerManager::get_index(types::MarkerId id) +{ + const std::shared_lock lock(p->markerRecordLock_); + if (!p->idToIndex_.contains(id)) + { + return {}; + } + return p->idToIndex_[id]; +} + +void MarkerManager::set_marker(types::MarkerId id, + const types::MarkerInfo& marker) +{ + { + const std::unique_lock lock(p->markerRecordLock_); + if (!p->idToIndex_.contains(id)) + { + return; + } + size_t index = p->idToIndex_[id]; + if (index >= p->markerRecords_.size()) + { + logger_->warn("id in idToIndex_ but out of range!"); + return; + } + const std::shared_ptr& markerRecord = + p->markerRecords_[index]; + markerRecord->markerInfo_ = marker; + markerRecord->markerInfo_.id = id; + + add_icon(marker.iconName); + } + Q_EMIT MarkerChanged(id); + Q_EMIT MarkersUpdated(); +} + +types::MarkerId MarkerManager::add_marker(const types::MarkerInfo& marker) +{ + types::MarkerId id; + { + const std::unique_lock lock(p->markerRecordLock_); + id = p->NewId(); + size_t index = p->markerRecords_.size(); + p->idToIndex_.emplace(id, index); + p->markerRecords_.emplace_back( + std::make_shared(marker)); + p->markerRecords_[index]->markerInfo_.id = id; + + add_icon(marker.iconName); + } + Q_EMIT MarkerAdded(id); + Q_EMIT MarkersUpdated(); + return id; +} + +void MarkerManager::remove_marker(types::MarkerId id) +{ + { + const std::unique_lock lock(p->markerRecordLock_); + if (!p->idToIndex_.contains(id)) + { + return; + } + size_t index = p->idToIndex_[id]; + if (index >= p->markerRecords_.size()) + { + logger_->warn("id in idToIndex_ but out of range!"); + return; + } + + p->markerRecords_.erase(std::next(p->markerRecords_.begin(), index)); + p->idToIndex_.erase(id); + + for (auto& pair : p->idToIndex_) + { + if (pair.second > index) + { + pair.second -= 1; + } + } + } + + Q_EMIT MarkerRemoved(id); + Q_EMIT MarkersUpdated(); +} + +void MarkerManager::move_marker(size_t from, size_t to) +{ + { + const std::unique_lock lock(p->markerRecordLock_); + if (from >= p->markerRecords_.size() || to >= p->markerRecords_.size()) + { + return; + } + std::shared_ptr& markerRecord = + p->markerRecords_[from]; + + if (from == to) {} + else if (from < to) + { + for (size_t i = from; i < to; i++) + { + p->markerRecords_[i] = p->markerRecords_[i + 1]; + } + p->markerRecords_[to] = markerRecord; + } + else + { + for (size_t i = from; i > to; i--) + { + p->markerRecords_[i] = p->markerRecords_[i - 1]; + } + p->markerRecords_[to] = markerRecord; + } + } + Q_EMIT MarkersUpdated(); +} + +void MarkerManager::for_each(std::function func) +{ + const std::shared_lock lock(p->markerRecordLock_); + for (auto marker : p->markerRecords_) + { + func(marker->markerInfo_); + } +} + +void MarkerManager::add_icon(const std::string& name, bool startup) +{ + { + const std::unique_lock lock(p->markerIconsLock_); + if (p->markerIcons_.contains(name)) + { + return; + } + const std::shared_ptr image = + ResourceManager::LoadImageResource(name); + + if (image) + { + auto icon = types::MarkerIconInfo(name, -1, -1, image); + p->markerIcons_.emplace(name, icon); + } + else + { + // defaultIconName should always be in markerIcons, so at is fine + auto icon = p->markerIcons_.at(defaultIconName); + p->markerIcons_.emplace(name, icon); + } + } + + if (!startup) + { + ResourceManager::BuildAtlas(); + Q_EMIT IconAdded(name); + } +} + +std::optional +MarkerManager::get_icon(const std::string& name) +{ + const std::shared_lock lock(p->markerIconsLock_); + auto it = p->markerIcons_.find(name); + if (it != p->markerIcons_.end()) + { + return it->second; + } + + return {}; +} + +const std::unordered_map +MarkerManager::get_icons() +{ + const std::shared_lock lock(p->markerIconsLock_); + return p->markerIcons_; +} + +// Only use for testing +void MarkerManager::set_marker_settings_path(const std::string& path) +{ + p->markerSettingsPath_ = path; +} + +std::shared_ptr MarkerManager::Instance() +{ + static std::weak_ptr markerManagerReference_ {}; + static std::mutex instanceMutex_ {}; + + const std::unique_lock lock(instanceMutex_); + + std::shared_ptr markerManager = + markerManagerReference_.lock(); + + if (markerManager == nullptr) + { + markerManager = std::make_shared(); + markerManagerReference_ = markerManager; + } + + return markerManager; +} + +const std::string& MarkerManager::getDefaultIconName() +{ + return defaultIconName; +} + +} // namespace manager +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/manager/marker_manager.hpp b/scwx-qt/source/scwx/qt/manager/marker_manager.hpp new file mode 100644 index 00000000..7004e117 --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/marker_manager.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +typedef void MarkerForEachFunc(const types::MarkerInfo&); +class MarkerManager : public QObject +{ + Q_OBJECT + +public: + explicit MarkerManager(); + ~MarkerManager(); + + size_t marker_count(); + std::optional get_marker(types::MarkerId id); + std::optional get_index(types::MarkerId id); + void set_marker(types::MarkerId id, const types::MarkerInfo& marker); + types::MarkerId add_marker(const types::MarkerInfo& marker); + void remove_marker(types::MarkerId id); + void move_marker(size_t from, size_t to); + + void add_icon(const std::string& name, bool startup = false); + std::optional get_icon(const std::string& name); + const std::unordered_map get_icons(); + + void for_each(std::function func); + + // Only use for testing + void set_marker_settings_path(const std::string& path); + + static std::shared_ptr Instance(); + static const std::string& getDefaultIconName(); + +signals: + void MarkersInitialized(size_t count); + void MarkersUpdated(); + void MarkerChanged(types::MarkerId id); + void MarkerAdded(types::MarkerId id); + void MarkerRemoved(types::MarkerId id); + + void IconsReady(); + void IconAdded(std::string name); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace manager +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/manager/media_manager.cpp b/scwx-qt/source/scwx/qt/manager/media_manager.cpp index 349e73b9..014b9a37 100644 --- a/scwx-qt/source/scwx/qt/manager/media_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/media_manager.cpp @@ -5,13 +5,10 @@ #include #include #include +#include #include -namespace scwx -{ -namespace qt -{ -namespace manager +namespace scwx::qt::manager { static const std::string logPrefix_ = "scwx::qt::manager::media_manager"; @@ -20,46 +17,80 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class MediaManager::Impl { public: - explicit Impl(MediaManager* self) : - self_ {self}, - mediaDevices_ {new QMediaDevices(self)}, - mediaPlayer_ {new QMediaPlayer(self)}, - audioOutput_ {new QAudioOutput(self)} + explicit Impl() { - logger_->debug("Audio device: {}", - audioOutput_->device().description().toStdString()); + mediaParent_ = std::make_unique(); + mediaParent_->moveToThread(&thread_); - mediaPlayer_->setAudioOutput(audioOutput_); + thread_.start(); - ConnectSignals(); + QMetaObject::invokeMethod( + mediaParent_.get(), + [this]() + { + // QObjects are managed by the parent + // NOLINTBEGIN(cppcoreguidelines-owning-memory) + + logger_->debug("Creating QMediaDevices"); + mediaDevices_ = new QMediaDevices(mediaParent_.get()); + logger_->debug("Creating QMediaPlayer"); + mediaPlayer_ = new QMediaPlayer(mediaParent_.get()); + logger_->debug("Creating QAudioOutput"); + audioOutput_ = new QAudioOutput(mediaParent_.get()); + + // NOLINTEND(cppcoreguidelines-owning-memory) + + logger_->debug("Audio device: {}", + audioOutput_->device().description().toStdString()); + + mediaPlayer_->setAudioOutput(audioOutput_); + + ConnectSignals(); + }); } - ~Impl() {} + ~Impl() + { + // Delete the media parent + mediaParent_.reset(); + + thread_.quit(); + thread_.wait(); + } + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; void ConnectSignals(); - MediaManager* self_; + QThread thread_ {}; - QMediaDevices* mediaDevices_; - QMediaPlayer* mediaPlayer_; - QAudioOutput* audioOutput_; + std::unique_ptr mediaParent_ {nullptr}; + QMediaDevices* mediaDevices_ {nullptr}; + QMediaPlayer* mediaPlayer_ {nullptr}; + QAudioOutput* audioOutput_ {nullptr}; }; -MediaManager::MediaManager() : p(std::make_unique(this)) {} +MediaManager::MediaManager() : p(std::make_unique()) {} MediaManager::~MediaManager() = default; +MediaManager::MediaManager(MediaManager&&) noexcept = default; +MediaManager& MediaManager::operator=(MediaManager&&) noexcept = default; + void MediaManager::Impl::ConnectSignals() { QObject::connect( mediaDevices_, &QMediaDevices::audioOutputsChanged, - self_, + mediaParent_.get(), [this]() { audioOutput_->setDevice(QMediaDevices::defaultAudioOutput()); }); QObject::connect(audioOutput_, &QAudioOutput::deviceChanged, - self_, + mediaParent_.get(), [this]() { logger_->debug( @@ -69,7 +100,7 @@ void MediaManager::Impl::ConnectSignals() QObject::connect(mediaPlayer_, &QMediaPlayer::errorOccurred, - self_, + mediaParent_.get(), [](QMediaPlayer::Error error, const QString& errorString) { logger_->error("Error {}: {}", @@ -81,30 +112,46 @@ void MediaManager::Impl::ConnectSignals() void MediaManager::Play(types::AudioFile media) { const std::string path = types::GetMediaPath(media); + Play(path); } void MediaManager::Play(const std::string& mediaPath) { logger_->debug("Playing audio: {}", mediaPath); + if (p->mediaPlayer_ == nullptr) + { + logger_->warn("Media player is not yet initialized"); + return; + } + if (mediaPath.starts_with(':')) { - p->mediaPlayer_->setSource( + QMetaObject::invokeMethod( + p->mediaPlayer_, + &QMediaPlayer::setSource, QUrl(QString("qrc%1").arg(QString::fromStdString(mediaPath)))); } else { - p->mediaPlayer_->setSource( + QMetaObject::invokeMethod( + p->mediaPlayer_, + &QMediaPlayer::setSource, QUrl::fromLocalFile(QString::fromStdString(mediaPath))); } - p->mediaPlayer_->setPosition(0); - + QMetaObject::invokeMethod(p->mediaPlayer_, &QMediaPlayer::setPosition, 0); QMetaObject::invokeMethod(p->mediaPlayer_, &QMediaPlayer::play); } void MediaManager::Stop() { + if (p->mediaPlayer_ == nullptr) + { + logger_->warn("Media player is not yet initialized"); + return; + } + QMetaObject::invokeMethod(p->mediaPlayer_, &QMediaPlayer::stop); } @@ -126,6 +173,4 @@ std::shared_ptr MediaManager::Instance() return mediaManager; } -} // namespace manager -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::manager diff --git a/scwx-qt/source/scwx/qt/manager/media_manager.hpp b/scwx-qt/source/scwx/qt/manager/media_manager.hpp index f1d73656..c10dd041 100644 --- a/scwx-qt/source/scwx/qt/manager/media_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/media_manager.hpp @@ -4,24 +4,21 @@ #include -#include - -namespace scwx -{ -namespace qt -{ -namespace manager +namespace scwx::qt::manager { -class MediaManager : public QObject +class MediaManager { - Q_OBJECT - Q_DISABLE_COPY_MOVE(MediaManager) - public: explicit MediaManager(); ~MediaManager(); + MediaManager(const MediaManager&) = delete; + MediaManager& operator=(const MediaManager&) = delete; + + MediaManager(MediaManager&&) noexcept; + MediaManager& operator=(MediaManager&&) noexcept; + void Play(types::AudioFile media); void Play(const std::string& mediaPath); void Stop(); @@ -33,6 +30,4 @@ private: std::unique_ptr p; }; -} // namespace manager -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::manager diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp index b324fae3..9685f33f 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.cpp @@ -2,10 +2,10 @@ #include #include #include -#include #include #include #include +#include #include #include @@ -52,8 +52,7 @@ public: void ReadPlacefileSettings(); void WritePlacefileSettings(); - static boost::unordered_flat_map> + static FontMap LoadFontResources(const std::shared_ptr& placefile); static std::vector> LoadImageResources(const std::shared_ptr& placefile); @@ -70,6 +69,8 @@ public: boost::unordered_flat_map> placefileRecordMap_ {}; std::shared_mutex placefileRecordLock_ {}; + + bool placefileSettingsRead_ {false}; }; class PlacefileManager::Impl::PlacefileRecord @@ -93,6 +94,7 @@ public: { std::unique_lock refreshLock(refreshMutex_); std::unique_lock timerLock(timerMutex_); + enabled_ = false; refreshTimer_.cancel(); timerLock.unlock(); refreshLock.unlock(); @@ -105,6 +107,8 @@ public: void CancelRefresh(); void ScheduleRefresh(); + void ScheduleRefresh( + const std::chrono::system_clock::duration timeUntilNextUpdate); void Update(); void UpdateAsync(); @@ -142,14 +146,15 @@ public: std::mutex refreshMutex_ {}; std::mutex timerMutex_ {}; - boost::unordered_flat_map> - fonts_ {}; + FontMap fonts_ {}; std::mutex fontsMutex_ {}; std::vector> images_ {}; std::string lastRadarSite_ {}; std::chrono::system_clock::time_point lastUpdateTime_ {}; + + std::size_t failureCount_ {}; }; PlacefileManager::PlacefileManager() : p(std::make_unique(this)) @@ -228,7 +233,7 @@ PlacefileManager::placefile(const std::string& name) return nullptr; } -boost::unordered_flat_map> +PlacefileManager::FontMap PlacefileManager::placefile_fonts(const std::string& name) { std::shared_lock lock(p->placefileRecordLock_); @@ -344,8 +349,8 @@ PlacefileManager::Impl::PlacefileRecord::refresh_time() const if (refresh_enabled()) { - // Don't refresh more often than every 15 seconds - return std::max(placefile_->refresh(), 15s); + // Don't refresh more often than every 1 second + return std::max(placefile_->refresh(), 1s); } return -1s; @@ -378,7 +383,7 @@ void PlacefileManager::Impl::ReadPlacefileSettings() // Determine if placefile settings exists if (std::filesystem::exists(placefileSettingsPath_)) { - placefileJson = util::json::ReadJsonFile(placefileSettingsPath_); + placefileJson = scwx::util::json::ReadJsonFile(placefileSettingsPath_); } // If placefile settings was successfully read @@ -408,15 +413,20 @@ void PlacefileManager::Impl::ReadPlacefileSettings() } } } + placefileSettingsRead_ = true; } void PlacefileManager::Impl::WritePlacefileSettings() { + if (!placefileSettingsRead_) + { + return; + } logger_->info("Saving placefile settings"); std::shared_lock lock {placefileRecordLock_}; auto placefileJson = boost::json::value_from(placefileRecords_); - util::json::WriteJsonFile(placefileSettingsPath_, placefileJson); + scwx::util::json::WriteJsonFile(placefileSettingsPath_, placefileJson); } void PlacefileManager::SetRadarSite( @@ -542,6 +552,11 @@ void PlacefileManager::Impl::PlacefileRecord::Update() if (url.isLocalFile()) { updatedPlacefile = gr::Placefile::Load(name); + + if (updatedPlacefile == nullptr) + { + logger_->error("Local placefile not found: {}", name); + } } else { @@ -625,6 +640,7 @@ void PlacefileManager::Impl::PlacefileRecord::Update() placefile_ = updatedPlacefile; title_ = placefile_->title(); lastUpdateTime_ = std::chrono::system_clock::now(); + failureCount_ = 0; // Update font resources { @@ -645,10 +661,30 @@ void PlacefileManager::Impl::PlacefileRecord::Update() // Notify slots of the placefile update Q_EMIT p->self_->PlacefileUpdated(name); } - } - // Update refresh timer - ScheduleRefresh(); + // Update refresh timer + ScheduleRefresh(); + } + else if (enabled_) + { + using namespace std::chrono_literals; + + ++failureCount_; + + // Update refresh timer if the file failed to load, in case it is able to + // be resolved later + if (url.isLocalFile()) + { + ScheduleRefresh(10s); + } + else + { + // Start attempting to refresh at 15 seconds, and start backing off + // until retrying every 60 seconds + ScheduleRefresh( + std::min(15s * failureCount_, 60s)); + } + } } void PlacefileManager::Impl::PlacefileRecord::ScheduleRefresh() @@ -666,6 +702,12 @@ void PlacefileManager::Impl::PlacefileRecord::ScheduleRefresh() auto nextUpdateTime = lastUpdateTime_ + refresh_time(); auto timeUntilNextUpdate = nextUpdateTime - std::chrono::system_clock::now(); + ScheduleRefresh(timeUntilNextUpdate); +} + +void PlacefileManager::Impl::PlacefileRecord::ScheduleRefresh( + const std::chrono::system_clock::duration timeUntilNextUpdate) +{ logger_->debug( "Scheduled refresh in {:%M:%S} ({})", std::chrono::duration_cast(timeUntilNextUpdate), @@ -731,13 +773,11 @@ std::shared_ptr PlacefileManager::Instance() return placefileManager; } -boost::unordered_flat_map> -PlacefileManager::Impl::LoadFontResources( +PlacefileManager::FontMap PlacefileManager::Impl::LoadFontResources( const std::shared_ptr& placefile) { - boost::unordered_flat_map> - imGuiFonts {}; - auto fonts = placefile->fonts(); + FontMap imGuiFonts {}; + auto fonts = placefile->fonts(); for (auto& font : fonts) { @@ -753,9 +793,11 @@ PlacefileManager::Impl::LoadFontResources( styles.push_back("italic"); } - auto imGuiFont = FontManager::Instance().LoadImGuiFont( - font.second->face_, styles, size); - imGuiFonts.emplace(font.first, std::move(imGuiFont)); + auto imGuiFont = + FontManager::Instance().LoadImGuiFont(font.second->face_, styles); + imGuiFonts.emplace( + font.first, + std::make_pair(std::move(imGuiFont), FontManager::ImFontSize(size))); } return imGuiFonts; diff --git a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp index 17b39b2f..4408fb2f 100644 --- a/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/placefile_manager.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -22,12 +23,16 @@ public: explicit PlacefileManager(); ~PlacefileManager(); + using FontMap = + boost::unordered_flat_map, + units::font_size::pixels>>; + bool placefile_enabled(const std::string& name); bool placefile_thresholded(const std::string& name); std::string placefile_title(const std::string& name); std::shared_ptr placefile(const std::string& name); - boost::unordered_flat_map> - placefile_fonts(const std::string& name); + FontMap placefile_fonts(const std::string& name); void set_placefile_enabled(const std::string& name, bool enabled); void set_placefile_thresholded(const std::string& name, bool thresholded); diff --git a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp index 595cda6d..bda6e232 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -4,17 +4,19 @@ #include #include #include +#include #include #include #include #include +#include #include -#include #include #include #include #include +#include #if defined(_MSC_VER) # pragma warning(push, 0) @@ -28,6 +30,7 @@ #include #include #include +#include #if defined(_MSC_VER) # pragma warning(pop) @@ -63,8 +66,12 @@ static constexpr uint32_t NUM_COORIDNATES_1_DEGREE = static const std::string kDefaultLevel3Product_ {"N0B"}; +static constexpr std::size_t kTimerPlaces_ {6u}; + static constexpr std::chrono::seconds kFastRetryInterval_ {15}; +static constexpr std::chrono::seconds kFastRetryIntervalChunks_ {3}; static constexpr std::chrono::seconds kSlowRetryInterval_ {120}; +static constexpr std::chrono::seconds kSlowRetryIntervalChunks_ {20}; static std::unordered_map> instanceMap_; @@ -82,27 +89,25 @@ class ProviderManager : public QObject Q_OBJECT public: explicit ProviderManager(RadarProductManager* self, - const std::string& radarId, - common::RadarProductGroup group) : - ProviderManager(self, radarId, group, "???") - { - } - explicit ProviderManager(RadarProductManager* self, - const std::string& radarId, + std::string radarId, common::RadarProductGroup group, - const std::string& product) : - radarId_ {radarId}, + std::string product = "???", + bool isChunks = false) : + radarId_ {std::move(radarId)}, group_ {group}, - product_ {product}, - refreshEnabled_ {false}, - refreshTimer_ {threadPool_}, - refreshTimerMutex_ {}, - provider_ {nullptr} + product_ {std::move(product)}, + isChunks_ {isChunks} { connect(this, &ProviderManager::NewDataAvailable, self, - &RadarProductManager::NewDataAvailable); + [this, self](common::RadarProductGroup group, + const std::string& product, + std::chrono::system_clock::time_point latestTime) + { + Q_EMIT self->NewDataAvailable( + group, product, isChunks_, latestTime); + }); } ~ProviderManager() { threadPool_.join(); }; @@ -115,10 +120,12 @@ public: const std::string radarId_; const common::RadarProductGroup group_; const std::string product_; - bool refreshEnabled_; - boost::asio::steady_timer refreshTimer_; - std::mutex refreshTimerMutex_; - std::shared_ptr provider_; + const bool isChunks_; + bool refreshEnabled_ {false}; + boost::asio::steady_timer refreshTimer_ {threadPool_}; + std::mutex refreshTimerMutex_ {}; + std::shared_ptr provider_ {nullptr}; + size_t refreshCount_ {0}; signals: void NewDataAvailable(common::RadarProductGroup group, @@ -136,24 +143,10 @@ public: initialized_ {false}, level3ProductsInitialized_ {false}, radarSite_ {config::RadarSite::Get(radarId)}, - coordinates0_5Degree_ {}, - coordinates1Degree_ {}, - level2ProductRecords_ {}, - level2ProductRecentRecords_ {}, - level3ProductRecordsMap_ {}, - level3ProductRecentRecordsMap_ {}, - level2ProductRecordMutex_ {}, - level3ProductRecordMutex_ {}, level2ProviderManager_ {std::make_shared( self_, radarId_, common::RadarProductGroup::Level2)}, - level3ProviderManagerMap_ {}, - level3ProviderManagerMutex_ {}, - initializeMutex_ {}, - level3ProductsInitializeMutex_ {}, - loadLevel2DataMutex_ {}, - loadLevel3DataMutex_ {}, - availableCategoryMap_ {}, - availableCategoryMutex_ {} + level2ChunksProviderManager_ {std::make_shared( + self_, radarId_, common::RadarProductGroup::Level2, "???", true)} { if (radarSite_ == nullptr) { @@ -163,13 +156,27 @@ public: level2ProviderManager_->provider_ = provider::NexradDataProviderFactory::CreateLevel2DataProvider(radarId); + level2ChunksProviderManager_->provider_ = + provider::NexradDataProviderFactory::CreateLevel2ChunksDataProvider( + radarId); + + auto level2ChunksProvider = + std::dynamic_pointer_cast( + level2ChunksProviderManager_->provider_); + if (level2ChunksProvider != nullptr) + { + level2ChunksProvider->SetLevel2DataProvider( + std::dynamic_pointer_cast( + level2ProviderManager_->provider_)); + } } ~RadarProductManagerImpl() { level2ProviderManager_->Disable(); + level2ChunksProviderManager_->Disable(); std::shared_lock lock(level3ProviderManagerMutex_); - std::for_each(std::execution::par_unseq, + std::for_each(std::execution::par, level3ProviderManagerMap_.begin(), level3ProviderManagerMap_.end(), [](auto& p) @@ -192,15 +199,16 @@ public: std::shared_ptr GetLevel3ProviderManager(const std::string& product); - void EnableRefresh(boost::uuids::uuid uuid, - std::shared_ptr providerManager, - bool enabled); + void EnableRefresh( + boost::uuids::uuid uuid, + const std::set>& providerManagers, + bool enabled); void RefreshData(std::shared_ptr providerManager); void RefreshDataSync(std::shared_ptr providerManager); - std::tuple, - std::chrono::system_clock::time_point> - GetLevel2ProductRecord(std::chrono::system_clock::time_point time); + std::map> + GetLevel2ProductRecords(std::chrono::system_clock::time_point time); std::tuple, std::chrono::system_clock::time_point> GetLevel3ProductRecord(const std::string& product, @@ -228,6 +236,13 @@ public: void UpdateAvailableProductsSync(); + void + CalculateCoordinates(const boost::integer_range& radialGates, + const units::angle::degrees radialAngle, + const units::angle::degrees angleOffset, + const float gateRangeOffset, + std::vector& outputCoordinates); + static void PopulateProductTimes(std::shared_ptr providerManager, RadarProductRecordMap& productRecordMap, @@ -243,37 +258,43 @@ public: const std::string radarId_; bool initialized_; bool level3ProductsInitialized_; + bool level3AvailabilityReady_ {false}; std::shared_ptr radarSite_; std::size_t cacheLimit_ {6u}; - std::vector coordinates0_5Degree_; - std::vector coordinates1Degree_; + std::vector coordinates0_5Degree_ {}; + std::vector coordinates0_5DegreeSmooth_ {}; + std::vector coordinates1Degree_ {}; + std::vector coordinates1DegreeSmooth_ {}; - RadarProductRecordMap level2ProductRecords_; - RadarProductRecordList level2ProductRecentRecords_; + RadarProductRecordMap level2ProductRecords_ {}; + RadarProductRecordList level2ProductRecentRecords_ {}; std::unordered_map - level3ProductRecordsMap_; + level3ProductRecordsMap_ {}; std::unordered_map - level3ProductRecentRecordsMap_; - std::shared_mutex level2ProductRecordMutex_; - std::shared_mutex level3ProductRecordMutex_; + level3ProductRecentRecordsMap_ {}; + std::shared_mutex level2ProductRecordMutex_ {}; + std::shared_mutex level3ProductRecordMutex_ {}; std::shared_ptr level2ProviderManager_; + std::shared_ptr level2ChunksProviderManager_; std::unordered_map> - level3ProviderManagerMap_; - std::shared_mutex level3ProviderManagerMutex_; + level3ProviderManagerMap_ {}; + std::shared_mutex level3ProviderManagerMutex_ {}; - std::mutex initializeMutex_; - std::mutex level3ProductsInitializeMutex_; - std::mutex loadLevel2DataMutex_; - std::mutex loadLevel3DataMutex_; + std::mutex initializeMutex_ {}; + std::mutex level3ProductsInitializeMutex_ {}; + std::mutex loadLevel2DataMutex_ {}; + std::mutex loadLevel3DataMutex_ {}; - common::Level3ProductCategoryMap availableCategoryMap_; - std::shared_mutex availableCategoryMutex_; + common::Level3ProductCategoryMap availableCategoryMap_ {}; + std::shared_mutex availableCategoryMutex_ {}; + + std::optional incomingLevel2Elevation_ {}; std::unordered_map, + std::set>, boost::hash> refreshMap_ {}; std::shared_mutex refreshMapMutex_ {}; @@ -383,14 +404,29 @@ void RadarProductManager::DumpRecords() } const std::vector& -RadarProductManager::coordinates(common::RadialSize radialSize) const +RadarProductManager::coordinates(common::RadialSize radialSize, + bool smoothingEnabled) const { switch (radialSize) { case common::RadialSize::_0_5Degree: - return p->coordinates0_5Degree_; + if (smoothingEnabled) + { + return p->coordinates0_5DegreeSmooth_; + } + else + { + return p->coordinates0_5Degree_; + } case common::RadialSize::_1Degree: - return p->coordinates1Degree_; + if (smoothingEnabled) + { + return p->coordinates1DegreeSmooth_; + } + else + { + return p->coordinates1Degree_; + } default: throw std::invalid_argument("Invalid radial size"); } @@ -413,7 +449,7 @@ const scwx::util::time_zone* RadarProductManager::default_time_zone() const } case types::DefaultTimeZone::Local: -#if defined(_MSC_VER) +#if (__cpp_lib_chrono >= 201907L) return std::chrono::current_zone(); #else return date::current_zone(); @@ -424,9 +460,21 @@ const scwx::util::time_zone* RadarProductManager::default_time_zone() const } } +bool RadarProductManager::is_tdwr() const +{ + return p->radarSite_->type() == "tdwr"; +} + float RadarProductManager::gate_size() const { - return (p->radarSite_->type() == "tdwr") ? 150.0f : 250.0f; + // tdwr is 150 meter per gate, wsr88d is 250 meter per gate + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) + return (is_tdwr()) ? 150.0f : 250.0f; +} + +std::optional RadarProductManager::incoming_level_2_elevation() const +{ + return p->incomingLevel2Elevation_; } std::string RadarProductManager::radar_id() const @@ -450,52 +498,59 @@ void RadarProductManager::Initialize() logger_->debug("Initialize()"); + if (is_tdwr()) + { + p->initialized_ = true; + return; + } + boost::timer::cpu_timer timer; - const GeographicLib::Geodesic& geodesic( - util::GeographicLib::DefaultGeodesic()); - - const QMapLibre::Coordinate radar(p->radarSite_->latitude(), - p->radarSite_->longitude()); - - const float gateSize = gate_size(); - // Calculate half degree azimuth coordinates timer.start(); std::vector& coordinates0_5Degree = p->coordinates0_5Degree_; coordinates0_5Degree.resize(NUM_COORIDNATES_0_5_DEGREE); - auto radialGates0_5Degree = + const auto radialGates0_5Degree = boost::irange(0, NUM_RADIAL_GATES_0_5_DEGREE); - std::for_each( - std::execution::par_unseq, - radialGates0_5Degree.begin(), - radialGates0_5Degree.end(), - [&](uint32_t radialGate) - { - const uint16_t gate = - static_cast(radialGate % common::MAX_DATA_MOMENT_GATES); - const uint16_t radial = - static_cast(radialGate / common::MAX_DATA_MOMENT_GATES); + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers): Values are given + // descriptions + p->CalculateCoordinates( + radialGates0_5Degree, + units::angle::degrees {0.5f}, // Radial angle + units::angle::degrees {0.0f}, // Angle offset + // Far end of the first gate is the gate size distance from the radar site + 1.0f, + coordinates0_5Degree); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) - const float angle = radial * 0.5f; // 0.5 degree radial - const float range = (gate + 1) * gateSize; - const size_t offset = radialGate * 2; - - double latitude; - double longitude; - - geodesic.Direct( - radar.first, radar.second, angle, range, latitude, longitude); - - coordinates0_5Degree[offset] = latitude; - coordinates0_5Degree[offset + 1] = longitude; - }); timer.stop(); logger_->debug("Coordinates (0.5 degree) calculated in {}", - timer.format(6, "%ws")); + timer.format(kTimerPlaces_, "%ws")); + + // Calculate half degree smooth azimuth coordinates + timer.start(); + std::vector& coordinates0_5DegreeSmooth = + p->coordinates0_5DegreeSmooth_; + + coordinates0_5DegreeSmooth.resize(NUM_COORIDNATES_0_5_DEGREE); + + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers): Values are given + // descriptions + p->CalculateCoordinates(radialGates0_5Degree, + units::angle::degrees {0.5f}, // Radial angle + units::angle::degrees {0.25f}, // Angle offset + // Center of the first gate is half the gate size + // distance from the radar site + 0.5f, + coordinates0_5DegreeSmooth); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + + timer.stop(); + logger_->debug("Coordinates (0.5 degree smooth) calculated in {}", + timer.format(kTimerPlaces_, "%ws")); // Calculate 1 degree azimuth coordinates timer.start(); @@ -503,38 +558,89 @@ void RadarProductManager::Initialize() coordinates1Degree.resize(NUM_COORIDNATES_1_DEGREE); - auto radialGates1Degree = + const auto radialGates1Degree = boost::irange(0, NUM_RADIAL_GATES_1_DEGREE); + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers): Values are given + // descriptions + p->CalculateCoordinates( + radialGates1Degree, + units::angle::degrees {1.0f}, // Radial angle + units::angle::degrees {0.0f}, // Angle offset + // Far end of the first gate is the gate size distance from the radar site + 1.0f, + coordinates1Degree); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + + timer.stop(); + logger_->debug("Coordinates (1 degree) calculated in {}", + timer.format(kTimerPlaces_, "%ws")); + + // Calculate 1 degree smooth azimuth coordinates + timer.start(); + std::vector& coordinates1DegreeSmooth = p->coordinates1DegreeSmooth_; + + coordinates1DegreeSmooth.resize(NUM_COORIDNATES_1_DEGREE); + + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers): Values are given + // descriptions + p->CalculateCoordinates(radialGates1Degree, + units::angle::degrees {1.0f}, // Radial angle + units::angle::degrees {0.5f}, // Angle offset + // Center of the first gate is half the gate size + // distance from the radar site + 0.5f, + coordinates1DegreeSmooth); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + + timer.stop(); + logger_->debug("Coordinates (1 degree smooth) calculated in {}", + timer.format(kTimerPlaces_, "%ws")); + + p->initialized_ = true; +} + +void RadarProductManagerImpl::CalculateCoordinates( + const boost::integer_range& radialGates, + const units::angle::degrees radialAngle, + const units::angle::degrees angleOffset, + const float gateRangeOffset, + std::vector& outputCoordinates) +{ + const GeographicLib::Geodesic& geodesic( + util::GeographicLib::DefaultGeodesic()); + + const QMapLibre::Coordinate radar(radarSite_->latitude(), + radarSite_->longitude()); + + const float gateSize = self_->gate_size(); + std::for_each( std::execution::par_unseq, - radialGates1Degree.begin(), - radialGates1Degree.end(), + radialGates.begin(), + radialGates.end(), [&](uint32_t radialGate) { - const uint16_t gate = - static_cast(radialGate % common::MAX_DATA_MOMENT_GATES); - const uint16_t radial = - static_cast(radialGate / common::MAX_DATA_MOMENT_GATES); + const auto gate = static_cast( + radialGate % common::MAX_DATA_MOMENT_GATES); + const auto radial = static_cast( + radialGate / common::MAX_DATA_MOMENT_GATES); - const float angle = radial * 1.0f; // 1 degree radial - const float range = (gate + 1) * gateSize; - const size_t offset = radialGate * 2; + const float angle = static_cast(radial) * radialAngle.value() + + angleOffset.value(); + const float range = + (static_cast(gate) + gateRangeOffset) * gateSize; + const std::size_t offset = static_cast(radialGate) * 2; - double latitude; - double longitude; + double latitude = 0.0; + double longitude = 0.0; geodesic.Direct( radar.first, radar.second, angle, range, latitude, longitude); - coordinates1Degree[offset] = latitude; - coordinates1Degree[offset + 1] = longitude; + outputCoordinates[offset] = static_cast(latitude); + outputCoordinates[offset + 1] = static_cast(longitude); }); - timer.stop(); - logger_->debug("Coordinates (1 degree) calculated in {}", - timer.format(6, "%ws")); - - p->initialized_ = true; } std::shared_ptr @@ -567,85 +673,90 @@ void RadarProductManager::EnableRefresh(common::RadarProductGroup group, { if (group == common::RadarProductGroup::Level2) { - p->EnableRefresh(uuid, p->level2ProviderManager_, enabled); + p->EnableRefresh( + uuid, + {p->level2ProviderManager_, p->level2ChunksProviderManager_}, + enabled); } else { - std::shared_ptr providerManager = + const std::shared_ptr providerManager = p->GetLevel3ProviderManager(product); // Only enable refresh on available products - boost::asio::post( - p->threadPool_, - [=, this]() - { - try + if (enabled) + { + boost::asio::post( + p->threadPool_, + [providerManager, product, uuid, enabled, this]() { - providerManager->provider_->RequestAvailableProducts(); - auto availableProducts = - providerManager->provider_->GetAvailableProducts(); - - if (std::find(std::execution::par_unseq, - availableProducts.cbegin(), - availableProducts.cend(), - product) != availableProducts.cend()) + try { - p->EnableRefresh(uuid, providerManager, enabled); + providerManager->provider_->RequestAvailableProducts(); + const auto availableProducts = + providerManager->provider_->GetAvailableProducts(); + + if (std::find(std::execution::par, + availableProducts.cbegin(), + availableProducts.cend(), + product) != availableProducts.cend()) + { + p->EnableRefresh(uuid, {providerManager}, enabled); + } } - } - catch (const std::exception& ex) - { - logger_->error(ex.what()); - } - }); + catch (const std::exception& ex) + { + logger_->error(ex.what()); + } + }); + } + else + { + p->EnableRefresh(uuid, {providerManager}, enabled); + } } } void RadarProductManagerImpl::EnableRefresh( - boost::uuids::uuid uuid, - std::shared_ptr providerManager, - bool enabled) + boost::uuids::uuid uuid, + const std::set>& providerManagers, + bool enabled) { // Lock the refresh map std::unique_lock lock {refreshMapMutex_}; - auto currentProviderManager = refreshMap_.find(uuid); - if (currentProviderManager != refreshMap_.cend()) + auto currentProviderManagers = refreshMap_.find(uuid); + if (currentProviderManagers != refreshMap_.cend()) { - // If the enabling refresh for a different product, or disabling refresh - if (currentProviderManager->second != providerManager || !enabled) + for (const auto& currentProviderManager : currentProviderManagers->second) { - // Determine number of entries in the map for the current provider - // manager - auto currentProviderManagerCount = std::count_if( - refreshMap_.cbegin(), - refreshMap_.cend(), - [&](const auto& provider) - { return provider.second == currentProviderManager->second; }); - - // If this is the last reference to the provider in the refresh map - if (currentProviderManagerCount == 1) + currentProviderManager->refreshCount_ -= 1; + // If the enabling refresh for a different product, or disabling + // refresh + if (!providerManagers.contains(currentProviderManager) || !enabled) { - // Disable current provider - currentProviderManager->second->Disable(); - } - - // Dissociate uuid from current provider manager - refreshMap_.erase(currentProviderManager); - - // If we are enabling a new provider manager - if (enabled) - { - // Associate uuid to providerManager - refreshMap_.emplace(uuid, providerManager); + // If this is the last reference to the provider in the refresh map + if (currentProviderManager->refreshCount_ == 0) + { + // Disable current provider + currentProviderManager->Disable(); + } } } + + // Dissociate uuid from current provider managers + refreshMap_.erase(currentProviderManagers); } - else if (enabled) + + if (enabled) { - // We are enabling a new provider manager + // We are enabling provider managers // Associate uuid to provider manager - refreshMap_.emplace(uuid, providerManager); + refreshMap_.emplace(uuid, providerManagers); + for (const auto& providerManager : providerManagers) + { + providerManager->refreshCount_ += 1; + } } // Release the refresh map mutex @@ -653,13 +764,15 @@ void RadarProductManagerImpl::EnableRefresh( // We have already handled a disable request by this point. If enabling, and // the provider manager refresh isn't already enabled, enable it. - if (enabled && providerManager->refreshEnabled_ != enabled) + if (enabled) { - providerManager->refreshEnabled_ = enabled; - - if (enabled) + for (const auto& providerManager : providerManagers) { - RefreshData(providerManager); + if (providerManager->refreshEnabled_ != enabled) + { + providerManager->refreshEnabled_ = enabled; + RefreshData(providerManager); + } } } } @@ -667,7 +780,7 @@ void RadarProductManagerImpl::EnableRefresh( void RadarProductManagerImpl::RefreshData( std::shared_ptr providerManager) { - logger_->debug("RefreshData: {}", providerManager->name()); + logger_->trace("RefreshData: {}", providerManager->name()); { std::unique_lock lock(providerManager->refreshTimerMutex_); @@ -695,16 +808,21 @@ void RadarProductManagerImpl::RefreshDataSync( auto [newObjects, totalObjects] = providerManager->provider_->Refresh(); - std::chrono::milliseconds interval = kFastRetryInterval_; + // Level2 chunked data is updated quickly and uses a faster interval + const std::chrono::milliseconds fastRetryInterval = + providerManager->isChunks_ ? kFastRetryIntervalChunks_ : + kFastRetryInterval_; + const std::chrono::milliseconds slowRetryInterval = + providerManager->isChunks_ ? kSlowRetryIntervalChunks_ : + kSlowRetryInterval_; + std::chrono::milliseconds interval = fastRetryInterval; if (totalObjects > 0) { - std::string key = providerManager->provider_->FindLatestKey(); - auto latestTime = providerManager->provider_->GetTimePointByKey(key); - + auto latestTime = providerManager->provider_->FindLatestTime(); auto updatePeriod = providerManager->provider_->update_period(); auto lastModified = providerManager->provider_->last_modified(); - auto sinceLastModified = std::chrono::system_clock::now() - lastModified; + auto sinceLastModified = scwx::util::time::now() - lastModified; // For the default interval, assume products are updated at a // constant rate. Expect the next product at a time based on the @@ -716,12 +834,12 @@ void RadarProductManagerImpl::RefreshDataSync( { // If it has been at least 5 update periods since the file has // been last modified, slow the retry period - interval = kSlowRetryInterval_; + interval = slowRetryInterval; } - else if (interval < std::chrono::milliseconds {kFastRetryInterval_}) + else if (interval < std::chrono::milliseconds {fastRetryInterval}) { // The interval should be no quicker than the fast retry interval - interval = kFastRetryInterval_; + interval = fastRetryInterval; } if (newObjects > 0) @@ -735,14 +853,14 @@ void RadarProductManagerImpl::RefreshDataSync( logger_->info("[{}] No data found", providerManager->name()); // If no data is found, retry at the slow retry interval - interval = kSlowRetryInterval_; + interval = slowRetryInterval; } + std::unique_lock const lock(providerManager->refreshTimerMutex_); + if (providerManager->refreshEnabled_) { - std::unique_lock lock(providerManager->refreshTimerMutex_); - - logger_->debug( + logger_->trace( "[{}] Scheduled refresh in {:%M:%S}", providerManager->name(), std::chrono::duration_cast(interval)); @@ -791,10 +909,13 @@ RadarProductManager::GetActiveVolumeTimes( std::shared_lock refreshLock {p->refreshMapMutex_}; // For each entry in the refresh map (refresh is enabled) - for (auto& refreshEntry : p->refreshMap_) + for (auto& refreshSet : p->refreshMap_) { - // Add the provider for the current entry - providers.insert(refreshEntry.second->provider_); + for (const auto& refreshEntry : refreshSet.second) + { + // Add the provider for the current entry + providers.insert(refreshEntry->provider_); + } } // Unlock the refresh map @@ -807,19 +928,19 @@ RadarProductManager::GetActiveVolumeTimes( // For each provider (in parallel) std::for_each( - std::execution::par_unseq, + std::execution::par, providers.begin(), providers.end(), [&](const std::shared_ptr& provider) { // For yesterday, today and tomorrow (in parallel) - std::for_each(std::execution::par_unseq, + std::for_each(std::execution::par, dates.begin(), dates.end(), [&](const auto& date) { // Don't query for a time point in the future - if (date > std::chrono::system_clock::now()) + if (date > scwx::util::time::now()) { return; } @@ -853,7 +974,7 @@ void RadarProductManagerImpl::LoadProviderData( std::mutex& loadDataMutex, const std::shared_ptr& request) { - logger_->debug("LoadProviderData: {}, {}", + logger_->trace("LoadProviderData: {}, {}", providerManager->name(), scwx::util::TimeString(time)); @@ -873,7 +994,7 @@ void RadarProductManagerImpl::LoadProviderData( if (existingRecord != nullptr) { - logger_->debug( + logger_->trace( "Data previously loaded, loading from data cache"); } } @@ -881,13 +1002,8 @@ void RadarProductManagerImpl::LoadProviderData( if (existingRecord == nullptr) { - std::string key = providerManager->provider_->FindKey(time); - - if (!key.empty()) - { - nexradFile = providerManager->provider_->LoadObjectByKey(key); - } - else + nexradFile = providerManager->provider_->LoadObjectByTime(time); + if (nexradFile == nullptr) { logger_->warn("Attempting to load object without key: {}", scwx::util::TimeString(time)); @@ -909,7 +1025,7 @@ void RadarProductManager::LoadLevel2Data( std::chrono::system_clock::time_point time, const std::shared_ptr& request) { - logger_->debug("LoadLevel2Data: {}", scwx::util::TimeString(time)); + logger_->trace("LoadLevel2Data: {}", scwx::util::TimeString(time)); p->LoadProviderData(time, p->level2ProviderManager_, @@ -1093,6 +1209,10 @@ void RadarProductManagerImpl::PopulateLevel2ProductTimes( level2ProductRecords_, level2ProductRecordMutex_, time); + PopulateProductTimes(level2ChunksProviderManager_, + level2ProductRecords_, + level2ProductRecordMutex_, + time); } void RadarProductManagerImpl::PopulateLevel3ProductTimes( @@ -1134,13 +1254,13 @@ void RadarProductManagerImpl::PopulateProductTimes( std::mutex volumeTimesMutex {}; // For yesterday, today and tomorrow (in parallel) - std::for_each(std::execution::par_unseq, + std::for_each(std::execution::par, dates.begin(), dates.end(), [&](const auto& date) { // Don't query for a time point in the future - if (date > std::chrono::system_clock::now()) + if (date > scwx::util::time::now()) { return; } @@ -1173,60 +1293,91 @@ void RadarProductManagerImpl::PopulateProductTimes( }); } -std::tuple, - std::chrono::system_clock::time_point> -RadarProductManagerImpl::GetLevel2ProductRecord( +std::map> +RadarProductManagerImpl::GetLevel2ProductRecords( std::chrono::system_clock::time_point time) { - std::shared_ptr record {nullptr}; - RadarProductRecordMap::const_pointer recordPtr {nullptr}; - std::chrono::system_clock::time_point recordTime {time}; + std::map> + records {}; + std::vector recordPtrs {}; // Ensure Level 2 product records are updated PopulateLevel2ProductTimes(time); - if (!level2ProductRecords_.empty() && - time == std::chrono::system_clock::time_point {}) { - // If a default-initialized time point is given, return the latest record - recordPtr = &(*level2ProductRecords_.rbegin()); - } - else - { - recordPtr = - scwx::util::GetBoundedElementPointer(level2ProductRecords_, time); - } + std::shared_lock lock {level2ProductRecordMutex_}; - if (recordPtr != nullptr) - { - // Don't check for an exact time match for level 2 products - recordTime = recordPtr->first; - record = recordPtr->second.lock(); - } + if (!level2ProductRecords_.empty() && + time == std::chrono::system_clock::time_point {}) + { + // If a default-initialized time point is given, return the latest + // record + recordPtrs.push_back(&(*level2ProductRecords_.rbegin())); + } + else + { + // Get the requested record + auto recordIt = + scwx::util::GetBoundedElementIterator(level2ProductRecords_, time); - if (recordPtr != nullptr && record == nullptr && - recordTime != std::chrono::system_clock::time_point {}) - { - // Product is expired, reload it - std::shared_ptr request = - std::make_shared(radarId_); - - QObject::connect( - request.get(), - &request::NexradFileRequest::RequestComplete, - self_, - [this](std::shared_ptr request) + if (recordIt != level2ProductRecords_.cend()) { - if (request->radar_product_record() != nullptr) - { - Q_EMIT self_->DataReloaded(request->radar_product_record()); - } - }); + recordPtrs.push_back(&(*(recordIt))); - self_->LoadLevel2Data(recordTime, request); + // The requested time may be in the previous record, so get that too + if (recordIt != level2ProductRecords_.cbegin()) + { + recordPtrs.push_back(&(*(--recordIt))); + } + } + } } - return {record, recordTime}; + // For each record pointer + for (auto& recordPtr : recordPtrs) + { + std::shared_ptr record {nullptr}; + std::chrono::system_clock::time_point recordTime {time}; + + if (recordPtr != nullptr) + { + // Don't check for an exact time match for level 2 products + recordTime = recordPtr->first; + record = recordPtr->second.lock(); + } + + if (recordPtr != nullptr && record == nullptr && + recordTime != std::chrono::system_clock::time_point {}) + { + // Product is expired, reload it + std::shared_ptr request = + std::make_shared(radarId_); + + QObject::connect( + request.get(), + &request::NexradFileRequest::RequestComplete, + self_, + [this](std::shared_ptr request) + { + if (request->radar_product_record() != nullptr) + { + Q_EMIT self_->DataReloaded(request->radar_product_record()); + } + }); + + self_->LoadLevel2Data(recordTime, request); + } + + if (record != nullptr) + { + // Return valid records + records.insert_or_assign(recordTime, record); + } + } + + return records; } std::tuple, @@ -1298,7 +1449,7 @@ std::shared_ptr RadarProductManagerImpl::StoreRadarProductRecord( std::shared_ptr record) { - logger_->debug("StoreRadarProductRecord()"); + logger_->trace("StoreRadarProductRecord()"); std::shared_ptr storedRecord = nullptr; @@ -1399,19 +1550,96 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType, { std::shared_ptr radarData = nullptr; float elevationCut = 0.0f; - std::vector elevationCuts; + std::vector elevationCuts {}; + std::chrono::system_clock::time_point foundTime {}; - std::shared_ptr record; - std::tie(record, time) = p->GetLevel2ProductRecord(time); + const bool isEpox = time == std::chrono::system_clock::time_point {}; + bool needArchive = true; + static const auto maxChunkDelay = std::chrono::minutes(10); + const std::chrono::system_clock::time_point firstValidChunkTime = + (isEpox ? scwx::util::time::now() : time) - maxChunkDelay; - if (record != nullptr) + // See if we have this one in the chunk provider. + auto chunkFile = std::dynamic_pointer_cast( + p->level2ChunksProviderManager_->provider_->LoadObjectByTime(time)); + if (chunkFile != nullptr) { std::tie(radarData, elevationCut, elevationCuts) = - record->level2_file()->GetElevationScan( - dataBlockType, elevation, time); + chunkFile->GetElevationScan(dataBlockType, elevation, time); + + if (radarData != nullptr) + { + auto& radarData0 = (*radarData)[0]; + foundTime = std::chrono::floor( + scwx::util::TimePoint(radarData0->modified_julian_date(), + radarData0->collection_time())); + + const std::optional incomingElevation = + std::dynamic_pointer_cast( + p->level2ChunksProviderManager_->provider_) + ->GetCurrentElevation(); + if (incomingElevation != p->incomingLevel2Elevation_) + { + p->incomingLevel2Elevation_ = incomingElevation; + Q_EMIT IncomingLevel2ElevationChanged(incomingElevation); + } + + if (foundTime >= firstValidChunkTime) + { + needArchive = false; + } + } } - return {radarData, elevationCut, elevationCuts, time}; + // It is not in the chunk provider, so get it from the archive + if (needArchive) + { + auto records = p->GetLevel2ProductRecords(time); + for (auto& recordPair : records) + { + auto& record = recordPair.second; + + if (record != nullptr) + { + std::shared_ptr recordRadarData = + nullptr; + float recordElevationCut = 0.0f; + std::vector recordElevationCuts; + + std::tie(recordRadarData, recordElevationCut, recordElevationCuts) = + record->level2_file()->GetElevationScan( + dataBlockType, elevation, time); + + if (recordRadarData != nullptr) + { + auto& radarData0 = (*recordRadarData)[0]; + auto collectionTime = std::chrono::floor( + scwx::util::TimePoint(radarData0->modified_julian_date(), + radarData0->collection_time())); + + // Find the newest radar data, not newer than the selected time + if (radarData == nullptr || + (collectionTime <= time && foundTime < collectionTime) || + (isEpox && foundTime < collectionTime)) + { + radarData = recordRadarData; + elevationCut = recordElevationCut; + elevationCuts = std::move(recordElevationCuts); + foundTime = collectionTime; + + if (!p->incomingLevel2Elevation_.has_value()) + { + p->incomingLevel2Elevation_ = {}; + Q_EMIT IncomingLevel2ElevationChanged( + p->incomingLevel2Elevation_); + } + } + } + } + } + } + + return {radarData, elevationCut, elevationCuts, foundTime}; } std::tuple, @@ -1449,7 +1677,7 @@ std::vector RadarProductManager::GetLevel3Products() void RadarProductManager::SetCacheLimit(size_t cacheLimit) { - p->cacheLimit_ = cacheLimit; + p->cacheLimit_ = std::max(cacheLimit, 6u); } void RadarProductManager::UpdateAvailableProducts() @@ -1458,6 +1686,12 @@ void RadarProductManager::UpdateAvailableProducts() if (p->level3ProductsInitialized_) { + if (p->level3AvailabilityReady_) + { + // Multiple maps may use the same manager, so this ensures that all get + // notified of the change + Q_EMIT Level3ProductsChanged(); + } return; } @@ -1533,6 +1767,7 @@ void RadarProductManagerImpl::UpdateAvailableProductsSync() } } + level3AvailabilityReady_ = true; Q_EMIT self_->Level3ProductsChanged(); } diff --git a/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp b/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp index 4a3edabf..e8c72193 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp @@ -41,11 +41,14 @@ public: */ static void DumpRecords(); - const std::vector& coordinates(common::RadialSize radialSize) const; - const scwx::util::time_zone* default_time_zone() const; - float gate_size() const; - std::string radar_id() const; - std::shared_ptr radar_site() const; + [[nodiscard]] const std::vector& + coordinates(common::RadialSize radialSize, bool smoothingEnabled) const; + [[nodiscard]] const scwx::util::time_zone* default_time_zone() const; + [[nodiscard]] float gate_size() const; + [[nodiscard]] std::optional incoming_level_2_elevation() const; + [[nodiscard]] bool is_tdwr() const; + [[nodiscard]] std::string radar_id() const; + [[nodiscard]] std::shared_ptr radar_site() const; void Initialize(); @@ -132,6 +135,7 @@ public: /** * @brief Set the maximum number of products of each type that may be cached. + * The cache limit cannot be set lower than 6. * * @param [in] cacheLimit The maximum number of products of each type */ @@ -144,7 +148,9 @@ signals: void Level3ProductsChanged(); void NewDataAvailable(common::RadarProductGroup group, const std::string& product, + bool isChunks, std::chrono::system_clock::time_point latestTime); + void IncomingLevel2ElevationChanged(std::optional incomingElevation); private: std::unique_ptr p; diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index 443d771b..0c8cecef 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -22,6 +22,9 @@ namespace ResourceManager static const std::string logPrefix_ = "scwx::qt::manager::resource_manager"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); +static const size_t atlasWidth = 2048; +static const size_t atlasHeight = 2048; + static void LoadFonts(); static void LoadTextures(); @@ -40,10 +43,17 @@ void Initialize() void Shutdown() {} std::shared_ptr -LoadImageResource(const std::string& urlString) +LoadImageResource(const std::string& urlString, double scale) { util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); - return textureAtlas.CacheTexture(urlString, urlString); + return textureAtlas.CacheTexture(urlString, urlString, scale); +} + +std::shared_ptr LoadImageResource( + const std::string& urlString, const std::string& textureName, double scale) +{ + util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); + return textureAtlas.CacheTexture(textureName, urlString, scale); } std::vector> @@ -52,7 +62,7 @@ LoadImageResources(const std::vector& urlStrings) std::mutex m {}; std::vector> images {}; - std::for_each(std::execution::par_unseq, + std::for_each(std::execution::par, urlStrings.begin(), urlStrings.end(), [&](auto& urlString) @@ -68,8 +78,7 @@ LoadImageResources(const std::vector& urlStrings) if (!images.empty()) { - util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); - textureAtlas.BuildAtlas(2048, 2048); + BuildAtlas(); } return images; @@ -103,7 +112,13 @@ static void LoadTextures() GetTexturePath(lineTexture)); } - textureAtlas.BuildAtlas(2048, 2048); + BuildAtlas(); +} + +void BuildAtlas() +{ + util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); + textureAtlas.BuildAtlas(atlasWidth, atlasHeight); } } // namespace ResourceManager diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp index 00658891..cf0aad03 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.hpp @@ -19,9 +19,14 @@ void Initialize(); void Shutdown(); std::shared_ptr -LoadImageResource(const std::string& urlString); +LoadImageResource(const std::string& urlString, double scale = 1); +std::shared_ptr +LoadImageResource(const std::string& urlString, + const std::string& textureName, + double scale = 1); std::vector> -LoadImageResources(const std::vector& urlStrings); + LoadImageResources(const std::vector& urlStrings); +void BuildAtlas(); } // namespace ResourceManager } // namespace manager diff --git a/scwx-qt/source/scwx/qt/manager/settings_manager.cpp b/scwx-qt/source/scwx/qt/manager/settings_manager.cpp index 056799ae..a47428bb 100644 --- a/scwx-qt/source/scwx/qt/manager/settings_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/settings_manager.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include @@ -67,9 +67,10 @@ void SettingsManager::Initialize() } p->settingsPath_ = appDataPath + "/settings.json"; - p->initialized_ = true; ReadSettings(p->settingsPath_); + + p->initialized_ = true; p->ValidateSettings(); } diff --git a/scwx-qt/source/scwx/qt/manager/task_manager.cpp b/scwx-qt/source/scwx/qt/manager/task_manager.cpp new file mode 100644 index 00000000..66b7285a --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/task_manager.cpp @@ -0,0 +1,30 @@ +#include +#include +#include + +namespace scwx::qt::manager::TaskManager +{ + +static const std::string logPrefix_ = "scwx::qt::manager::task_manager"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static std::shared_ptr ntpClient_ {}; + +void Initialize() +{ + logger_->debug("Initialize"); + + ntpClient_ = network::NtpClient::Instance(); + + ntpClient_->Start(); + ntpClient_->WaitForInitialOffset(); +} + +void Shutdown() +{ + logger_->debug("Shutdown"); + + ntpClient_->Stop(); +} + +} // namespace scwx::qt::manager::TaskManager diff --git a/scwx-qt/source/scwx/qt/manager/task_manager.hpp b/scwx-qt/source/scwx/qt/manager/task_manager.hpp new file mode 100644 index 00000000..bb50fb3b --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/task_manager.hpp @@ -0,0 +1,9 @@ +#pragma once + +namespace scwx::qt::manager::TaskManager +{ + +void Initialize(); +void Shutdown(); + +} // namespace scwx::qt::manager::TaskManager diff --git a/scwx-qt/source/scwx/qt/manager/text_event_manager.cpp b/scwx-qt/source/scwx/qt/manager/text_event_manager.cpp index e48ecd48..009f2441 100644 --- a/scwx-qt/source/scwx/qt/manager/text_event_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/text_event_manager.cpp @@ -2,28 +2,60 @@ #include #include #include +#include #include #include +#include +#include +#include +#include #include #include #include #include #include +#include +#include +#include +#include +#include -namespace scwx -{ -namespace qt -{ -namespace manager +#if (__cpp_lib_chrono < 201907L) +# include +#endif + +namespace scwx::qt::manager { +using namespace std::chrono_literals; + static const std::string logPrefix_ = "scwx::qt::manager::text_event_manager"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -static const std::string& kDefaultWarningsProviderUrl { - "https://warnings.allisonhouse.com"}; +static constexpr std::chrono::hours kInitialLoadHistoryDuration_ = + std::chrono::days {3}; +static constexpr std::chrono::hours kDefaultLoadHistoryDuration_ = + std::chrono::hours {1}; + +static const std::array kPils_ = { + "FFS", "FFW", "MWS", "SMW", "SQW", "SVR", "SVS", "TOR"}; + +static const std:: + unordered_map> + kPilLoadWindows_ {{"FFS", {-24h, 1h}}, + {"FFW", {-24h, 1h}}, + {"MWS", {-4h, 1h}}, + {"SMW", {-4h, 1h}}, + {"SQW", {-4h, 1h}}, + {"SVR", {-4h, 1h}}, + {"SVS", {-4h, 1h}}, + {"TOR", {-4h, 1h}}}; + +// Widest load window provided by kPilLoadWindows_ +static const std::pair + kArchiveLoadWindow_ {-24h, 1h}; class TextEventManager::Impl { @@ -42,7 +74,9 @@ public: warningsProviderChangedCallbackUuid_ = generalSettings.warnings_provider().RegisterValueChangedCallback( - [this](const std::string& value) { + [this](const std::string& value) + { + loadHistoryDuration_ = kInitialLoadHistoryDuration_; warningsProvider_ = std::make_shared(value); }); @@ -76,11 +110,30 @@ public: threadPool_.join(); } - void HandleMessage(std::shared_ptr message); + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + + void HandleMessage(const std::shared_ptr& message, + bool archiveEvent = false); + template + requires std::same_as, + std::chrono::sys_days> + void ListArchives(DateRange dates); + void LoadArchives(std::chrono::system_clock::time_point dateTime); + void PruneArchives(); void RefreshAsync(); void Refresh(); + template + requires std::same_as, + std::chrono::sys_days> + void UpdateArchiveDates(DateRange dates); - boost::asio::thread_pool threadPool_ {1u}; + // Thread pool sized for: + // - Live Refresh (1x) + // - Archive Loading (1x) + boost::asio::thread_pool threadPool_ {2u}; TextEventManager* self_; @@ -95,6 +148,27 @@ public: std::shared_ptr warningsProvider_ {nullptr}; + std::chrono::hours loadHistoryDuration_ {kInitialLoadHistoryDuration_}; + std::chrono::sys_time prevLoadTime_ {}; + std::chrono::sys_days archiveLimit_ {}; + + std::mutex archiveMutex_ {}; + std::list archiveDates_ {}; + + std::mutex archiveEventKeyMutex_ {}; + std::map>> + archiveEventKeys_ {}; + std::unordered_set> + liveEventKeys_ {}; + + std::mutex unloadedProductMapMutex_ {}; + std::map> + unloadedProductMap_; + boost::uuids::uuid warningsProviderChangedCallbackUuid_ {}; }; @@ -164,9 +238,56 @@ void TextEventManager::LoadFile(const std::string& filename) }); } -void TextEventManager::Impl::HandleMessage( - std::shared_ptr message) +void TextEventManager::SelectTime( + std::chrono::system_clock::time_point dateTime) { + if (dateTime == std::chrono::system_clock::time_point {}) + { + // Ignore a default date/time selection + return; + } + + logger_->trace("Select Time: {}", util::TimeString(dateTime)); + + boost::asio::post( + p->threadPool_, + [dateTime, this]() + { + try + { + const auto today = std::chrono::floor(dateTime); + const auto yesterday = today - std::chrono::days {1}; + const auto tomorrow = today + std::chrono::days {1}; + const auto dateArray = std::array {yesterday, today, tomorrow}; + + const auto dates = + dateArray | + ranges::views::filter( + [this](const auto& date) + { + return p->archiveLimit_ == std::chrono::sys_days {} || + date < p->archiveLimit_; + }); + + const std::unique_lock lock {p->archiveMutex_}; + + p->UpdateArchiveDates(dates); + p->ListArchives(dates); + p->LoadArchives(dateTime); + p->PruneArchives(); + } + catch (const std::exception& ex) + { + logger_->error(ex.what()); + } + }); +} + +void TextEventManager::Impl::HandleMessage( + const std::shared_ptr& message, bool archiveEvent) +{ + using namespace std::chrono_literals; + auto segments = message->segments(); // If there are no segments, skip this message @@ -187,21 +308,49 @@ void TextEventManager::Impl::HandleMessage( } } + // Determine year + const std::chrono::year_month_day wmoDate = + std::chrono::floor( + message->wmo_header()->GetDateTime()); + const std::chrono::year wmoYear = wmoDate.year(); + std::unique_lock lock(textEventMutex_); // Find a matching event in the event map auto& vtecString = segments[0]->header_->vtecString_; - types::TextEventKey key {vtecString[0].pVtec_}; + types::TextEventKey key {vtecString[0].pVtec_, wmoYear}; size_t messageIndex = 0; auto it = textEventMap_.find(key); bool updated = false; + if ( + // If there was no matching event + it == textEventMap_.cend() && + // The event is not new + vtecString[0].pVtec_.action() != awips::PVtec::Action::New && + // The message was on January 1 + wmoDate.month() == std::chrono::January && wmoDate.day() == 1d && + // This is at least the 10th ETN of the year + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers): Readability + vtecString[0].pVtec_.event_tracking_number() > 10) + { + // Attempt to find a matching event from last year + key = {vtecString[0].pVtec_, wmoYear - std::chrono::years {1}}; + it = textEventMap_.find(key); + } + if (it == textEventMap_.cend()) { // If there was no matching event, add the message to a new event textEventMap_.emplace(key, std::vector {message}); messageIndex = 0; updated = true; + + if (!archiveEvent) + { + // Add the Text Event Key to the list of live events to prevent pruning + liveEventKeys_.insert(key); + } } else if (std::find_if(it->second.cbegin(), it->second.cend(), @@ -214,16 +363,284 @@ void TextEventManager::Impl::HandleMessage( // If there was a matching event, and this message has not been stored // (WMO header equivalence check), add the updated message to the existing // event - messageIndex = it->second.size(); - it->second.push_back(message); + + // Determine the chronological sequence of the message. Note, if there + // were no time hints given to the WMO header, this will place the message + // at the end of the vector. + auto insertionPoint = std::upper_bound( + it->second.begin(), + it->second.end(), + message, + [](const std::shared_ptr& a, + const std::shared_ptr& b) + { + return a->wmo_header()->GetDateTime() < + b->wmo_header()->GetDateTime(); + }); + + // Insert the message in chronological order + messageIndex = std::distance(it->second.begin(), insertionPoint); + it->second.insert(insertionPoint, message); updated = true; }; + // If this is an archive event, and the key does not exist in the live events + // Assumption: A live event will always be loaded before a duplicate archive + // event + if (archiveEvent && !liveEventKeys_.contains(key)) + { + // Add the Text Event Key to the current date's archive + const std::unique_lock archiveEventKeyLock {archiveEventKeyMutex_}; + auto& archiveKeys = archiveEventKeys_[wmoDate]; + archiveKeys.insert(key); + } + lock.unlock(); if (updated) { - Q_EMIT self_->AlertUpdated(key, messageIndex); + Q_EMIT self_->AlertUpdated(key, messageIndex, message->uuid()); + } +} + +template + requires std::same_as, + std::chrono::sys_days> +void TextEventManager::Impl::ListArchives(DateRange dates) +{ + // Don't reload data that has already been loaded + auto filteredDates = + dates | + ranges::views::filter([this](const auto& date) + { return !unloadedProductMap_.contains(date); }); + + const auto dv = ranges::to(filteredDates); + + std::for_each( + std::execution::par, + dv.begin(), + dv.end(), + [this](const auto& date) + { + static const auto kEmptyRange_ = + ranges::views::single(std::string_view {}); + static const auto kPilsView_ = + kPils_ | + ranges::views::transform([](const std::string& pil) + { return std::string_view {pil}; }); + + const auto dateArray = std::array {date}; + + auto productEntries = provider::IemApiProvider::ListTextProducts( + dateArray | ranges::views::all, kEmptyRange_, kPilsView_); + + const std::unique_lock lock {unloadedProductMapMutex_}; + + if (productEntries.has_value()) + { + unloadedProductMap_.try_emplace( + date, + boost::container::stable_vector { + std::make_move_iterator(productEntries.value().begin()), + std::make_move_iterator(productEntries.value().end())}); + } + }); +} + +void TextEventManager::Impl::LoadArchives( + std::chrono::system_clock::time_point dateTime) +{ + using namespace std::chrono; + +#if (__cpp_lib_chrono >= 201907L) + namespace df = std; + + static constexpr std::string_view kDateFormat {"{:%Y%m%d%H%M}"}; +#else + using namespace date; + namespace df = date; + +# define kDateFormat "%Y%m%d%H%M" +#endif + + // Search unloaded products in the widest archive load window + const std::chrono::sys_days startDate = + std::chrono::floor(dateTime + + kArchiveLoadWindow_.first); + const std::chrono::sys_days endDate = std::chrono::floor( + dateTime + kArchiveLoadWindow_.second + std::chrono::days {1}); + + // Determine load windows for each PIL + std::unordered_map> + pilLoadWindowStrings; + + for (auto& loadWindow : kPilLoadWindows_) + { + const std::string& pil = loadWindow.first; + + pilLoadWindowStrings.insert_or_assign( + pil, + std::pair { + df::format(kDateFormat, (dateTime + loadWindow.second.first)), + df::format(kDateFormat, (dateTime + loadWindow.second.second))}); + } + + std::vector loadListEntries {}; + + for (auto date = startDate; date < endDate; date += std::chrono::days {1}) + { + auto mapIt = unloadedProductMap_.find(date); + if (mapIt == unloadedProductMap_.cend()) + { + continue; + } + + for (auto it = mapIt->second.begin(); it != mapIt->second.end();) + { + const auto& pil = it->pil_; + + // Check PIL + if (pil.size() >= 3) + { + auto pilPrefix = pil.substr(0, 3); + auto windowIt = pilLoadWindowStrings.find(pilPrefix); + + // Check Window + if (windowIt != pilLoadWindowStrings.cend()) + { + const auto& productId = it->productId_; + const auto& windowStart = windowIt->second.first; + const auto& windowEnd = windowIt->second.second; + + if (windowStart <= productId && productId <= windowEnd) + { + // Product matches, move it to the load list + loadListEntries.emplace_back(std::move(*it)); + it = mapIt->second.erase(it); + continue; + } + } + } + + // Current iterator was not matched + ++it; + } + } + + std::vector> products {}; + + // Load the load list + if (!loadListEntries.empty()) + { + auto loadView = loadListEntries | + ranges::views::transform([](const auto& entry) + { return entry.productId_; }); + products = provider::IemApiProvider::LoadTextProducts(loadView); + } + + // Process loaded products + for (auto& product : products) + { + const auto& messages = product->messages(); + + for (auto& message : messages) + { + HandleMessage(message, true); + } + } +} + +void TextEventManager::Impl::PruneArchives() +{ + static constexpr std::size_t kMaxArchiveDates_ = 5; + + std::unordered_set> + eventKeysToKeep {}; + std::unordered_set> + eventKeysToPrune {}; + + // Remove oldest dates from the archive + while (archiveDates_.size() > kMaxArchiveDates_) + { + archiveDates_.pop_front(); + } + + const std::unique_lock archiveEventKeyLock {archiveEventKeyMutex_}; + + // If there are the same number of dates in archiveEventKeys_, archiveDates_ + // and unloadedProductMap_, there is nothing to prune + if (archiveEventKeys_.size() == archiveDates_.size() && + unloadedProductMap_.size() == archiveDates_.size()) + { + // Nothing to prune + return; + } + + const std::unique_lock unloadedProductMapLock {unloadedProductMapMutex_}; + + for (auto it = archiveEventKeys_.begin(); it != archiveEventKeys_.end();) + { + const auto& date = it->first; + const auto& eventKeys = it->second; + + // If date is not in recent days map + if (std::find(archiveDates_.cbegin(), archiveDates_.cend(), date) == + archiveDates_.cend()) + { + // Prune these keys (unless they are in the eventKeysToKeep set) + eventKeysToPrune.insert(eventKeys.begin(), eventKeys.end()); + + // The date is not in the list of recent dates, remove it + it = archiveEventKeys_.erase(it); + } + else + { + // Make sure these keys don't get pruned + eventKeysToKeep.insert(eventKeys.begin(), eventKeys.end()); + + // The date is recent, keep it + ++it; + } + } + + for (auto it = unloadedProductMap_.begin(); it != unloadedProductMap_.end();) + { + const auto& date = it->first; + + // If date is not in recent days map + if (std::find(archiveDates_.cbegin(), archiveDates_.cend(), date) == + archiveDates_.cend()) + { + // The date is not in the list of recent dates, remove it + it = unloadedProductMap_.erase(it); + } + else + { + // The date is recent, keep it + ++it; + } + } + + // Remove elements from eventKeysToPrune if they are in eventKeysToKeep + for (const auto& eventKey : eventKeysToKeep) + { + eventKeysToPrune.erase(eventKey); + } + + // Remove eventKeysToPrune from textEventMap + for (const auto& eventKey : eventKeysToPrune) + { + textEventMap_.erase(eventKey); + } + + // If event keys were pruned, emit a signal + if (!eventKeysToPrune.empty()) + { + logger_->debug("Pruned {} archive events", eventKeysToPrune.size()); + + Q_EMIT self_->AlertsRemoved(eventKeysToPrune); } } @@ -254,21 +671,38 @@ void TextEventManager::Impl::Refresh() std::shared_ptr warningsProvider = warningsProvider_; - // Update the file listing from the warnings provider - auto [newFiles, totalFiles] = warningsProvider->ListFiles(); + // Load updated files from the warnings provider + // Start time should default to: + // - 3 days of history for the first load + // - 1 hour of history for subsequent loads + // If the time jumps, we should attempt to load from no later than the + // previous load time + auto loadTime = + std::chrono::floor(scwx::util::time::now()); + auto startTime = loadTime - loadHistoryDuration_; - if (newFiles > 0) + if (prevLoadTime_ != std::chrono::sys_time {}) { - // Load new files - auto updatedFiles = warningsProvider->LoadUpdatedFiles(); + startTime = std::min(startTime, prevLoadTime_); + } - // Handle messages - for (auto& file : updatedFiles) + if (archiveLimit_ == std::chrono::sys_days {}) + { + archiveLimit_ = std::chrono::ceil(startTime); + } + + auto updatedFiles = warningsProvider->LoadUpdatedFiles(startTime); + + // Store the load time and reset the load history duration + prevLoadTime_ = loadTime; + loadHistoryDuration_ = kDefaultLoadHistoryDuration_; + + // Handle messages + for (auto& file : updatedFiles) + { + for (auto& message : file->messages()) { - for (auto& message : file->messages()) - { - HandleMessage(message); - } + HandleMessage(message); } } @@ -293,6 +727,19 @@ void TextEventManager::Impl::Refresh() }); } +template + requires std::same_as, + std::chrono::sys_days> +void TextEventManager::Impl::UpdateArchiveDates(DateRange dates) +{ + for (const auto& date : dates) + { + // Remove any existing occurrences of day, and add to the back of the list + archiveDates_.remove(date); + archiveDates_.push_back(date); + } +} + std::shared_ptr TextEventManager::Instance() { static std::weak_ptr textEventManagerReference_ {}; @@ -312,6 +759,4 @@ std::shared_ptr TextEventManager::Instance() return textEventManager; } -} // namespace manager -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::manager diff --git a/scwx-qt/source/scwx/qt/manager/text_event_manager.hpp b/scwx-qt/source/scwx/qt/manager/text_event_manager.hpp index f97ca223..61affe6c 100644 --- a/scwx-qt/source/scwx/qt/manager/text_event_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/text_event_manager.hpp @@ -3,9 +3,12 @@ #include #include +#include #include #include +#include +#include #include namespace scwx @@ -28,11 +31,18 @@ public: message_list(const types::TextEventKey& key) const; void LoadFile(const std::string& filename); + void SelectTime(std::chrono::system_clock::time_point dateTime); static std::shared_ptr Instance(); signals: - void AlertUpdated(const types::TextEventKey& key, size_t messageIndex); + void AlertsRemoved( + const std::unordered_set>& + keys); + void AlertUpdated(const types::TextEventKey& key, + std::size_t messageIndex, + boost::uuids::uuid uuid); private: class Impl; diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index 1d48ac9c..70c4b2ed 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,8 @@ enum class Direction // Wait up to 5 seconds for radar sweeps to update static constexpr std::chrono::seconds kRadarSweepMonitorTimeout_ {5}; +// Only allow for 3 steps to be queued at any time +static constexpr size_t kMaxQueuedSteps_ {3}; class TimelineManager::Impl { @@ -80,6 +83,8 @@ public: boost::asio::thread_pool playThreadPool_ {1}; boost::asio::thread_pool selectThreadPool_ {1}; + util::QueueCounter stepCounter_ {kMaxQueuedSteps_}; + std::size_t mapCount_ {0}; std::string radarSite_ {"?"}; std::string previousRadarSite_ {"?"}; @@ -107,6 +112,11 @@ public: TimelineManager::TimelineManager() : p(std::make_unique(this)) {} TimelineManager::~TimelineManager() = default; +std::chrono::system_clock::time_point TimelineManager::GetSelectedTime() const +{ + return p->selectedTime_; +} + void TimelineManager::SetMapCount(std::size_t mapCount) { p->mapCount_ = mapCount; @@ -208,7 +218,7 @@ void TimelineManager::AnimationStepBegin() p->pinnedTime_ == std::chrono::system_clock::time_point {}) { // If the selected view type is live, select the current products - p->SelectTimeAsync(std::chrono::system_clock::now() - p->loopTime_); + p->SelectTimeAsync(scwx::util::time::now() - p->loopTime_); } else { @@ -256,7 +266,7 @@ void TimelineManager::AnimationStepEnd() if (p->viewType_ == types::MapTime::Live) { // If the selected view type is live, select the current products - p->SelectTime(); + p->SelectTimeAsync(); } else { @@ -336,10 +346,10 @@ void TimelineManager::ReceiveMapWidgetPainted(std::size_t mapIndex) std::unique_lock lock {p->radarSweepMonitorMutex_}; // If the radar sweep has been updated - if (p->radarSweepsUpdated_.contains(mapIndex)) + if (p->radarSweepsUpdated_.contains(mapIndex) && + !p->radarSweepsComplete_.contains(mapIndex)) { // Mark the radar sweep complete - p->radarSweepsUpdated_.erase(mapIndex); p->radarSweepsComplete_.insert(mapIndex); // If all sweeps have completed rendering @@ -375,8 +385,8 @@ TimelineManager::Impl::GetLoopStartAndEndTimes() if (viewType_ == types::MapTime::Live || pinnedTime_ == std::chrono::system_clock::time_point {}) { - endTime = std::chrono::floor( - std::chrono::system_clock::now()); + endTime = + std::chrono::floor(scwx::util::time::now()); } else { @@ -395,8 +405,9 @@ void TimelineManager::Impl::UpdateCacheLimit( { // Calculate the number of volume scans in the loop auto [startTime, endTime] = GetLoopStartAndEndTimes(); - auto startIter = util::GetBoundedElementIterator(volumeTimes, startTime); - auto endIter = util::GetBoundedElementIterator(volumeTimes, endTime); + auto startIter = + scwx::util::GetBoundedElementIterator(volumeTimes, startTime); + auto endIter = scwx::util::GetBoundedElementIterator(volumeTimes, endTime); std::size_t numVolumeScans = std::distance(startIter, endIter) + 1; // Dynamically update maximum cached volume scans to the lesser of @@ -466,20 +477,12 @@ void TimelineManager::Impl::PlaySync() // Select the time auto selectTimeStart = std::chrono::steady_clock::now(); - auto [volumeTimeUpdated, selectedTimeUpdated] = SelectTime(newTime); + SelectTime(newTime); auto selectTimeEnd = std::chrono::steady_clock::now(); auto elapsedTime = selectTimeEnd - selectTimeStart; - if (volumeTimeUpdated) - { - // Wait for radar sweeps to update - RadarSweepMonitorWait(radarSweepMonitorLock); - } - else - { - // Disable radar sweep monitor - RadarSweepMonitorDisable(); - } + // Wait for radar sweeps to update + RadarSweepMonitorWait(radarSweepMonitorLock); // Calculate the interval until the next update, prior to selecting std::chrono::milliseconds interval; @@ -579,7 +582,8 @@ std::pair TimelineManager::Impl::SelectTime( UpdateCacheLimit(radarProductManager, volumeTimes); // Find the best match bounded time - auto elementPtr = util::GetBoundedElementPointer(volumeTimes, selectedTime); + auto elementPtr = + scwx::util::GetBoundedElementPointer(volumeTimes, selectedTime); // The timeline is no longer live Q_EMIT self_->LiveStateUpdated(false); @@ -620,6 +624,12 @@ std::pair TimelineManager::Impl::SelectTime( void TimelineManager::Impl::StepAsync(Direction direction) { + // Prevent too many steps from being added to the queue + if (!stepCounter_.add()) + { + return; + } + boost::asio::post(selectThreadPool_, [=, this]() { @@ -631,6 +641,7 @@ void TimelineManager::Impl::StepAsync(Direction direction) { logger_->error(ex.what()); } + stepCounter_.remove(); }); } @@ -639,79 +650,63 @@ void TimelineManager::Impl::Step(Direction direction) // Take a lock for time selection std::unique_lock lock {selectTimeMutex_}; - // Determine time to get active volume times - std::chrono::system_clock::time_point queryTime = adjustedTime_; - if (queryTime == std::chrono::system_clock::time_point {}) + std::chrono::system_clock::time_point newTime = selectedTime_; + + if (newTime == std::chrono::system_clock::time_point {}) { - queryTime = std::chrono::system_clock::now(); - } - - // Request active volume times - auto radarProductManager = - manager::RadarProductManager::Instance(radarSite_); - auto volumeTimes = radarProductManager->GetActiveVolumeTimes(queryTime); - - if (volumeTimes.empty()) - { - logger_->debug("No products to step through"); - return; - } - - // Dynamically update maximum cached volume scans - UpdateCacheLimit(radarProductManager, volumeTimes); - - std::set::const_iterator it; - - if (adjustedTime_ == std::chrono::system_clock::time_point {}) - { - // If the adjusted time is live, get the last element in the set - it = std::prev(volumeTimes.cend()); - } - else - { - // Get the current element in the set - it = scwx::util::GetBoundedElementIterator(volumeTimes, adjustedTime_); - } - - if (it == volumeTimes.cend()) - { - // Should not get here, but protect against an error - logger_->error("No suitable volume time found"); - return; - } - - if (direction == Direction::Back) - { - // Only if we aren't at the beginning of the volume times set - if (it != volumeTimes.cbegin()) + if (direction == Direction::Back) { - // Select the previous time - adjustedTime_ = *(--it); - selectedTime_ = adjustedTime_; - - logger_->debug("Volume time updated: {}", - scwx::util::TimeString(adjustedTime_)); - - Q_EMIT self_->LiveStateUpdated(false); - Q_EMIT self_->VolumeTimeUpdated(adjustedTime_); - Q_EMIT self_->SelectedTimeUpdated(adjustedTime_); + newTime = + std::chrono::floor(scwx::util::time::now()); + } + else + { + // Cannot step forward any further + return; } } - else + + // Unlock prior to selecting time + lock.unlock(); + + // Lock radar sweep monitor + std::unique_lock radarSweepMonitorLock {radarSweepMonitorMutex_}; + + // Attempt to step forward or backward up to 30 minutes until an update is + // received on at least one map + for (std::size_t i = 0; i < 30; ++i) { - // Only if we aren't at the end of the volume times set - if (it != std::prev(volumeTimes.cend())) + using namespace std::chrono_literals; + + // Increment/decrement selected time by one minute + if (direction == Direction::Back) { - // Select the next time - adjustedTime_ = *(++it); - selectedTime_ = adjustedTime_; + newTime -= 1min; + } + else + { + newTime += 1min; - logger_->debug("Volume time updated: {}", - scwx::util::TimeString(adjustedTime_)); + // If the new time is more than 2 minutes in the future, stop stepping + if (newTime > scwx::util::time::now() + 2min) + { + break; + } + } - Q_EMIT self_->LiveStateUpdated(false); - Q_EMIT self_->VolumeTimeUpdated(adjustedTime_); - Q_EMIT self_->SelectedTimeUpdated(adjustedTime_); + // Reset radar sweep monitor in preparation for update + RadarSweepMonitorReset(); + + // Select the time + SelectTime(newTime); + + // Wait for radar sweeps to update + RadarSweepMonitorWait(radarSweepMonitorLock); + + // Check for updates + if (!radarSweepsUpdated_.empty()) + { + break; } } } diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp index 1a4154ac..054a8201 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp @@ -24,6 +24,8 @@ public: static std::shared_ptr Instance(); + [[nodiscard]] std::chrono::system_clock::time_point GetSelectedTime() const; + void SetMapCount(std::size_t mapCount); public slots: diff --git a/scwx-qt/source/scwx/qt/manager/update_manager.cpp b/scwx-qt/source/scwx/qt/manager/update_manager.cpp index d21068cb..05a9c0d1 100644 --- a/scwx-qt/source/scwx/qt/manager/update_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/update_manager.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -29,8 +30,7 @@ public: ~Impl() {} - static std::string GetVersionString(const std::string& releaseName); - static boost::json::value ParseResponseText(const std::string& s); + static std::string GetVersionString(const std::string& releaseName); size_t PopulateReleases(); size_t AddReleases(const boost::json::value& json); @@ -70,28 +70,6 @@ UpdateManager::Impl::GetVersionString(const std::string& releaseName) return versionString; } -boost::json::value UpdateManager::Impl::ParseResponseText(const std::string& s) -{ - boost::json::stream_parser p; - boost::system::error_code ec; - - p.write(s, ec); - if (ec) - { - logger_->warn("{}", ec.message()); - return nullptr; - } - - p.finish(ec); - if (ec) - { - logger_->warn("{}", ec.message()); - return nullptr; - } - - return p.release(); -} - bool UpdateManager::CheckForUpdates(const std::string& currentVersion) { std::unique_lock lock(p->updateMutex_); @@ -148,7 +126,7 @@ size_t UpdateManager::Impl::PopulateReleases() // Successful REST API query if (r.status_code == 200) { - boost::json::value json = Impl::ParseResponseText(r.text); + const boost::json::value json = util::json::ReadJsonString(r.text); if (json == nullptr) { logger_->warn("Response not JSON: {}", r.header["content-type"]); diff --git a/scwx-qt/source/scwx/qt/map/alert_layer.cpp b/scwx-qt/source/scwx/qt/map/alert_layer.cpp index cc8bc29b..7bea5938 100644 --- a/scwx-qt/source/scwx/qt/map/alert_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/alert_layer.cpp @@ -6,9 +6,13 @@ #include #include #include +#include #include #include +#include +#include +#include #include #include @@ -18,12 +22,9 @@ #include #include #include +#include -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { static const std::string logPrefix_ = "scwx::qt::map::alert_layer"; @@ -40,9 +41,13 @@ struct AlertTypeHash> size_t operator()(const std::pair& x) const; }; +static bool IsAlertActive(const std::shared_ptr& segment); + class AlertLayerHandler : public QObject { Q_OBJECT + Q_DISABLE_COPY_MOVE(AlertLayerHandler) + public: struct SegmentRecord { @@ -54,10 +59,10 @@ public: SegmentRecord( const std::shared_ptr& segment, - const types::TextEventKey& key, + types::TextEventKey key, const std::shared_ptr& message) : segment_ {segment}, - key_ {key}, + key_ {std::move(key)}, message_ {message}, segmentBegin_ {segment->event_begin()}, segmentEnd_ {segment->event_end()} @@ -70,8 +75,11 @@ public: connect(textEventManager_.get(), &manager::TextEventManager::AlertUpdated, this, - [this](const types::TextEventKey& key, std::size_t messageIndex) - { HandleAlert(key, messageIndex); }); + &AlertLayerHandler::HandleAlert); + connect(textEventManager_.get(), + &manager::TextEventManager::AlertsRemoved, + this, + &AlertLayerHandler::HandleAlertsRemoved); } ~AlertLayerHandler() { @@ -92,7 +100,13 @@ public: types::TextEventHash> segmentsByKey_ {}; - void HandleAlert(const types::TextEventKey& key, size_t messageIndex); + void HandleAlert(const types::TextEventKey& key, + size_t messageIndex, + boost::uuids::uuid uuid); + void HandleAlertsRemoved( + const std::unordered_set>& + keys); static AlertLayerHandler& Instance(); @@ -105,31 +119,34 @@ signals: void AlertAdded(const std::shared_ptr& segmentRecord, awips::Phenomenon phenomenon); void AlertUpdated(const std::shared_ptr& segmentRecord); + void AlertsRemoved(awips::Phenomenon phenomenon); void AlertsUpdated(awips::Phenomenon phenomenon, bool alertActive); }; class AlertLayer::Impl { public: - explicit Impl(AlertLayer* self, - std::shared_ptr context, - awips::Phenomenon phenomenon) : + struct LineData + { + boost::gil::rgba32f_pixel_t borderColor_ {}; + boost::gil::rgba32f_pixel_t highlightColor_ {}; + boost::gil::rgba32f_pixel_t lineColor_ {}; + + std::size_t borderWidth_ {}; + std::size_t highlightWidth_ {}; + std::size_t lineWidth_ {}; + }; + + explicit Impl(AlertLayer* self, + const std::shared_ptr& glContext, + awips::Phenomenon phenomenon) : self_ {self}, phenomenon_ {phenomenon}, - geoLines_ {{false, std::make_shared(context)}, - {true, std::make_shared(context)}} + ibw_ {awips::ibw::GetImpactBasedWarningInfo(phenomenon)}, + geoLines_ {{false, std::make_shared(glContext)}, + {true, std::make_shared(glContext)}} { - auto& paletteSettings = settings::PaletteSettings::Instance(); - - for (auto alertActive : {false, true}) - { - lineColor_.emplace( - alertActive, - util::color::ToRgba32fPixelT( - paletteSettings.alert_color(phenomenon_, alertActive) - .GetValue())); - } - + UpdateLineData(); ConnectSignals(); ScheduleRefresh(); } @@ -146,36 +163,51 @@ public: std::unique_lock lock(linesMutex_); }; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + void AddAlert( const std::shared_ptr& segmentRecord); void UpdateAlert( const std::shared_ptr& segmentRecord); void ConnectAlertHandlerSignals(); void ConnectSignals(); - void HandleGeoLinesEvent(std::shared_ptr& di, - QEvent* ev); - void HandleGeoLinesHover(std::shared_ptr& di, - const QPointF& mouseGlobalPos); + void HandleGeoLinesEvent(std::weak_ptr& di, + QEvent* ev); + void + HandleGeoLinesHover(const std::shared_ptr& di, + const QPointF& mouseGlobalPos); void ScheduleRefresh(); + LineData& GetLineData(const std::shared_ptr& segment, + bool alertActive); + void UpdateLineData(); + void AddLine(std::shared_ptr& geoLines, std::shared_ptr& di, const common::Coordinate& p1, const common::Coordinate& p2, - boost::gil::rgba32f_pixel_t color, + const boost::gil::rgba32f_pixel_t& color, float width, std::chrono::system_clock::time_point startTime, std::chrono::system_clock::time_point endTime, bool enableHover); void AddLines(std::shared_ptr& geoLines, const std::vector& coordinates, - boost::gil::rgba32f_pixel_t color, + const boost::gil::rgba32f_pixel_t& color, float width, std::chrono::system_clock::time_point startTime, std::chrono::system_clock::time_point endTime, bool enableHover, boost::container::stable_vector< std::shared_ptr>& drawItems); + void PopulateLines(bool alertActive); + void RepopulateLines(); + void UpdateLines(); + + static LineData CreateLineData(const settings::LineSettings& lineSettings); boost::asio::thread_pool threadPool_ {1u}; @@ -184,9 +216,11 @@ public: boost::asio::system_timer refreshTimer_ {threadPool_}; std::mutex refreshMutex_; - const awips::Phenomenon phenomenon_; + const awips::Phenomenon phenomenon_; + const awips::ibw::ImpactBasedWarningInfo& ibw_; std::unique_ptr receiver_ {std::make_unique()}; + std::mutex receiverMutex_ {}; std::unordered_map> geoLines_; @@ -199,17 +233,26 @@ public: segmentsByLine_; std::mutex linesMutex_ {}; - std::unordered_map lineColor_; + std::unordered_map + threatCategoryLineData_; + LineData observedLineData_ {}; + LineData tornadoPossibleLineData_ {}; + LineData inactiveLineData_ {}; std::chrono::system_clock::time_point selectedTime_ {}; std::shared_ptr lastHoverDi_ {nullptr}; std::string tooltip_ {}; + + std::vector connections_ {}; }; -AlertLayer::AlertLayer(std::shared_ptr context, - awips::Phenomenon phenomenon) : - DrawLayer(context), p(std::make_unique(this, context, phenomenon)) +AlertLayer::AlertLayer(const std::shared_ptr& glContext, + awips::Phenomenon phenomenon) : + DrawLayer( + glContext, + fmt::format("AlertLayer {}", awips::GetPhenomenonText(phenomenon))), + p(std::make_unique(this, glContext, phenomenon)) { for (auto alertActive : {false, true}) { @@ -233,51 +276,37 @@ void AlertLayer::InitializeHandler() } } -void AlertLayer::Initialize() +void AlertLayer::Initialize(const std::shared_ptr& mapContext) { logger_->debug("Initialize: {}", awips::GetPhenomenonText(p->phenomenon_)); - DrawLayer::Initialize(); + DrawLayer::Initialize(mapContext); auto& alertLayerHandler = AlertLayerHandler::Instance(); + p->selectedTime_ = manager::TimelineManager::Instance()->GetSelectedTime(); + // Take a shared lock to prevent handling additional alerts while populating // initial lists std::shared_lock lock {alertLayerHandler.alertMutex_}; for (auto alertActive : {false, true}) { - auto& geoLines = p->geoLines_.at(alertActive); - - geoLines->StartLines(); - - // Populate initial segments - auto segmentsIt = - alertLayerHandler.segmentsByType_.find({p->phenomenon_, alertActive}); - if (segmentsIt != alertLayerHandler.segmentsByType_.cend()) - { - for (auto& segment : segmentsIt->second) - { - p->AddAlert(segment); - } - } - - geoLines->FinishLines(); + p->PopulateLines(alertActive); } p->ConnectAlertHandlerSignals(); } -void AlertLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) +void AlertLayer::Render(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters& params) { - gl::OpenGLFunctions& gl = context()->gl(); - for (auto alertActive : {false, true}) { p->geoLines_.at(alertActive)->set_selected_time(p->selectedTime_); } - DrawLayer::Render(params); + DrawLayer::Render(mapContext, params); SCWX_GL_CHECK_ERROR(); } @@ -289,8 +318,18 @@ void AlertLayer::Deinitialize() DrawLayer::Deinitialize(); } +bool IsAlertActive(const std::shared_ptr& segment) +{ + auto& vtec = segment->header_->vtecString_.front(); + auto action = vtec.pVtec_.action(); + bool alertActive = (action != awips::PVtec::Action::Canceled); + + return alertActive; +} + void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, - size_t messageIndex) + size_t messageIndex, + boost::uuids::uuid uuid) { logger_->trace("HandleAlert: {}", key.ToString()); @@ -298,7 +337,28 @@ void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, AlertTypeHash>> alertsUpdated {}; - auto message = textEventManager_->message_list(key).at(messageIndex); + const auto& messageList = textEventManager_->message_list(key); + + // Find message by UUID instead of index, as the message index could have + // changed between the signal being emitted and the handler being called + auto messageIt = std::find_if(messageList.cbegin(), + messageList.cend(), + [&uuid](const auto& message) + { return uuid == message->uuid(); }); + + if (messageIt == messageList.cend()) + { + logger_->warn( + "Could not find alert uuid: {} ({})", key.ToString(), messageIndex); + return; + } + + auto& message = *messageIt; + auto nextMessageIt = std::next(messageIt); + + // Store the current message index + messageIndex = + static_cast(std::distance(messageList.cbegin(), messageIt)); // Determine start time for first segment std::chrono::system_clock::time_point segmentBegin {}; @@ -307,14 +367,31 @@ void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, segmentBegin = message->segment(0)->event_begin(); } + // Determine the start time for the first segment of the next message + std::optional nextMessageBegin {}; + if (nextMessageIt != messageList.cend()) + { + nextMessageBegin = + (*nextMessageIt) + ->wmo_header() + ->GetDateTime((*nextMessageIt)->segment(0)->event_begin()); + } + // Take a unique mutex before modifying segments std::unique_lock lock {alertMutex_}; - // Update any existing segments with new end time + // Update any existing earlier segments with new end time auto& segmentsForKey = segmentsByKey_[key]; for (auto& segmentRecord : segmentsForKey) { - if (segmentRecord->segmentEnd_ > segmentBegin) + // Determine if the segment is earlier than the current message + auto it = std::find( + messageList.cbegin(), messageList.cend(), segmentRecord->message_); + auto segmentIndex = + static_cast(std::distance(messageList.cbegin(), it)); + + if (segmentIndex < messageIndex && + segmentRecord->segmentEnd_ > segmentBegin) { segmentRecord->segmentEnd_ = segmentBegin; @@ -331,10 +408,9 @@ void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, continue; } - auto& vtec = segment->header_->vtecString_.front(); - auto action = vtec.pVtec_.action(); - awips::Phenomenon phenomenon = vtec.pVtec_.phenomenon(); - bool alertActive = (action != awips::PVtec::Action::Canceled); + auto& vtec = segment->header_->vtecString_.front(); + awips::Phenomenon phenomenon = vtec.pVtec_.phenomenon(); + bool alertActive = IsAlertActive(segment); auto& segmentsForType = segmentsByType_[{key.phenomenon_, alertActive}]; @@ -342,6 +418,14 @@ void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, std::shared_ptr segmentRecord = std::make_shared(segment, key, message); + // Update segment end time to be no later than the begin time of the next + // message (if present) + if (nextMessageBegin.has_value() && + segmentRecord->segmentEnd_ > nextMessageBegin) + { + segmentRecord->segmentEnd_ = nextMessageBegin.value(); + } + segmentsForKey.push_back(segmentRecord); segmentsForType.push_back(segmentRecord); @@ -360,6 +444,63 @@ void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, } } +void AlertLayerHandler::HandleAlertsRemoved( + const std::unordered_set>& keys) +{ + logger_->trace("HandleAlertsRemoved: {} keys", keys.size()); + + std::set alertsRemoved {}; + + // Take a unique lock before modifying segments + std::unique_lock lock {alertMutex_}; + + for (const auto& key : keys) + { + // Remove segments associated with the key + auto segmentsIt = segmentsByKey_.find(key); + if (segmentsIt != segmentsByKey_.end()) + { + for (const auto& segmentRecord : segmentsIt->second) + { + auto& segment = segmentRecord->segment_; + const bool alertActive = IsAlertActive(segment); + + // Remove from segmentsByType_ + auto typeIt = segmentsByType_.find({key.phenomenon_, alertActive}); + if (typeIt != segmentsByType_.end()) + { + auto& segmentsForType = typeIt->second; + segmentsForType.erase(std::remove(segmentsForType.begin(), + segmentsForType.end(), + segmentRecord), + segmentsForType.end()); + + // If no segments remain for this type, erase the entry + if (segmentsForType.empty()) + { + segmentsByType_.erase(typeIt); + } + } + + alertsRemoved.emplace(key.phenomenon_); + } + + // Remove the key from segmentsByKey_ + segmentsByKey_.erase(segmentsIt); + } + } + + // Release the lock after completing segment updates + lock.unlock(); + + // Emit signal to notify that alerts have been removed + for (auto& alert : alertsRemoved) + { + Q_EMIT AlertsRemoved(alert); + } +} + void AlertLayer::Impl::ConnectAlertHandlerSignals() { auto& alertLayerHandler = AlertLayerHandler::Instance(); @@ -374,6 +515,9 @@ void AlertLayer::Impl::ConnectAlertHandlerSignals() { if (phenomenon == phenomenon_) { + // Only process one signal at a time + const std::unique_lock lock {receiverMutex_}; + AddAlert(segmentRecord); } }); @@ -386,13 +530,33 @@ void AlertLayer::Impl::ConnectAlertHandlerSignals() { if (segmentRecord->key_.phenomenon_ == phenomenon_) { + // Only process one signal at a time + const std::unique_lock lock {receiverMutex_}; + UpdateAlert(segmentRecord); } }); + QObject::connect(&alertLayerHandler, + &AlertLayerHandler::AlertsRemoved, + receiver_.get(), + [this](awips::Phenomenon phenomenon) + { + if (phenomenon == phenomenon_) + { + // Only process one signal at a time + const std::unique_lock lock {receiverMutex_}; + + // Re-populate the lines if multiple alerts were + // removed + RepopulateLines(); + } + }); } void AlertLayer::Impl::ConnectSignals() { + auto& alertPaletteSettings = + settings::PaletteSettings::Instance().alert_palette(phenomenon_); auto timelineManager = manager::TimelineManager::Instance(); QObject::connect(timelineManager.get(), @@ -400,6 +564,13 @@ void AlertLayer::Impl::ConnectSignals() receiver_.get(), [this](std::chrono::system_clock::time_point dateTime) { selectedTime_ = dateTime; }); + + connections_.push_back(alertPaletteSettings.changed_signal().connect( + [this]() + { + UpdateLineData(); + UpdateLines(); + })); } void AlertLayer::Impl::ScheduleRefresh() @@ -411,8 +582,7 @@ void AlertLayer::Impl::ScheduleRefresh() // Expires at the top of the next minute std::chrono::system_clock::time_point now = - std::chrono::floor( - std::chrono::system_clock::now()); + std::chrono::floor(scwx::util::time::now()); refreshTimer_.expires_at(now + 1min); refreshTimer_.async_wait( @@ -439,14 +609,12 @@ void AlertLayer::Impl::AddAlert( { auto& segment = segmentRecord->segment_; - auto& vtec = segment->header_->vtecString_.front(); - auto action = vtec.pVtec_.action(); - bool alertActive = (action != awips::PVtec::Action::Canceled); + bool alertActive = IsAlertActive(segment); auto& startTime = segmentRecord->segmentBegin_; auto& endTime = segmentRecord->segmentEnd_; - auto& lineColor = lineColor_.at(alertActive); - auto& geoLines = geoLines_.at(alertActive); + auto& lineData = GetLineData(segment, alertActive); + auto& geoLines = geoLines_.at(alertActive); const auto& coordinates = segment->codedLocation_->coordinates(); @@ -462,32 +630,55 @@ void AlertLayer::Impl::AddAlert( // If draw items were added if (drawItems.second) { + const auto borderWidth = static_cast(lineData.borderWidth_); + const auto highlightWidth = static_cast(lineData.highlightWidth_); + const auto lineWidth = static_cast(lineData.lineWidth_); + + const float totalHighlightWidth = lineWidth + (highlightWidth * 2.0f); + const float totalBorderWidth = totalHighlightWidth + (borderWidth * 2.0f); + + constexpr bool borderHover = true; + constexpr bool highlightHover = false; + constexpr bool lineHover = false; + // Add border AddLines(geoLines, coordinates, - kBlack_, - 5.0f, + lineData.borderColor_, + totalBorderWidth, startTime, endTime, - true, + borderHover, drawItems.first->second); - // Add only border to segmentsByLine_ + // Add border to segmentsByLine_ for (auto& di : drawItems.first->second) { segmentsByLine_.insert({di, segmentRecord}); } + // Add highlight + AddLines(geoLines, + coordinates, + lineData.highlightColor_, + totalHighlightWidth, + startTime, + endTime, + highlightHover, + drawItems.first->second); + // Add line AddLines(geoLines, coordinates, - lineColor, - 3.0f, + lineData.lineColor_, + lineWidth, startTime, endTime, - false, + lineHover, drawItems.first->second); } + + Q_EMIT self_->NeedsRendering(); } void AlertLayer::Impl::UpdateAlert( @@ -499,11 +690,8 @@ void AlertLayer::Impl::UpdateAlert( auto it = linesBySegment_.find(segmentRecord); if (it != linesBySegment_.cend()) { - auto& segment = segmentRecord->segment_; - - auto& vtec = segment->header_->vtecString_.front(); - auto action = vtec.pVtec_.action(); - bool alertActive = (action != awips::PVtec::Action::Canceled); + auto& segment = segmentRecord->segment_; + bool alertActive = IsAlertActive(segment); auto& geoLines = geoLines_.at(alertActive); @@ -514,12 +702,14 @@ void AlertLayer::Impl::UpdateAlert( geoLines->SetLineEndTime(line, segmentRecord->segmentEnd_); } } + + Q_EMIT self_->NeedsRendering(); } void AlertLayer::Impl::AddLines( std::shared_ptr& geoLines, const std::vector& coordinates, - boost::gil::rgba32f_pixel_t color, + const boost::gil::rgba32f_pixel_t& color, float width, std::chrono::system_clock::time_point startTime, std::chrono::system_clock::time_point endTime, @@ -559,14 +749,17 @@ void AlertLayer::Impl::AddLine(std::shared_ptr& geoLines, std::shared_ptr& di, const common::Coordinate& p1, const common::Coordinate& p2, - boost::gil::rgba32f_pixel_t color, + const boost::gil::rgba32f_pixel_t& color, float width, std::chrono::system_clock::time_point startTime, std::chrono::system_clock::time_point endTime, bool enableHover) { - geoLines->SetLineLocation( - di, p1.latitude_, p1.longitude_, p2.latitude_, p2.longitude_); + geoLines->SetLineLocation(di, + static_cast(p1.latitude_), + static_cast(p1.longitude_), + static_cast(p2.latitude_), + static_cast(p2.longitude_)); geoLines->SetLineModulate(di, color); geoLines->SetLineWidth(di, width); geoLines->SetLineStartTime(di, startTime); @@ -581,18 +774,113 @@ void AlertLayer::Impl::AddLine(std::shared_ptr& geoLines, std::placeholders::_1, std::placeholders::_2)); + const std::weak_ptr diWeak = di; gl::draw::GeoLines::RegisterEventHandler( di, std::bind(&AlertLayer::Impl::HandleGeoLinesEvent, this, - di, + diWeak, std::placeholders::_1)); } } -void AlertLayer::Impl::HandleGeoLinesEvent( - std::shared_ptr& di, QEvent* ev) +void AlertLayer::Impl::PopulateLines(bool alertActive) { + auto& alertLayerHandler = AlertLayerHandler::Instance(); + auto& geoLines = geoLines_.at(alertActive); + + geoLines->StartLines(); + + // Populate initial segments + auto segmentsIt = + alertLayerHandler.segmentsByType_.find({phenomenon_, alertActive}); + if (segmentsIt != alertLayerHandler.segmentsByType_.cend()) + { + for (auto& segment : segmentsIt->second) + { + AddAlert(segment); + } + } + + geoLines->FinishLines(); +} + +void AlertLayer::Impl::RepopulateLines() +{ + auto& alertLayerHandler = AlertLayerHandler::Instance(); + + // Take a shared lock to prevent handling additional alerts while populating + // initial lists + const std::shared_lock alertLock {alertLayerHandler.alertMutex_}; + + linesBySegment_.clear(); + segmentsByLine_.clear(); + + for (auto alertActive : {false, true}) + { + PopulateLines(alertActive); + } + + Q_EMIT self_->NeedsRendering(); +} + +void AlertLayer::Impl::UpdateLines() +{ + std::unique_lock lock {linesMutex_}; + + for (auto& segmentLine : linesBySegment_) + { + auto& segmentRecord = segmentLine.first; + auto& geoLineDrawItems = segmentLine.second; + auto& segment = segmentRecord->segment_; + bool alertActive = IsAlertActive(segment); + auto& lineData = GetLineData(segment, alertActive); + auto& geoLines = geoLines_.at(alertActive); + + const auto borderWidth = static_cast(lineData.borderWidth_); + const auto highlightWidth = static_cast(lineData.highlightWidth_); + const auto lineWidth = static_cast(lineData.lineWidth_); + + const float totalHighlightWidth = lineWidth + (highlightWidth * 2.0f); + const float totalBorderWidth = totalHighlightWidth + (borderWidth * 2.0f); + + // Border, highlight and line + std::size_t linesPerType = geoLineDrawItems.size() / 3; + + // Border + for (auto& borderLine : geoLineDrawItems | std::views::take(linesPerType)) + { + geoLines->SetLineModulate(borderLine, lineData.borderColor_); + geoLines->SetLineWidth(borderLine, totalBorderWidth); + } + + // Highlight + for (auto& highlightLine : geoLineDrawItems | + std::views::drop(linesPerType) | + std::views::take(linesPerType)) + { + geoLines->SetLineModulate(highlightLine, lineData.highlightColor_); + geoLines->SetLineWidth(highlightLine, totalHighlightWidth); + } + + // Line + for (auto& line : geoLineDrawItems | std::views::drop(linesPerType * 2)) + { + geoLines->SetLineModulate(line, lineData.lineColor_); + geoLines->SetLineWidth(line, lineWidth); + } + } +} + +void AlertLayer::Impl::HandleGeoLinesEvent( + std::weak_ptr& diWeak, QEvent* ev) +{ + const std::shared_ptr di = diWeak.lock(); + if (di == nullptr) + { + return; + } + switch (ev->type()) { case QEvent::Type::MouseButtonPress: @@ -613,8 +901,8 @@ void AlertLayer::Impl::HandleGeoLinesEvent( } void AlertLayer::Impl::HandleGeoLinesHover( - std::shared_ptr& di, - const QPointF& mouseGlobalPos) + const std::shared_ptr& di, + const QPointF& mouseGlobalPos) { if (di != lastHoverDi_) { @@ -638,6 +926,79 @@ void AlertLayer::Impl::HandleGeoLinesHover( } } +AlertLayer::Impl::LineData +AlertLayer::Impl::CreateLineData(const settings::LineSettings& lineSettings) +{ + return LineData { + .borderColor_ {lineSettings.GetBorderColorRgba32f()}, + .highlightColor_ {lineSettings.GetHighlightColorRgba32f()}, + .lineColor_ {lineSettings.GetLineColorRgba32f()}, + .borderWidth_ = + static_cast(lineSettings.border_width().GetValue()), + .highlightWidth_ = + static_cast(lineSettings.highlight_width().GetValue()), + .lineWidth_ = + static_cast(lineSettings.line_width().GetValue())}; +} + +void AlertLayer::Impl::UpdateLineData() +{ + auto& alertPalette = + settings::PaletteSettings().Instance().alert_palette(phenomenon_); + + for (auto threatCategory : ibw_.threatCategories_) + { + auto& palette = alertPalette.threat_category(threatCategory); + threatCategoryLineData_.insert_or_assign(threatCategory, + CreateLineData(palette)); + } + + if (ibw_.hasObservedTag_) + { + observedLineData_ = CreateLineData(alertPalette.observed()); + } + + if (ibw_.hasTornadoPossibleTag_) + { + tornadoPossibleLineData_ = + CreateLineData(alertPalette.tornado_possible()); + } + + inactiveLineData_ = CreateLineData(alertPalette.inactive()); +} + +AlertLayer::Impl::LineData& AlertLayer::Impl::GetLineData( + const std::shared_ptr& segment, bool alertActive) +{ + if (!alertActive) + { + return inactiveLineData_; + } + + for (auto& threatCategory : ibw_.threatCategories_) + { + if (segment->threatCategory_ == threatCategory) + { + if (threatCategory == awips::ibw::ThreatCategory::Base) + { + if (ibw_.hasObservedTag_ && segment->observed_) + { + return observedLineData_; + } + + if (ibw_.hasTornadoPossibleTag_ && segment->tornadoPossible_) + { + return tornadoPossibleLineData_; + } + } + + return threatCategoryLineData_.at(threatCategory); + } + } + + return threatCategoryLineData_.at(awips::ibw::ThreatCategory::Base); +}; + AlertLayerHandler& AlertLayerHandler::Instance() { static AlertLayerHandler alertLayerHandler_ {}; @@ -653,8 +1014,6 @@ size_t AlertTypeHash>::operator()( return seed; } -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map #include "alert_layer.moc" diff --git a/scwx-qt/source/scwx/qt/map/alert_layer.hpp b/scwx-qt/source/scwx/qt/map/alert_layer.hpp index 99609210..0416ff04 100644 --- a/scwx-qt/source/scwx/qt/map/alert_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/alert_layer.hpp @@ -1,18 +1,13 @@ #pragma once #include + #include #include #include -#include -#include -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { class AlertLayer : public DrawLayer @@ -21,13 +16,14 @@ class AlertLayer : public DrawLayer Q_DISABLE_COPY_MOVE(AlertLayer) public: - explicit AlertLayer(std::shared_ptr context, - scwx::awips::Phenomenon phenomenon); + explicit AlertLayer(const std::shared_ptr& glContext, + scwx::awips::Phenomenon phenomenon); ~AlertLayer(); - void Initialize() override final; - void Render(const QMapLibre::CustomLayerRenderParameters&) override final; - void Deinitialize() override final; + void Initialize(const std::shared_ptr& mapContext) final; + void Render(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters&) final; + void Deinitialize() final; static void InitializeHandler(); @@ -39,6 +35,4 @@ private: std::unique_ptr p; }; -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/color_table_layer.cpp b/scwx-qt/source/scwx/qt/map/color_table_layer.cpp index bdafce3f..f55bd94a 100644 --- a/scwx-qt/source/scwx/qt/map/color_table_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/color_table_layer.cpp @@ -14,87 +14,83 @@ # pragma warning(pop) #endif -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { static const std::string logPrefix_ = "scwx::qt::map::color_table_layer"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -class ColorTableLayerImpl +class ColorTableLayer::Impl { public: - explicit ColorTableLayerImpl() : - shaderProgram_(nullptr), - uMVPMatrixLocation_(GL_INVALID_INDEX), - vbo_ {GL_INVALID_INDEX}, - vao_ {GL_INVALID_INDEX}, - texture_ {GL_INVALID_INDEX}, - colorTable_ {}, - colorTableNeedsUpdate_ {true} - { - } - ~ColorTableLayerImpl() = default; + explicit Impl() = default; + ~Impl() = default; - std::shared_ptr shaderProgram_; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; - GLint uMVPMatrixLocation_; - std::array vbo_; - GLuint vao_; - GLuint texture_; + std::shared_ptr shaderProgram_ {nullptr}; - std::vector colorTable_; + GLint uMVPMatrixLocation_ {static_cast(GL_INVALID_INDEX)}; + std::array vbo_ {GL_INVALID_INDEX}; + GLuint vao_ {GL_INVALID_INDEX}; + GLuint texture_ {GL_INVALID_INDEX}; - bool colorTableNeedsUpdate_; + std::vector colorTable_ {}; + + bool colorTableNeedsUpdate_ {true}; }; -ColorTableLayer::ColorTableLayer(std::shared_ptr context) : - GenericLayer(context), p(std::make_unique()) +ColorTableLayer::ColorTableLayer(std::shared_ptr glContext) : + GenericLayer(std::move(glContext)), p(std::make_unique()) { } ColorTableLayer::~ColorTableLayer() = default; -void ColorTableLayer::Initialize() +void ColorTableLayer::Initialize(const std::shared_ptr& mapContext) { logger_->debug("Initialize()"); - gl::OpenGLFunctions& gl = context()->gl(); + auto glContext = gl_context(); // Load and configure overlay shader p->shaderProgram_ = - context()->GetShaderProgram(":/gl/texture1d.vert", ":/gl/texture1d.frag"); + glContext->GetShaderProgram(":/gl/texture1d.vert", ":/gl/texture1d.frag"); p->uMVPMatrixLocation_ = - gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); + glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); if (p->uMVPMatrixLocation_ == -1) { logger_->warn("Could not find uMVPMatrix"); } - gl.glGenTextures(1, &p->texture_); + glGenTextures(1, &p->texture_); p->shaderProgram_->Use(); // Generate a vertex array object - gl.glGenVertexArrays(1, &p->vao_); + glGenVertexArrays(1, &p->vao_); // Generate vertex buffer objects - gl.glGenBuffers(2, p->vbo_.data()); + glGenBuffers(2, p->vbo_.data()); - gl.glBindVertexArray(p->vao_); + glBindVertexArray(p->vao_); + + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + // NOLINTBEGIN(modernize-use-nullptr) // Bottom panel - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); - gl.glBufferData( + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + glBufferData( GL_ARRAY_BUFFER, sizeof(float) * 6 * 2, nullptr, GL_DYNAMIC_DRAW); - gl.glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, static_cast(0)); - gl.glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, static_cast(0)); + glEnableVertexAttribArray(0); // Color table panel texture coordinates + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) const float textureCoords[6][1] = {{0.0f}, // TL {0.0f}, // BL {1.0f}, // TR @@ -102,27 +98,31 @@ void ColorTableLayer::Initialize() {0.0f}, // BL {1.0f}, // TR {1.0f}}; // BR - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); - gl.glBufferData( - GL_ARRAY_BUFFER, sizeof(textureCoords), textureCoords, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + glBufferData(GL_ARRAY_BUFFER, + sizeof(textureCoords), + static_cast(textureCoords), + GL_STATIC_DRAW); - gl.glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 0, static_cast(0)); - gl.glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 0, static_cast(0)); + glEnableVertexAttribArray(1); - connect(context()->radar_product_view().get(), + // NOLINTEND(modernize-use-nullptr) + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + + connect(mapContext->radar_product_view().get(), &view::RadarProductView::ColorTableLutUpdated, this, [this]() { p->colorTableNeedsUpdate_ = true; }); } void ColorTableLayer::Render( + const std::shared_ptr& mapContext, const QMapLibre::CustomLayerRenderParameters& params) { - gl::OpenGLFunctions& gl = context()->gl(); - auto radarProductView = context()->radar_product_view(); + auto radarProductView = mapContext->radar_product_view(); - if (context()->radar_product_view() == nullptr || - !context()->radar_product_view()->IsInitialized()) + if (radarProductView == nullptr || !radarProductView->IsInitialized()) { // Defer rendering until view is initialized return; @@ -136,33 +136,36 @@ void ColorTableLayer::Render( p->shaderProgram_->Use(); // Set OpenGL blend mode for transparency - gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gl.glUniformMatrix4fv( + glUniformMatrix4fv( p->uMVPMatrixLocation_, 1, GL_FALSE, glm::value_ptr(projection)); if (p->colorTableNeedsUpdate_) { p->colorTable_ = radarProductView->color_table_lut(); - gl.glActiveTexture(GL_TEXTURE0); - gl.glBindTexture(GL_TEXTURE_1D, p->texture_); - gl.glTexImage1D(GL_TEXTURE_1D, - 0, - GL_RGBA, - (GLsizei) p->colorTable_.size(), - 0, - GL_RGBA, - GL_UNSIGNED_BYTE, - p->colorTable_.data()); - gl.glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - gl.glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - gl.glGenerateMipmap(GL_TEXTURE_1D); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_1D, p->texture_); + glTexImage1D(GL_TEXTURE_1D, + 0, + GL_RGBA, + (GLsizei) p->colorTable_.size(), + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + p->colorTable_.data()); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glGenerateMipmap(GL_TEXTURE_1D); } if (p->colorTable_.size() > 0 && radarProductView->sweep_time() != std::chrono::system_clock::time_point()) { + // NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays) + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + // Color table panel vertices const float vertexLX = 0.0f; const float vertexRX = static_cast(params.width); @@ -177,16 +180,28 @@ void ColorTableLayer::Render( {vertexRX, vertexBY}}; // BR // Draw vertices - gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); - gl.glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); - gl.glDrawArrays(GL_TRIANGLES, 0, 6); + glBindVertexArray(p->vao_); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + glBufferSubData(GL_ARRAY_BUFFER, + 0, + sizeof(vertices), + static_cast(vertices)); + glDrawArrays(GL_TRIANGLES, 0, 6); - context()->set_color_table_margins(QMargins {0, 0, 0, 10}); + static constexpr int kLeftMargin_ = 0; + static constexpr int kTopMargin_ = 0; + static constexpr int kRightMargin_ = 0; + static constexpr int kBottomMargin_ = 10; + + mapContext->set_color_table_margins( + QMargins {kLeftMargin_, kTopMargin_, kRightMargin_, kBottomMargin_}); + + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + // NOLINTEND(cppcoreguidelines-avoid-c-arrays) } else { - context()->set_color_table_margins(QMargins {}); + mapContext->set_color_table_margins(QMargins {}); } SCWX_GL_CHECK_ERROR(); @@ -196,20 +211,14 @@ void ColorTableLayer::Deinitialize() { logger_->debug("Deinitialize()"); - gl::OpenGLFunctions& gl = context()->gl(); - - gl.glDeleteVertexArrays(1, &p->vao_); - gl.glDeleteBuffers(2, p->vbo_.data()); - gl.glDeleteTextures(1, &p->texture_); + glDeleteVertexArrays(1, &p->vao_); + glDeleteBuffers(2, p->vbo_.data()); + glDeleteTextures(1, &p->texture_); p->uMVPMatrixLocation_ = GL_INVALID_INDEX; p->vao_ = GL_INVALID_INDEX; p->vbo_ = {GL_INVALID_INDEX}; p->texture_ = GL_INVALID_INDEX; - - context()->set_color_table_margins(QMargins {}); } -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/color_table_layer.hpp b/scwx-qt/source/scwx/qt/map/color_table_layer.hpp index c23dc2b8..17cb505c 100644 --- a/scwx-qt/source/scwx/qt/map/color_table_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/color_table_layer.hpp @@ -2,29 +2,25 @@ #include -namespace scwx +namespace scwx::qt::map { -namespace qt -{ -namespace map -{ - -class ColorTableLayerImpl; class ColorTableLayer : public GenericLayer { + Q_DISABLE_COPY_MOVE(ColorTableLayer) + public: - explicit ColorTableLayer(std::shared_ptr context); + explicit ColorTableLayer(std::shared_ptr glContext); ~ColorTableLayer(); - void Initialize() override final; - void Render(const QMapLibre::CustomLayerRenderParameters&) override final; - void Deinitialize() override final; + void Initialize(const std::shared_ptr& mapContext) final; + void Render(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters&) final; + void Deinitialize() final; private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.cpp b/scwx-qt/source/scwx/qt/map/draw_layer.cpp index 7d03d13a..f07cb2ac 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.cpp @@ -1,65 +1,146 @@ +#include #include +#include #include #include -namespace scwx -{ -namespace qt -{ -namespace map +#include + +#include +#include +#include +#include +#include + +namespace scwx::qt::map { static const std::string logPrefix_ = "scwx::qt::map::draw_layer"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -class DrawLayerImpl +class DrawLayer::Impl { public: - explicit DrawLayerImpl(std::shared_ptr context) : - context_ {context}, drawList_ {}, textureAtlas_ {GL_INVALID_INDEX} + explicit Impl(std::shared_ptr glContext, + const std::string& imGuiContextName) : + glContext_ {std::move(glContext)} { - } - ~DrawLayerImpl() {} + static size_t currentLayerId_ {0u}; + imGuiContextName_ = + fmt::format("{} {}", imGuiContextName, ++currentLayerId_); + // This must be initialized after the last line + // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) + imGuiContext_ = + model::ImGuiContextModel::Instance().CreateContext(imGuiContextName_); - std::shared_ptr context_; - std::vector> drawList_; - GLuint textureAtlas_; + // Initialize ImGui Qt backend + ImGui_ImplQt_Init(); + } + ~Impl() + { + // Set ImGui Context + ImGui::SetCurrentContext(imGuiContext_); + + // Shutdown ImGui Context + if (imGuiRendererInitialized_) + { + ImGui_ImplOpenGL3_Shutdown(); + } + ImGui_ImplQt_Shutdown(); + + // Destroy ImGui Context + model::ImGuiContextModel::Instance().DestroyContext(imGuiContextName_); + } + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + + std::shared_ptr glContext_; + + std::vector> drawList_ {}; + GLuint textureAtlas_ {GL_INVALID_INDEX}; std::uint64_t textureAtlasBuildCount_ {}; + + std::string imGuiContextName_; + ImGuiContext* imGuiContext_; + bool imGuiRendererInitialized_ {}; }; -DrawLayer::DrawLayer(const std::shared_ptr& context) : - GenericLayer(context), p(std::make_unique(context)) +DrawLayer::DrawLayer(std::shared_ptr glContext, + const std::string& imGuiContextName) : + GenericLayer(glContext), + p(std::make_unique(std::move(glContext), imGuiContextName)) { } DrawLayer::~DrawLayer() = default; -void DrawLayer::Initialize() +void DrawLayer::Initialize(const std::shared_ptr& mapContext) { - p->textureAtlas_ = p->context_->GetTextureAtlas(); + p->textureAtlas_ = p->glContext_->GetTextureAtlas(); for (auto& item : p->drawList_) { item->Initialize(); } + + ImGuiInitialize(mapContext); } -void DrawLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) +void DrawLayer::ImGuiFrameStart(const std::shared_ptr& mapContext) { - gl::OpenGLFunctions& gl = p->context_->gl(); - p->textureAtlas_ = p->context_->GetTextureAtlas(); + auto defaultFont = manager::FontManager::Instance().GetImGuiFont( + types::FontCategory::Default); + + // Setup ImGui Frame + ImGui::SetCurrentContext(p->imGuiContext_); + + // Start ImGui Frame + model::ImGuiContextModel::Instance().NewFrame(); + ImGui_ImplQt_NewFrame(mapContext->widget()); + ImGui_ImplOpenGL3_NewFrame(); + ImGui::NewFrame(); + ImGui::PushFont(defaultFont.first->font(), defaultFont.second.value()); +} + +void DrawLayer::ImGuiFrameEnd() +{ + // Pop default font + ImGui::PopFont(); + + // Render ImGui Frame + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); +} + +void DrawLayer::ImGuiInitialize(const std::shared_ptr& mapContext) +{ + ImGui::SetCurrentContext(p->imGuiContext_); + ImGui_ImplQt_RegisterWidget(mapContext->widget()); + ImGui_ImplOpenGL3_Init(); + p->imGuiRendererInitialized_ = true; +} + +void DrawLayer::RenderWithoutImGui( + const QMapLibre::CustomLayerRenderParameters& params) +{ + auto& glContext = p->glContext_; + + p->textureAtlas_ = glContext->GetTextureAtlas(); // Determine if the texture atlas changed since last render - std::uint64_t newTextureAtlasBuildCount = - p->context_->texture_buffer_count(); - bool textureAtlasChanged = + const std::uint64_t newTextureAtlasBuildCount = + glContext->texture_buffer_count(); + const bool textureAtlasChanged = newTextureAtlasBuildCount != p->textureAtlasBuildCount_; // Set OpenGL blend mode for transparency - gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gl.glActiveTexture(GL_TEXTURE0); - gl.glBindTexture(GL_TEXTURE_2D_ARRAY, p->textureAtlas_); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D_ARRAY, p->textureAtlas_); for (auto& item : p->drawList_) { @@ -69,6 +150,19 @@ void DrawLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) p->textureAtlasBuildCount_ = newTextureAtlasBuildCount; } +void DrawLayer::ImGuiSelectContext() +{ + ImGui::SetCurrentContext(p->imGuiContext_); +} + +void DrawLayer::Render(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters& params) +{ + ImGuiFrameStart(mapContext); + RenderWithoutImGui(params); + ImGuiFrameEnd(); +} + void DrawLayer::Deinitialize() { p->textureAtlas_ = GL_INVALID_INDEX; @@ -80,6 +174,7 @@ void DrawLayer::Deinitialize() } bool DrawLayer::RunMousePicking( + const std::shared_ptr& /* mapContext */, const QMapLibre::CustomLayerRenderParameters& params, const QPointF& mouseLocalPos, const QPointF& mouseGlobalPos, @@ -90,15 +185,15 @@ bool DrawLayer::RunMousePicking( bool itemPicked = false; // For each draw item in the draw list in reverse - for (auto it = p->drawList_.rbegin(); it != p->drawList_.rend(); ++it) + for (auto& it : std::ranges::reverse_view(p->drawList_)) { // Run mouse picking on each draw item - if ((*it)->RunMousePicking(params, - mouseLocalPos, - mouseGlobalPos, - mouseCoords, - mouseGeoCoords, - eventHandler)) + if (it->RunMousePicking(params, + mouseLocalPos, + mouseGlobalPos, + mouseCoords, + mouseGeoCoords, + eventHandler)) { // If a draw item was picked, don't process additional items itemPicked = true; @@ -114,6 +209,4 @@ void DrawLayer::AddDrawItem(const std::shared_ptr& drawItem) p->drawList_.push_back(drawItem); } -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.hpp b/scwx-qt/source/scwx/qt/map/draw_layer.hpp index 22dfa76c..f0589eef 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.hpp @@ -3,27 +3,26 @@ #include #include -namespace scwx +namespace scwx::qt::map { -namespace qt -{ -namespace map -{ - -class DrawLayerImpl; class DrawLayer : public GenericLayer { + Q_DISABLE_COPY_MOVE(DrawLayer) + public: - explicit DrawLayer(const std::shared_ptr& context); + explicit DrawLayer(std::shared_ptr glContext, + const std::string& imGuiContextName); virtual ~DrawLayer(); - virtual void Initialize() override; - virtual void Render(const QMapLibre::CustomLayerRenderParameters&) override; - virtual void Deinitialize() override; + void Initialize(const std::shared_ptr& mapContext) override; + void Render(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters&) override; + void Deinitialize() override; - virtual bool - RunMousePicking(const QMapLibre::CustomLayerRenderParameters& params, + bool + RunMousePicking(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters& params, const QPointF& mouseLocalPos, const QPointF& mouseGlobalPos, const glm::vec2& mouseCoords, @@ -32,11 +31,16 @@ public: protected: void AddDrawItem(const std::shared_ptr& drawItem); + void ImGuiFrameStart(const std::shared_ptr& mapContext); + void ImGuiFrameEnd(); + void ImGuiInitialize(const std::shared_ptr& mapContext); + void + RenderWithoutImGui(const QMapLibre::CustomLayerRenderParameters& params); + void ImGuiSelectContext(); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/generic_layer.cpp b/scwx-qt/source/scwx/qt/map/generic_layer.cpp index 97f22097..ec88d1f1 100644 --- a/scwx-qt/source/scwx/qt/map/generic_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/generic_layer.cpp @@ -1,32 +1,34 @@ #include -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { -class GenericLayerImpl +class GenericLayer::Impl { public: - explicit GenericLayerImpl(std::shared_ptr context) : - context_ {context} + explicit Impl(std::shared_ptr glContext) : + glContext_ {std::move(glContext)} { } - ~GenericLayerImpl() {} + ~Impl() = default; - std::shared_ptr context_; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + + std::shared_ptr glContext_; }; -GenericLayer::GenericLayer(std::shared_ptr context) : - p(std::make_unique(context)) +GenericLayer::GenericLayer(std::shared_ptr glContext) : + p(std::make_unique(std::move(glContext))) { } GenericLayer::~GenericLayer() = default; bool GenericLayer::RunMousePicking( + const std::shared_ptr& /* mapContext */, const QMapLibre::CustomLayerRenderParameters& /* params */, const QPointF& /* mouseLocalPos */, const QPointF& /* mouseGlobalPos */, @@ -38,11 +40,9 @@ bool GenericLayer::RunMousePicking( return false; } -std::shared_ptr GenericLayer::context() const +std::shared_ptr GenericLayer::gl_context() const { - return p->context_; + return p->glContext_; } -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/generic_layer.hpp b/scwx-qt/source/scwx/qt/map/generic_layer.hpp index 0fee92ab..2b713c9c 100644 --- a/scwx-qt/source/scwx/qt/map/generic_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/generic_layer.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -10,30 +11,27 @@ #include #include -namespace scwx +namespace scwx::qt::map { -namespace qt -{ -namespace map -{ - -class GenericLayerImpl; class GenericLayer : public QObject { Q_OBJECT + Q_DISABLE_COPY_MOVE(GenericLayer) public: - explicit GenericLayer(std::shared_ptr context); + explicit GenericLayer(std::shared_ptr glContext); virtual ~GenericLayer(); - virtual void Initialize() = 0; - virtual void Render(const QMapLibre::CustomLayerRenderParameters&) = 0; - virtual void Deinitialize() = 0; + virtual void Initialize(const std::shared_ptr& mapContext) = 0; + virtual void Render(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters&) = 0; + virtual void Deinitialize() = 0; /** * @brief Run mouse picking on the layer. * + * @param [in] mapContext Map context * @param [in] params Custom layer render parameters * @param [in] mouseLocalPos Mouse cursor widget position * @param [in] mouseGlobalPos Mouse cursor screen position @@ -44,7 +42,8 @@ public: * @return true if a draw item was picked, otherwise false */ virtual bool - RunMousePicking(const QMapLibre::CustomLayerRenderParameters& params, + RunMousePicking(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters& params, const QPointF& mouseLocalPos, const QPointF& mouseGlobalPos, const glm::vec2& mouseCoords, @@ -55,12 +54,11 @@ signals: void NeedsRendering(); protected: - std::shared_ptr context() const; + [[nodiscard]] std::shared_ptr gl_context() const; private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/layer_wrapper.cpp b/scwx-qt/source/scwx/qt/map/layer_wrapper.cpp index 88fc7df9..5a60f90b 100644 --- a/scwx-qt/source/scwx/qt/map/layer_wrapper.cpp +++ b/scwx-qt/source/scwx/qt/map/layer_wrapper.cpp @@ -1,27 +1,31 @@ #include -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { -class LayerWrapperImpl +class LayerWrapper::Impl { public: - explicit LayerWrapperImpl(std::shared_ptr layer) : - layer_ {layer} + explicit Impl(std::shared_ptr layer, + std::shared_ptr mapContext) : + layer_ {std::move(layer)}, mapContext_ {std::move(mapContext)} { } - ~LayerWrapperImpl() {} + ~Impl() = default; + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; std::shared_ptr layer_; + std::shared_ptr mapContext_; }; -LayerWrapper::LayerWrapper(std::shared_ptr layer) : - p(std::make_unique(layer)) +LayerWrapper::LayerWrapper(std::shared_ptr layer, + std::shared_ptr mapContext) : + p(std::make_unique(std::move(layer), std::move(mapContext))) { } LayerWrapper::~LayerWrapper() = default; @@ -34,7 +38,7 @@ void LayerWrapper::initialize() auto& layer = p->layer_; if (layer != nullptr) { - layer->Initialize(); + layer->Initialize(p->mapContext_); } } @@ -43,7 +47,7 @@ void LayerWrapper::render(const QMapLibre::CustomLayerRenderParameters& params) auto& layer = p->layer_; if (layer != nullptr) { - layer->Render(params); + layer->Render(p->mapContext_, params); } } @@ -58,6 +62,4 @@ void LayerWrapper::deinitialize() } } -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/layer_wrapper.hpp b/scwx-qt/source/scwx/qt/map/layer_wrapper.hpp index 6e0e44ee..ae133c29 100644 --- a/scwx-qt/source/scwx/qt/map/layer_wrapper.hpp +++ b/scwx-qt/source/scwx/qt/map/layer_wrapper.hpp @@ -1,20 +1,16 @@ #pragma once #include +#include -namespace scwx +namespace scwx::qt::map { -namespace qt -{ -namespace map -{ - -class LayerWrapperImpl; class LayerWrapper : public QMapLibre::CustomLayerHostInterface { public: - explicit LayerWrapper(std::shared_ptr layer); + explicit LayerWrapper(std::shared_ptr layer, + std::shared_ptr mapContext); ~LayerWrapper(); LayerWrapper(const LayerWrapper&) = delete; @@ -23,14 +19,13 @@ public: LayerWrapper(LayerWrapper&&) noexcept; LayerWrapper& operator=(LayerWrapper&&) noexcept; - void initialize() override final; - void render(const QMapLibre::CustomLayerRenderParameters&) override final; - void deinitialize() override final; + void initialize() final; + void render(const QMapLibre::CustomLayerRenderParameters&) final; + void deinitialize() final; private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/map_context.cpp b/scwx-qt/source/scwx/qt/map/map_context.cpp index c659c432..1d1b5c4d 100644 --- a/scwx-qt/source/scwx/qt/map/map_context.cpp +++ b/scwx-qt/source/scwx/qt/map/map_context.cpp @@ -3,11 +3,7 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { class MapContext::Impl @@ -25,9 +21,9 @@ public: float pixelRatio_ {1.0f}; common::RadarProductGroup radarProductGroup_ { common::RadarProductGroup::Unknown}; - std::string radarProduct_ {"???"}; - int16_t radarProductCode_ {0}; - QMapLibre::CustomLayerRenderParameters renderParameters_ {}; + std::string radarProduct_ {"???"}; + int16_t radarProductCode_ {0}; + std::shared_ptr radarSite_ {nullptr}; MapProvider mapProvider_ {MapProvider::Unknown}; std::string mapCopyrights_ {}; @@ -37,6 +33,8 @@ public: std::shared_ptr overlayProductView_ {nullptr}; std::shared_ptr radarProductView_; + + QWidget* widget_; }; MapContext::MapContext( @@ -105,14 +103,19 @@ std::string MapContext::radar_product() const return p->radarProduct_; } +std::shared_ptr MapContext::radar_site() const +{ + return p->radarSite_; +} + int16_t MapContext::radar_product_code() const { return p->radarProductCode_; } -QMapLibre::CustomLayerRenderParameters MapContext::render_parameters() const +QWidget* MapContext::widget() const { - return p->renderParameters_; + return p->widget_; } void MapContext::set_map(const std::shared_ptr& map) @@ -173,12 +176,14 @@ void MapContext::set_radar_product_code(int16_t radarProductCode) p->radarProductCode_ = radarProductCode; } -void MapContext::set_render_parameters( - const QMapLibre::CustomLayerRenderParameters& params) +void MapContext::set_radar_site(const std::shared_ptr& site) { - p->renderParameters_ = params; + p->radarSite_ = site; } -} // namespace map -} // namespace qt -} // namespace scwx +void MapContext::set_widget(QWidget* widget) +{ + p->widget_ = widget; +} + +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/map_context.hpp b/scwx-qt/source/scwx/qt/map/map_context.hpp index 86f49b8f..ba319b95 100644 --- a/scwx-qt/source/scwx/qt/map/map_context.hpp +++ b/scwx-qt/source/scwx/qt/map/map_context.hpp @@ -1,31 +1,27 @@ #pragma once -#include #include #include #include +#include #include #include -namespace scwx -{ -namespace qt -{ -namespace view +namespace scwx::qt::view { class OverlayProductView; class RadarProductView; -} // namespace view +} // namespace scwx::qt::view -namespace map +namespace scwx::qt::map { struct MapSettings; -class MapContext : public gl::GlContext +class MapContext { public: explicit MapContext( @@ -38,19 +34,22 @@ public: MapContext(MapContext&&) noexcept; MapContext& operator=(MapContext&&) noexcept; - std::weak_ptr map() const; - std::string map_copyrights() const; - MapProvider map_provider() const; - MapSettings& settings(); - QMargins color_table_margins() const; - float pixel_ratio() const; - common::Coordinate mouse_coordinate() const; - std::shared_ptr overlay_product_view() const; - std::shared_ptr radar_product_view() const; - common::RadarProductGroup radar_product_group() const; - std::string radar_product() const; - int16_t radar_product_code() const; - QMapLibre::CustomLayerRenderParameters render_parameters() const; + [[nodiscard]] std::weak_ptr map() const; + [[nodiscard]] std::string map_copyrights() const; + [[nodiscard]] MapProvider map_provider() const; + [[nodiscard]] MapSettings& settings(); + [[nodiscard]] QMargins color_table_margins() const; + [[nodiscard]] float pixel_ratio() const; + [[nodiscard]] common::Coordinate mouse_coordinate() const; + [[nodiscard]] std::shared_ptr + overlay_product_view() const; + [[nodiscard]] std::shared_ptr + radar_product_view() const; + [[nodiscard]] common::RadarProductGroup radar_product_group() const; + [[nodiscard]] std::string radar_product() const; + [[nodiscard]] int16_t radar_product_code() const; + [[nodiscard]] std::shared_ptr radar_site() const; + [[nodiscard]] QWidget* widget() const; void set_map(const std::shared_ptr& map); void set_map_copyrights(const std::string& copyrights); @@ -65,8 +64,8 @@ public: void set_radar_product_group(common::RadarProductGroup radarProductGroup); void set_radar_product(const std::string& radarProduct); void set_radar_product_code(int16_t radarProductCode); - void - set_render_parameters(const QMapLibre::CustomLayerRenderParameters& params); + void set_radar_site(const std::shared_ptr& site); + void set_widget(QWidget* widget); private: class Impl; @@ -74,6 +73,4 @@ private: std::unique_ptr p; }; -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/map_provider.cpp b/scwx-qt/source/scwx/qt/map/map_provider.cpp index 586d012c..b1b5979d 100644 --- a/scwx-qt/source/scwx/qt/map/map_provider.cpp +++ b/scwx-qt/source/scwx/qt/map/map_provider.cpp @@ -5,11 +5,7 @@ #include -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { static const std::unordered_map mapProviderName_ { @@ -24,10 +20,10 @@ static const std::vector mapboxDrawBelow_ { static const std::unordered_map mapProviderInfo_ { {MapProvider::Mapbox, MapProviderInfo { - .mapProvider_ {MapProvider::Mapbox}, + .mapProvider_ = MapProvider::Mapbox, .cacheDbName_ {"mbgl-cache.db"}, - .providerTemplate_ { - QMapLibre::Settings::ProviderTemplate::MapboxProvider}, + .providerTemplate_ = + QMapLibre::Settings::ProviderTemplate::MapboxProvider, .mapStyles_ { {.name_ {"Streets"}, .url_ {"mapbox://styles/mapbox/streets-v11"}, @@ -117,10 +113,10 @@ static const std::unordered_map mapProviderInfo_ { .drawBelow_ {mapboxDrawBelow_}}}}}, {MapProvider::MapTiler, MapProviderInfo { - .mapProvider_ {MapProvider::MapTiler}, + .mapProvider_ = MapProvider::MapTiler, .cacheDbName_ {"maptiler-cache.db"}, - .providerTemplate_ { - QMapLibre::Settings::ProviderTemplate::MapTilerProvider}, + .providerTemplate_ = + QMapLibre::Settings::ProviderTemplate::MapTilerProvider, .mapStyles_ { {.name_ {"Satellite"}, .url_ {"https://api.maptiler.com/maps/hybrid/style.json"}, @@ -149,6 +145,9 @@ static const std::unordered_map mapProviderInfo_ { {.name_ {"Landscape"}, .url_ {"https://api.maptiler.com/maps/landscape/style.json"}, .drawBelow_ {"Runway"}}, + {.name_ {"Ocean"}, + .url_ {"https://api.maptiler.com/maps/ocean/style.json"}, + .drawBelow_ {"Landform labels"}}, {.name_ {"Outdoor"}, .url_ {"https://api.maptiler.com/maps/outdoor-v2/style.json"}, .drawBelow_ {"aeroway_runway", "Aeroway"}}, @@ -167,6 +166,9 @@ static const std::unordered_map mapProviderInfo_ { .url_ {"https://api.maptiler.com/maps/ch-swisstopo-lbm-vivid/" "style.json"}, .drawBelow_ {"pattern_landcover_vineyard", "Vineyard pattern"}}, + {.name_ {"Toner"}, + .url_ {"https://api.maptiler.com/maps/toner-v2/style.json"}, + .drawBelow_ {"Bridge"}}, {.name_ {"Topo"}, .url_ {"https://api.maptiler.com/maps/topo-v2/style.json"}, .drawBelow_ {"aeroway_runway", "Runway"}}, @@ -237,6 +239,4 @@ const MapProviderInfo& GetMapProviderInfo(MapProvider mapProvider) return mapProviderInfo_.at(mapProvider); } -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/map_provider.hpp b/scwx-qt/source/scwx/qt/map/map_provider.hpp index 5bdd67fc..1ef6fe80 100644 --- a/scwx-qt/source/scwx/qt/map/map_provider.hpp +++ b/scwx-qt/source/scwx/qt/map/map_provider.hpp @@ -6,11 +6,7 @@ #include -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { enum class MapProvider @@ -19,9 +15,8 @@ enum class MapProvider MapTiler, Unknown }; -typedef scwx::util:: - Iterator - MapProviderIterator; +using MapProviderIterator = scwx::util:: + Iterator; struct MapStyle { @@ -29,7 +24,7 @@ struct MapStyle std::string url_; std::vector drawBelow_; - bool IsValid() const; + [[nodiscard]] bool IsValid() const; }; struct MapProviderInfo @@ -45,6 +40,4 @@ std::string GetMapProviderName(MapProvider mapProvider); std::string GetMapProviderApiKey(MapProvider mapProvider); const MapProviderInfo& GetMapProviderInfo(MapProvider mapProvider); -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/map_settings.hpp b/scwx-qt/source/scwx/qt/map/map_settings.hpp index 642c8fa1..05a69b42 100644 --- a/scwx-qt/source/scwx/qt/map/map_settings.hpp +++ b/scwx-qt/source/scwx/qt/map/map_settings.hpp @@ -1,26 +1,21 @@ #pragma once -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { struct MapSettings { - explicit MapSettings() : isActive_ {false} {} - ~MapSettings() = default; + explicit MapSettings() = default; + ~MapSettings() = default; - MapSettings(const MapSettings&) = delete; + MapSettings(const MapSettings&) = delete; MapSettings& operator=(const MapSettings&) = delete; - MapSettings(MapSettings&&) noexcept = default; + MapSettings(MapSettings&&) noexcept = default; MapSettings& operator=(MapSettings&&) noexcept = default; - bool isActive_; + bool isActive_ {false}; + bool radarWireframeEnabled_ {false}; }; -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index e718bc0e..537318f0 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -18,7 +19,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -27,7 +30,10 @@ #include #include +#include +#include #include +#include #include #include @@ -48,14 +54,11 @@ #include #include #include +#include #include #include -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { static const std::string logPrefix_ = "scwx::qt::map::map_widget"; @@ -66,14 +69,15 @@ class MapWidgetImpl : public QObject Q_OBJECT public: - explicit MapWidgetImpl(MapWidget* widget, - std::size_t id, - const QMapLibre::Settings& settings) : + explicit MapWidgetImpl(MapWidget* widget, + std::size_t id, + QMapLibre::Settings settings, + std::shared_ptr glContext) : id_ {id}, uuid_ {boost::uuids::random_generator()()}, - context_ {std::make_shared()}, + glContext_ {std::move(glContext)}, widget_ {widget}, - settings_(settings), + settings_(std::move(settings)), map_(), layerList_ {}, imGuiRendererInitialized_ {false}, @@ -81,6 +85,7 @@ public: radarProductLayer_ {nullptr}, overlayLayer_ {nullptr}, placefileLayer_ {nullptr}, + markerLayer_ {nullptr}, colorTableLayer_ {nullptr}, autoRefreshEnabled_ {true}, autoUpdateEnabled_ {true}, @@ -92,7 +97,8 @@ public: prevLongitude_ {0.0}, prevZoom_ {0.0}, prevBearing_ {0.0}, - prevPitch_ {0.0} + prevPitch_ {0.0}, + tiltsToIndices_ {} { // Create views auto overlayProductView = std::make_shared(); @@ -103,13 +109,17 @@ public: map::AlertLayer::InitializeHandler(); auto& generalSettings = settings::GeneralSettings::Instance(); + auto& mapSettings = settings::MapSettings::Instance(); // Initialize context context_->set_map_provider( GetMapProvider(generalSettings.map_provider().GetValue())); context_->set_overlay_product_view(overlayProductView); + context_->set_widget(widget); + // Initialize map data SetRadarSite(generalSettings.default_radar_site().GetValue()); + smoothingEnabled_ = mapSettings.smoothing_enabled(id).GetValue(); // Create ImGui Context static size_t currentMapId_ {0u}; @@ -121,12 +131,13 @@ public: ImGui_ImplQt_Init(); InitializeCustomStyles(); - - ConnectSignals(); } ~MapWidgetImpl() { + // Disconnect signals + colorPaletteConnection_.disconnect(); + DeinitializeCustomStyles(); // Set ImGui Context @@ -148,9 +159,9 @@ public: void AddLayer(types::LayerType type, types::LayerDescription description, const std::string& before = {}); - void AddLayer(const std::string& id, - std::shared_ptr layer, - const std::string& before = {}); + void AddLayer(const std::string& id, + const std::shared_ptr& layer, + const std::string& before = {}); void AddLayers(); void AddPlacefileLayer(const std::string& placefileName, const std::string& before); @@ -160,7 +171,7 @@ public: void HandleHotkeyPressed(types::Hotkey hotkey, bool isAutoRepeat); void HandleHotkeyReleased(types::Hotkey hotkey); void HandleHotkeyUpdates(); - void ImGuiCheckFonts(); + void HandlePinchGesture(QPinchGesture* gesture); void InitializeCustomStyles(); void InitializeNewRadarProductView(const std::string& colorPalette); void RadarProductManagerConnect(); @@ -171,11 +182,12 @@ public: void SelectNearestRadarSite(double latitude, double longitude, std::optional type); - void SetRadarSite(const std::string& radarSite); + void SetRadarSite(const std::string& radarSite, + bool checkProductAvailability = false); + void UpdateColorTable(const std::string& colorPalette); void UpdateLoadedStyle(); bool UpdateStoredMapParameters(); - - std::string FindMapSymbologyLayer(); + void CheckLevel3Availability(); common::Level2Product GetLevel2ProductOrDefault(const std::string& productName) const; @@ -187,7 +199,8 @@ public: std::size_t id_; boost::uuids::uuid uuid_; - std::shared_ptr context_; + std::shared_ptr context_ {std::make_shared()}; + std::shared_ptr glContext_; MapWidget* widget_; QMapLibre::Settings settings_; @@ -199,20 +212,22 @@ public: const std::vector emptyStyles_ {}; std::vector customStyles_ { MapStyle {.name_ {"Custom"}, .url_ {}, .drawBelow_ {}}}; - QStringList styleLayers_; - types::LayerVector customLayers_; + QStringList styleLayers_; boost::uuids::uuid customStyleUrlChangedCallbackId_ {}; boost::uuids::uuid customStyleDrawBelowChangedCallbackId_ {}; + boost::signals2::scoped_connection colorPaletteConnection_ {}; + ImGuiContext* imGuiContext_; std::string imGuiContextName_; - bool imGuiRendererInitialized_; - std::uint64_t imGuiFontsBuildCount_ {}; + bool imGuiRendererInitialized_ {false}; std::shared_ptr layerModel_ { model::LayerModel::Instance()}; + ui::EditMarkerDialog* editMarkerDialog_ {nullptr}; + std::shared_ptr hotkeyManager_ { manager::HotkeyManager::Instance()}; std::shared_ptr placefileManager_ { @@ -223,6 +238,7 @@ public: std::shared_ptr overlayLayer_; std::shared_ptr overlayProductLayer_ {nullptr}; std::shared_ptr placefileLayer_; + std::shared_ptr markerLayer_; std::shared_ptr colorTableLayer_; std::shared_ptr radarSiteLayer_ {nullptr}; @@ -230,6 +246,7 @@ public: bool autoRefreshEnabled_; bool autoUpdateEnabled_; + bool smoothingEnabled_ {false}; common::Level2Product selectedLevel2Product_; @@ -258,12 +275,21 @@ public: std::set activeHotkeys_ {}; std::chrono::system_clock::time_point prevHotkeyTime_ {}; + bool productAvailabilityCheckNeeded_ {false}; + bool productAvailabilityUpdated_ {false}; + bool productAvailabilityProductSelected_ {false}; + + std::unordered_map tiltsToIndices_; + size_t currentTiltIndex_ {0}; + public slots: void Update(); }; -MapWidget::MapWidget(std::size_t id, const QMapLibre::Settings& settings) : - p(std::make_unique(this, id, settings)) +MapWidget::MapWidget(std::size_t id, + const QMapLibre::Settings& settings, + std::shared_ptr glContext) : + p(std::make_unique(this, id, settings, std::move(glContext))) { if (settings::GeneralSettings::Instance().anti_aliasing_enabled().GetValue()) { @@ -274,7 +300,15 @@ MapWidget::MapWidget(std::size_t id, const QMapLibre::Settings& settings) : setFocusPolicy(Qt::StrongFocus); + grabGesture(Qt::GestureType::PinchGesture); + ImGui_ImplQt_RegisterWidget(this); + + // Qt parent deals with memory management + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + p->editMarkerDialog_ = new ui::EditMarkerDialog(this); + + p->ConnectSignals(); } MapWidget::~MapWidget() @@ -354,7 +388,7 @@ void MapWidgetImpl::ConnectSignals() connect(placefileManager_.get(), &manager::PlacefileManager::PlacefileUpdated, widget_, - [this]() { widget_->update(); }); + static_cast(&QWidget::update)); // When the layer model changes, update the layers connect(layerModel_.get(), @@ -413,6 +447,14 @@ void MapWidgetImpl::ConnectSignals() &manager::HotkeyManager::HotkeyReleased, this, &MapWidgetImpl::HandleHotkeyReleased); + connect(widget_, + &MapWidget::RadarSiteUpdated, + widget_, + [this](const std::shared_ptr&) + { + productAvailabilityProductSelected_ = true; + CheckLevel3Availability(); + }); } void MapWidgetImpl::HandleHotkeyPressed(types::Hotkey hotkey, bool isAutoRepeat) @@ -421,6 +463,16 @@ void MapWidgetImpl::HandleHotkeyPressed(types::Hotkey hotkey, bool isAutoRepeat) switch (hotkey) { + case types::Hotkey::AddLocationMarker: + if (hasMouse_) + { + auto coordinate = map_->coordinateForPixel(lastPos_); + + editMarkerDialog_->setup(coordinate.first, coordinate.second); + editMarkerDialog_->show(); + } + break; + case types::Hotkey::ChangeMapStyle: if (context_->settings().isActive_) { @@ -560,6 +612,15 @@ void MapWidgetImpl::HandleHotkeyUpdates() } } +void MapWidgetImpl::HandlePinchGesture(QPinchGesture* gesture) +{ + if (gesture->changeFlags() & QPinchGesture::ChangeFlag::ScaleFactorChanged) + { + map_->scaleBy(gesture->scaleFactor(), + widget_->mapFromGlobal(gesture->centerPoint())); + } +} + common::Level3ProductCategoryMap MapWidget::GetAvailableLevel3Categories() { if (p->radarProductManager_ != nullptr) @@ -572,7 +633,7 @@ common::Level3ProductCategoryMap MapWidget::GetAvailableLevel3Categories() } } -float MapWidget::GetElevation() const +std::optional MapWidget::GetElevation() const { auto radarProductView = p->context_->radar_product_view(); @@ -582,7 +643,7 @@ float MapWidget::GetElevation() const } else { - return 0.0f; + return {}; } } @@ -600,6 +661,11 @@ std::vector MapWidget::GetElevationCuts() const } } +std::optional MapWidget::GetIncomingLevel2Elevation() const +{ + return p->radarProductManager_->incoming_level_2_elevation(); +} + common::Level2Product MapWidgetImpl::GetLevel2ProductOrDefault(const std::string& productName) const { @@ -724,6 +790,40 @@ std::uint16_t MapWidget::GetVcp() const } } +bool MapWidget::GetRadarWireframeEnabled() const +{ + return p->context_->settings().radarWireframeEnabled_; +} + +void MapWidget::SetRadarWireframeEnabled(bool wireframeEnabled) +{ + p->context_->settings().radarWireframeEnabled_ = wireframeEnabled; + QMetaObject::invokeMethod( + this, static_cast(&QWidget::update)); +} + +bool MapWidget::GetSmoothingEnabled() const +{ + return p->smoothingEnabled_; +} + +void MapWidget::SetSmoothingEnabled(bool smoothingEnabled) +{ + p->smoothingEnabled_ = smoothingEnabled; + + auto radarProductView = p->context_->radar_product_view(); + if (radarProductView != nullptr) + { + radarProductView->set_smoothing_enabled(smoothingEnabled); + radarProductView->Update(); + } +} + +const scwx::util::time_zone* MapWidget::GetDefaultTimeZone() const +{ + return p->radarProductManager_->default_time_zone(); +} + void MapWidget::SelectElevation(float elevation) { auto radarProductView = p->context_->radar_product_view(); @@ -761,6 +861,17 @@ void MapWidget::SelectRadarProduct(common::RadarProductGroup group, productCode = common::GetLevel3ProductCodeByAwipsId(productName); } + if (group == common::RadarProductGroup::Level3) + { + const auto& tiltIndex = p->tiltsToIndices_.find(productName); + p->currentTiltIndex_ = + tiltIndex != p->tiltsToIndices_.cend() ? tiltIndex->second : 0; + } + else + { + p->currentTiltIndex_ = 0; + } + if (radarProductView == nullptr || radarProductView->GetRadarProductGroup() != group || (radarProductView->GetRadarProductGroup() == @@ -772,6 +883,7 @@ void MapWidget::SelectRadarProduct(common::RadarProductGroup group, radarProductView = view::RadarProductViewFactory::Create( group, productName, productCode, p->radarProductManager_); + radarProductView->set_smoothing_enabled(p->smoothingEnabled_); p->context_->set_radar_product_view(radarProductView); p->RadarProductViewConnect(); @@ -798,6 +910,13 @@ void MapWidget::SelectRadarProduct(common::RadarProductGroup group, (group == common::RadarProductGroup::Level2) ? common::GetLevel2Palette(common::GetLevel2Product(productName)) : common::GetLevel3Palette(productCode); + + auto& paletteSetting = + settings::PaletteSettings::Instance().palette(palette); + + p->colorPaletteConnection_ = paletteSetting.changed_signal().connect( + [this, palette]() { p->UpdateColorTable(palette); }); + p->InitializeNewRadarProductView(palette); } else if (update) @@ -857,18 +976,13 @@ void MapWidget::SelectRadarSite(std::shared_ptr radarSite, p->map_->setCoordinate( {radarSite->latitude(), radarSite->longitude()}); } - p->SetRadarSite(radarSite->id()); + p->SetRadarSite(radarSite->id(), true); p->Update(); // Select products from new site if (radarProductView != nullptr) { radarProductView->set_radar_product_manager(p->radarProductManager_); - SelectRadarProduct(radarProductView->GetRadarProductGroup(), - radarProductView->GetRadarProductName(), - 0, - radarProductView->selected_time(), - false); } p->AddLayers(); @@ -900,7 +1014,8 @@ void MapWidget::SelectTime(std::chrono::system_clock::time_point time) void MapWidget::SetActive(bool isActive) { p->context_->settings().isActive_ = isActive; - update(); + QMetaObject::invokeMethod( + this, static_cast(&QWidget::update)); } void MapWidget::SetAutoRefresh(bool enabled) @@ -1016,14 +1131,18 @@ void MapWidget::UpdateMouseCoordinate(const common::Coordinate& coordinate) { if (p->context_->mouse_coordinate() != coordinate) { + auto& generalSettings = settings::GeneralSettings::Instance(); + p->context_->set_mouse_coordinate(coordinate); auto keyboardModifiers = QGuiApplication::keyboardModifiers(); - if (keyboardModifiers != Qt::KeyboardModifier::NoModifier || + if (generalSettings.cursor_icon_always_on().GetValue() || + keyboardModifiers != Qt::KeyboardModifier::NoModifier || keyboardModifiers != p->lastKeyboardModifiers_) { - update(); + QMetaObject::invokeMethod( + this, static_cast(&QWidget::update)); } p->lastKeyboardModifiers_ = keyboardModifiers; @@ -1070,43 +1189,6 @@ void MapWidget::DumpLayerList() const logger_->info("Layers: {}", p->map_->layerIds().join(", ").toStdString()); } -std::string MapWidgetImpl::FindMapSymbologyLayer() -{ - std::string before = "ferry"; - - for (const QString& qlayer : styleLayers_) - { - const std::string layer = qlayer.toStdString(); - - // Draw below layers defined in map style - auto it = std::find_if(currentStyle_->drawBelow_.cbegin(), - currentStyle_->drawBelow_.cend(), - [&layer](const std::string& styleLayer) -> bool - { - // Perform case-insensitive matching - RE2 re {"(?i)" + styleLayer}; - if (re.ok()) - { - return RE2::FullMatch(layer, re); - } - else - { - // Fall back to basic comparison if RE - // doesn't compile - return layer == styleLayer; - } - }); - - if (it != currentStyle_->drawBelow_.cend()) - { - before = layer; - break; - } - } - - return before; -} - void MapWidgetImpl::AddLayers() { if (styleLayers_.isEmpty()) @@ -1127,22 +1209,23 @@ void MapWidgetImpl::AddLayers() placefileLayers_.clear(); // Update custom layer list from model - customLayers_ = model::LayerModel::Instance()->GetLayers(); + types::LayerVector customLayers = model::LayerModel::Instance()->GetLayers(); // Start by drawing layers before any style-defined layers std::string before = styleLayers_.front().toStdString(); // Loop through each custom layer in reverse order - for (auto it = customLayers_.crbegin(); it != customLayers_.crend(); ++it) + for (const auto& customLayer : std::ranges::reverse_view(customLayers)) { - if (it->type_ == types::LayerType::Map) + if (customLayer.type_ == types::LayerType::Map) { // Style-defined map layers - switch (std::get(it->description_)) + switch (std::get(customLayer.description_)) { // Subsequent layers are drawn underneath the map symbology layer case types::MapLayer::MapUnderlay: - before = FindMapSymbologyLayer(); + before = util::maplibre::FindMapSymbologyLayer( + styleLayers_, currentStyle_->drawBelow_); break; // Subsequent layers are drawn after all style-defined layers @@ -1154,10 +1237,12 @@ void MapWidgetImpl::AddLayers() break; } } - else if (it->displayed_[id_]) + // id_ is always < 4, so this is safe + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) + else if (customLayer.displayed_[id_]) { // If the layer is displayed for the current map, add it - AddLayer(it->type_, it->description_, before); + AddLayer(customLayer.type_, customLayer.description_, before); } } } @@ -1175,7 +1260,7 @@ void MapWidgetImpl::AddLayer(types::LayerType type, // If there is a radar product view, create the radar product layer if (radarProductView != nullptr) { - radarProductLayer_ = std::make_shared(context_); + radarProductLayer_ = std::make_shared(glContext_); AddLayer(layerName, radarProductLayer_, before); } } @@ -1184,7 +1269,7 @@ void MapWidgetImpl::AddLayer(types::LayerType type, auto phenomenon = std::get(description); std::shared_ptr alertLayer = - std::make_shared(context_, phenomenon); + std::make_shared(glContext_, phenomenon); AddLayer(fmt::format("alert.{}", awips::GetPhenomenonCode(phenomenon)), alertLayer, before); @@ -1208,7 +1293,7 @@ void MapWidgetImpl::AddLayer(types::LayerType type, { // Create the map overlay layer case types::InformationLayer::MapOverlay: - overlayLayer_ = std::make_shared(context_); + overlayLayer_ = std::make_shared(glContext_); AddLayer(layerName, overlayLayer_, before); break; @@ -1216,20 +1301,31 @@ void MapWidgetImpl::AddLayer(types::LayerType type, case types::InformationLayer::ColorTable: if (radarProductView != nullptr) { - colorTableLayer_ = std::make_shared(context_); + colorTableLayer_ = std::make_shared(glContext_); AddLayer(layerName, colorTableLayer_, before); } break; // Create the radar site layer case types::InformationLayer::RadarSite: - radarSiteLayer_ = std::make_shared(context_); + radarSiteLayer_ = std::make_shared(glContext_); AddLayer(layerName, radarSiteLayer_, before); - connect(radarSiteLayer_.get(), - &RadarSiteLayer::RadarSiteSelected, - this, - [this](const std::string& id) - { widget_->RadarSiteRequested(id); }); + connect( + radarSiteLayer_.get(), + &RadarSiteLayer::RadarSiteSelected, + this, + [this](const std::string& id) + { + auto& generalSettings = settings::GeneralSettings::Instance(); + widget_->RadarSiteRequested( + id, generalSettings.center_on_radar_selection().GetValue()); + }); + break; + + // Create the location marker layer + case types::InformationLayer::Markers: + markerLayer_ = std::make_shared(glContext_); + AddLayer(layerName, markerLayer_, before); break; default: @@ -1245,7 +1341,7 @@ void MapWidgetImpl::AddLayer(types::LayerType type, if (radarProductView != nullptr) { overlayProductLayer_ = - std::make_shared(context_); + std::make_shared(glContext_); AddLayer(layerName, overlayProductLayer_, before); } break; @@ -1275,7 +1371,7 @@ void MapWidgetImpl::AddPlacefileLayer(const std::string& placefileName, const std::string& before) { std::shared_ptr placefileLayer = - std::make_shared(context_, placefileName); + std::make_shared(glContext_, placefileName); placefileLayers_.push_back(placefileLayer); AddLayer(GetPlacefileLayerName(placefileName), placefileLayer, before); @@ -1283,7 +1379,7 @@ void MapWidgetImpl::AddPlacefileLayer(const std::string& placefileName, connect(placefileLayer.get(), &PlacefileLayer::DataReloaded, widget_, - [this]() { widget_->update(); }); + static_cast(&QWidget::update)); } std::string @@ -1292,13 +1388,13 @@ MapWidgetImpl::GetPlacefileLayerName(const std::string& placefileName) return types::GetLayerName(types::LayerType::Placefile, placefileName); } -void MapWidgetImpl::AddLayer(const std::string& id, - std::shared_ptr layer, - const std::string& before) +void MapWidgetImpl::AddLayer(const std::string& id, + const std::shared_ptr& layer, + const std::string& before) { // QMapLibre::addCustomLayer will take ownership of the std::unique_ptr std::unique_ptr pHost = - std::make_unique(layer); + std::make_unique(layer, context_); try { @@ -1310,7 +1406,7 @@ void MapWidgetImpl::AddLayer(const std::string& id, connect(layer.get(), &GenericLayer::NeedsRendering, widget_, - [this]() { widget_->update(); }); + static_cast(&QWidget::update)); } catch (const std::exception&) { @@ -1335,6 +1431,18 @@ bool MapWidget::event(QEvent* e) } pickedEventHandler.reset(); + switch (e->type()) + { + case QEvent::Type::Gesture: + // QEvent is always a QGestureEvent + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + gestureEvent(static_cast(e)); + break; + + default: + break; + } + return QOpenGLWidget::event(e); } @@ -1364,6 +1472,16 @@ void MapWidget::keyReleaseEvent(QKeyEvent* ev) } } +void MapWidget::gestureEvent(QGestureEvent* ev) +{ + if (QGesture* pinch = ev->gesture(Qt::PinchGesture)) + { + // QGesture is always a QPinchGesture + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + p->HandlePinchGesture(static_cast(pinch)); + } +} + void MapWidget::mousePressEvent(QMouseEvent* ev) { p->lastPos_ = ev->position(); @@ -1444,7 +1562,8 @@ void MapWidget::initializeGL() logger_->debug("initializeGL()"); makeCurrent(); - p->context_->Initialize(); + + p->glContext_->Initialize(); // Lock ImGui font atlas prior to new ImGui frame std::shared_lock imguiFontAtlasLock { @@ -1454,8 +1573,6 @@ void MapWidget::initializeGL() ImGui::SetCurrentContext(p->imGuiContext_); ImGui_ImplQt_RegisterWidget(this); ImGui_ImplOpenGL3_Init(); - p->imGuiFontsBuildCount_ = - manager::FontManager::Instance().imgui_fonts_build_count(); p->imGuiRendererInitialized_ = true; p->map_.reset( @@ -1498,36 +1615,37 @@ void MapWidget::paintGL() p->frameDraws_++; - p->context_->StartFrame(); + p->glContext_->StartFrame(); // Handle hotkey updates p->HandleHotkeyUpdates(); - // Setup ImGui Frame - ImGui::SetCurrentContext(p->imGuiContext_); - // Lock ImGui font atlas prior to new ImGui frame std::shared_lock imguiFontAtlasLock { manager::FontManager::Instance().imgui_font_atlas_mutex()}; - // Start ImGui Frame - ImGui_ImplQt_NewFrame(this); - ImGui_ImplOpenGL3_NewFrame(); - p->ImGuiCheckFonts(); - ImGui::NewFrame(); - - // Set default font - ImGui::PushFont(defaultFont->font()); - // Update pixel ratio p->context_->set_pixel_ratio(pixelRatio()); // Render QMapLibre Map p->map_->resize(size()); - p->map_->setFramebufferObject(defaultFramebufferObject(), - size() * pixelRatio()); + p->map_->setOpenGLFramebufferObject(defaultFramebufferObject(), + size() * pixelRatio()); p->map_->render(); + // ImGui tool tip code + // Setup ImGui Frame + ImGui::SetCurrentContext(p->imGuiContext_); + + // Start ImGui Frame + model::ImGuiContextModel::Instance().NewFrame(); + ImGui_ImplQt_NewFrame(this); + ImGui_ImplOpenGL3_NewFrame(); + ImGui::NewFrame(); + + // Set default font + ImGui::PushFont(defaultFont.first->font(), defaultFont.second.value()); + // Perform mouse picking if (p->hasMouse_) { @@ -1557,26 +1675,17 @@ void MapWidget::paintGL() p->isPainting_ = false; } -void MapWidgetImpl::ImGuiCheckFonts() -{ - // Update ImGui Fonts if required - std::uint64_t currentImGuiFontsBuildCount = - manager::FontManager::Instance().imgui_fonts_build_count(); - - if (imGuiFontsBuildCount_ != currentImGuiFontsBuildCount || - !model::ImGuiContextModel::Instance().font_atlas()->IsBuilt()) - { - ImGui_ImplOpenGL3_DestroyFontsTexture(); - ImGui_ImplOpenGL3_CreateFontsTexture(); - } - - imGuiFontsBuildCount_ = currentImGuiFontsBuildCount; -} - void MapWidgetImpl::RunMousePicking() { - const QMapLibre::CustomLayerRenderParameters params = - context_->render_parameters(); + const QMapLibre::CustomLayerRenderParameters params = { + .width = static_cast(widget_->size().width()), + .height = static_cast(widget_->size().height()), + .latitude = map_->coordinate().first, + .longitude = map_->coordinate().second, + .zoom = map_->zoom(), + .bearing = map_->bearing(), + .pitch = map_->pitch(), + .fieldOfView = 0}; auto coordinate = map_->coordinateForPixel(lastPos_); auto mouseScreenCoordinate = @@ -1588,7 +1697,8 @@ void MapWidgetImpl::RunMousePicking() for (auto it = genericLayers_.rbegin(); it != genericLayers_.rend(); ++it) { // Run mouse picking for each layer - if ((*it)->RunMousePicking(params, + if ((*it)->RunMousePicking(context_, + params, lastPos_, lastGlobalPos_, mouseScreenCoordinate, @@ -1681,10 +1791,41 @@ void MapWidgetImpl::RadarProductManagerConnect() { if (radarProductManager_ != nullptr) { + connect(radarProductManager_.get(), + &manager::RadarProductManager::IncomingLevel2ElevationChanged, + this, + [this](std::optional incomingElevation) + { + Q_EMIT widget_->IncomingLevel2ElevationChanged( + incomingElevation); + }); connect(radarProductManager_.get(), &manager::RadarProductManager::Level3ProductsChanged, this, - [this]() { Q_EMIT widget_->Level3ProductsChanged(); }); + [this]() + { + const common::Level3ProductCategoryMap& categoryMap = + widget_->GetAvailableLevel3Categories(); + + tiltsToIndices_.clear(); + for (const auto& category : categoryMap) + { + for (const auto& product : category.second) + { + for (size_t tiltIndex = 0; + tiltIndex < product.second.size(); + tiltIndex++) + { + tiltsToIndices_.emplace(product.second[tiltIndex], + tiltIndex); + } + } + } + + productAvailabilityUpdated_ = true; + CheckLevel3Availability(); + Q_EMIT widget_->Level3ProductsChanged(); + }); connect( radarProductManager_.get(), @@ -1692,6 +1833,7 @@ void MapWidgetImpl::RadarProductManagerConnect() this, [this](common::RadarProductGroup group, const std::string& product, + bool isChunks, std::chrono::system_clock::time_point latestTime) { if (autoRefreshEnabled_ && @@ -1699,62 +1841,81 @@ void MapWidgetImpl::RadarProductManagerConnect() (group == common::RadarProductGroup::Level2 || context_->radar_product() == product)) { - // Create file request - std::shared_ptr request = - std::make_shared( - radarProductManager_->radar_id()); - - // File request callback - if (autoUpdateEnabled_) + if (isChunks && autoUpdateEnabled_) { - connect( - request.get(), - &request::NexradFileRequest::RequestComplete, - this, - [=, - this](std::shared_ptr request) - { - // Select loaded record - auto record = request->radar_product_record(); + // Level 2 products may have multiple time points, + // ensure the latest is selected + widget_->SelectRadarProduct(group, product); + } + else + { + // Create file request + const std::shared_ptr request = + std::make_shared( + radarProductManager_->radar_id()); - // Validate record, and verify current map context - // still displays site and product - if (record != nullptr && - radarProductManager_ != nullptr && - radarProductManager_->radar_id() == - request->current_radar_site() && - context_->radar_product_group() == group && - (group == common::RadarProductGroup::Level2 || - context_->radar_product() == product)) + // File request callback + if (autoUpdateEnabled_) + { + connect( + request.get(), + &request::NexradFileRequest::RequestComplete, + this, + [group, product, this]( + const std::shared_ptr& + request) { - widget_->SelectRadarProduct(record); + // Select loaded record + auto record = request->radar_product_record(); + + // Validate record, and verify current map context + // still displays site and product + if (record != nullptr && + radarProductManager_ != nullptr && + radarProductManager_->radar_id() == + request->current_radar_site() && + context_->radar_product_group() == group && + (group == common::RadarProductGroup::Level2 || + context_->radar_product() == product)) + { + if (group == common::RadarProductGroup::Level2) + { + // Level 2 products may have multiple time + // points, ensure the latest is selected + widget_->SelectRadarProduct(group, product); + } + else + { + widget_->SelectRadarProduct(record); + } + } + }); + } + + // Load file + boost::asio::post( + threadPool_, + [group, latestTime, request, product, this]() + { + try + { + if (group == common::RadarProductGroup::Level2) + { + radarProductManager_->LoadLevel2Data(latestTime, + request); + } + else + { + radarProductManager_->LoadLevel3Data( + product, latestTime, request); + } + } + catch (const std::exception& ex) + { + logger_->error(ex.what()); } }); } - - // Load file - boost::asio::post( - threadPool_, - [=, this]() - { - try - { - if (group == common::RadarProductGroup::Level2) - { - radarProductManager_->LoadLevel2Data(latestTime, - request); - } - else - { - radarProductManager_->LoadLevel3Data( - product, latestTime, request); - } - } - catch (const std::exception& ex) - { - logger_->error(ex.what()); - } - }); } }, Qt::QueuedConnection); @@ -1769,6 +1930,10 @@ void MapWidgetImpl::RadarProductManagerDisconnect() &manager::RadarProductManager::NewDataAvailable, this, nullptr); + disconnect(radarProductManager_.get(), + &manager::RadarProductManager::IncomingLevel2ElevationChanged, + this, + nullptr); } } @@ -1776,27 +1941,12 @@ void MapWidgetImpl::InitializeNewRadarProductView( const std::string& colorPalette) { boost::asio::post(threadPool_, - [=, this]() + [colorPalette, this]() { try { - auto radarProductView = - context_->radar_product_view(); - - std::string colorTableFile = - settings::PaletteSettings::Instance() - .palette(colorPalette) - .GetValue(); - if (!colorTableFile.empty()) - { - std::unique_ptr colorTableStream = - util::OpenFile(colorTableFile); - std::shared_ptr colorTable = - common::ColorTable::Load(*colorTableStream); - radarProductView->LoadColorTable(colorTable); - } - - radarProductView->Initialize(); + UpdateColorTable(colorPalette); + context_->radar_product_view()->Initialize(); } catch (const std::exception& ex) { @@ -1816,12 +1966,11 @@ void MapWidgetImpl::RadarProductViewConnect() if (radarProductView != nullptr) { - connect( - radarProductView.get(), - &view::RadarProductView::ColorTableLutUpdated, - this, - [this]() { widget_->update(); }, - Qt::QueuedConnection); + connect(radarProductView.get(), + &view::RadarProductView::ColorTableLutUpdated, + widget_, + static_cast(&QWidget::update), + Qt::QueuedConnection); connect( radarProductView.get(), &view::RadarProductView::SweepComputed, @@ -1831,10 +1980,14 @@ void MapWidgetImpl::RadarProductViewConnect() std::shared_ptr radarSite = radarProductManager_->radar_site(); - RadarRangeLayer::Update( - map_, - radarProductView->range(), - {radarSite->latitude(), radarSite->longitude()}); + if (map_ != nullptr) + { + RadarRangeLayer::Update( + map_, + radarProductView->range(), + {radarSite->latitude(), radarSite->longitude()}); + } + widget_->update(); Q_EMIT widget_->RadarSweepUpdated(); }, @@ -1854,7 +2007,7 @@ void MapWidgetImpl::RadarProductViewDisconnect() { disconnect(radarProductView.get(), &view::RadarProductView::ColorTableLutUpdated, - this, + widget_, nullptr); disconnect(radarProductView.get(), &view::RadarProductView::SweepComputed, @@ -1879,8 +2032,12 @@ void MapWidgetImpl::SelectNearestRadarSite(double latitude, } } -void MapWidgetImpl::SetRadarSite(const std::string& radarSite) +void MapWidgetImpl::SetRadarSite(const std::string& radarSite, + bool checkProductAvailability) { + // Set the radar site in the context + context_->set_radar_site(config::RadarSite::Get(radarSite)); + // Check if radar site has changed if (radarProductManager_ == nullptr || radarSite != radarProductManager_->radar_site()->id()) @@ -1898,13 +2055,20 @@ void MapWidgetImpl::SetRadarSite(const std::string& radarSite) // Connect signals to new RadarProductManager RadarProductManagerConnect(); + // Once the available products are loaded, check to make sure the current + // one is available + productAvailabilityCheckNeeded_ = checkProductAvailability; + productAvailabilityUpdated_ = false; + productAvailabilityProductSelected_ = false; + radarProductManager_->UpdateAvailableProducts(); } } void MapWidgetImpl::Update() { - widget_->update(); + QMetaObject::invokeMethod( + widget_, static_cast(&QWidget::update)); if (UpdateStoredMapParameters()) { @@ -1913,6 +2077,37 @@ void MapWidgetImpl::Update() } } +void MapWidgetImpl::UpdateColorTable(const std::string& colorPalette) +{ + auto& paletteSetting = + settings::PaletteSettings::Instance().palette(colorPalette); + + std::string colorTableFile = paletteSetting.GetValue(); + if (colorTableFile.empty()) + { + colorTableFile = paletteSetting.GetDefault(); + } + + std::unique_ptr colorTableStream = + util::OpenFile(colorTableFile); + if (colorTableStream->fail()) + { + logger_->warn("Could not open color table {}", colorTableFile); + colorTableStream = util::OpenFile(paletteSetting.GetDefault()); + } + + std::shared_ptr colorTable = + common::ColorTable::Load(*colorTableStream); + if (!colorTable->IsValid()) + { + logger_->warn("Could not load color table {}", colorTableFile); + colorTableStream = util::OpenFile(paletteSetting.GetDefault()); + colorTable = common::ColorTable::Load(*colorTableStream); + } + + context_->radar_product_view()->LoadColorTable(colorTable); +} + bool MapWidgetImpl::UpdateStoredMapParameters() { bool changed = false; @@ -1941,8 +2136,116 @@ bool MapWidgetImpl::UpdateStoredMapParameters() return changed; } -} // namespace map -} // namespace qt -} // namespace scwx +void MapWidgetImpl::CheckLevel3Availability() +{ + /* + * productAvailabilityCheckNeeded_ Only do this when it is indicated that it + * is needed (mostly on radar site change). This is mainly to avoid potential + * recursion with SelectRadarProduct calls. + * + * productAvailabilityUpdated_ Only update once the product availability + * has been updated + * + * productAvailabilityProductSelected_ Only update once the radar site is + * fully selected + */ + if (!(productAvailabilityCheckNeeded_ && productAvailabilityUpdated_ && + productAvailabilityProductSelected_)) + { + return; + } + productAvailabilityCheckNeeded_ = false; + + // Get radar product view for fallback and level2 selection + auto radarProductView = context_->radar_product_view(); + if (radarProductView == nullptr) + { + return; + } + + // Only do this for level3 products + if (widget_->GetRadarProductGroup() != common::RadarProductGroup::Level3) + { + widget_->SelectRadarProduct(radarProductView->GetRadarProductGroup(), + radarProductView->GetRadarProductName(), + 0, + radarProductView->selected_time(), + false); + return; + } + + const common::Level3ProductCategoryMap& categoryMap = + widget_->GetAvailableLevel3Categories(); + + const std::string& productTilt = context_->radar_product(); + const std::string& productName = + common::GetLevel3ProductByAwipsId(productTilt); + const common::Level3ProductCategory productCategory = + common::GetLevel3CategoryByProduct(productName); + if (productCategory == common::Level3ProductCategory::Unknown) + { + // Default to the same as already selected + widget_->SelectRadarProduct(radarProductView->GetRadarProductGroup(), + radarProductView->GetRadarProductName(), + 0, + radarProductView->selected_time(), + false); + return; + } + + const auto& availableProductsIt = categoryMap.find(productCategory); + // Has no products in this category, do not change categories + if (availableProductsIt == categoryMap.cend()) + { + // Default to the same as already selected + widget_->SelectRadarProduct(radarProductView->GetRadarProductGroup(), + radarProductView->GetRadarProductName(), + 0, + radarProductView->selected_time(), + false); + return; + } + + const auto& availableProducts = availableProductsIt->second; + const auto& availableTiltsIt = availableProducts.find(productName); + + const auto& availableTilts = + availableTiltsIt == availableProducts.cend() ? + // Does not have the same product, but has others in the same category. + // Switch to the default product and tilt in this category. + availableProducts.at(common::GetLevel3ProductByAwipsId( + common::GetLevel3CategoryDefaultProduct(productCategory, + categoryMap))) : + // Has the same product + availableTiltsIt->second; + + // Try to match the tilt to the last tilt. + if (currentTiltIndex_ < availableTilts.size()) + { + widget_->SelectRadarProduct(common::RadarProductGroup::Level3, + availableTilts[currentTiltIndex_], + 0, + widget_->GetSelectedTime()); + } + else if (availableTilts.size() > 0) + { + widget_->SelectRadarProduct(common::RadarProductGroup::Level3, + availableTilts[availableTilts.size() - 1], + 0, + widget_->GetSelectedTime()); + } + else + { + // No tilts available in this case, default to the same as already + // selected + widget_->SelectRadarProduct(radarProductView->GetRadarProductGroup(), + radarProductView->GetRadarProductName(), + 0, + radarProductView->selected_time(), + false); + } +} + +} // namespace scwx::qt::map #include "map_widget.moc" diff --git a/scwx-qt/source/scwx/qt/map/map_widget.hpp b/scwx-qt/source/scwx/qt/map/map_widget.hpp index 40f7df77..f721044e 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.hpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.hpp @@ -3,15 +3,18 @@ #include #include #include +#include #include #include #include #include #include +#include #include +#include #include #include #include @@ -20,11 +23,12 @@ class QKeyEvent; class QMouseEvent; class QWheelEvent; -namespace scwx +namespace scwx::qt::gl { -namespace qt -{ -namespace map +class GlContext; +} + +namespace scwx::qt::map { class MapWidgetImpl; @@ -34,21 +38,28 @@ class MapWidget : public QOpenGLWidget Q_OBJECT public: - explicit MapWidget(std::size_t id, const QMapLibre::Settings&); + explicit MapWidget(std::size_t id, + const QMapLibre::Settings&, + std::shared_ptr glContext); ~MapWidget(); void DumpLayerList() const; - common::Level3ProductCategoryMap GetAvailableLevel3Categories(); - float GetElevation() const; - std::vector GetElevationCuts() const; - std::vector GetLevel3Products(); - std::string GetMapStyle() const; - common::RadarProductGroup GetRadarProductGroup() const; - std::string GetRadarProductName() const; - std::shared_ptr GetRadarSite() const; - std::chrono::system_clock::time_point GetSelectedTime() const; - std::uint16_t GetVcp() const; + [[nodiscard]] common::Level3ProductCategoryMap + GetAvailableLevel3Categories(); + [[nodiscard]] const scwx::util::time_zone* GetDefaultTimeZone() const; + [[nodiscard]] std::optional GetElevation() const; + [[nodiscard]] std::vector GetElevationCuts() const; + [[nodiscard]] std::optional GetIncomingLevel2Elevation() const; + [[nodiscard]] std::vector GetLevel3Products(); + [[nodiscard]] std::string GetMapStyle() const; + [[nodiscard]] common::RadarProductGroup GetRadarProductGroup() const; + [[nodiscard]] std::string GetRadarProductName() const; + [[nodiscard]] std::shared_ptr GetRadarSite() const; + [[nodiscard]] bool GetRadarWireframeEnabled() const; + [[nodiscard]] std::chrono::system_clock::time_point GetSelectedTime() const; + [[nodiscard]] bool GetSmoothingEnabled() const; + [[nodiscard]] std::uint16_t GetVcp() const; void SelectElevation(float elevation); @@ -117,6 +128,8 @@ public: double pitch); void SetInitialMapStyle(const std::string& styleName); void SetMapStyle(const std::string& styleName); + void SetRadarWireframeEnabled(bool enabled); + void SetSmoothingEnabled(bool enabled); /** * Updates the coordinates associated with mouse movement from another map. @@ -131,13 +144,14 @@ private: // QWidget implementation. bool event(QEvent* e) override; - void enterEvent(QEnterEvent* ev) override final; - void keyPressEvent(QKeyEvent* ev) override final; - void keyReleaseEvent(QKeyEvent* ev) override final; - void leaveEvent(QEvent* ev) override final; - void mousePressEvent(QMouseEvent* ev) override final; - void mouseMoveEvent(QMouseEvent* ev) override final; - void wheelEvent(QWheelEvent* ev) override final; + void enterEvent(QEnterEvent* ev) final; + void keyPressEvent(QKeyEvent* ev) final; + void keyReleaseEvent(QKeyEvent* ev) final; + void gestureEvent(QGestureEvent* ev); + void leaveEvent(QEvent* ev) final; + void mousePressEvent(QMouseEvent* ev) final; + void mouseMoveEvent(QMouseEvent* ev) final; + void wheelEvent(QWheelEvent* ev) final; // QOpenGLWidget implementation. void initializeGL() override final; @@ -176,8 +190,7 @@ signals: void RadarSweepUpdated(); void RadarSweepNotUpdated(types::NoUpdateReason reason); void WidgetPainted(); + void IncomingLevel2ElevationChanged(std::optional incomingElevation); }; -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/marker_layer.cpp b/scwx-qt/source/scwx/qt/map/marker_layer.cpp new file mode 100644 index 00000000..3e49c1d1 --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/marker_layer.cpp @@ -0,0 +1,180 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace scwx::qt::map +{ + +static const std::string logPrefix_ = "scwx::qt::map::marker_layer"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class MarkerLayer::Impl +{ +public: + explicit Impl(MarkerLayer* self, + const std::shared_ptr& glContext) : + self_ {self}, + geoIcons_ {std::make_shared(glContext)}, + editMarkerDialog_ {std::make_shared()} + { + ConnectSignals(); + } + ~Impl() = default; + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + + void ReloadMarkers(); + void ConnectSignals(); + + std::shared_ptr markerManager_ { + manager::MarkerManager::Instance()}; + + void set_icon_sheets(); + + MarkerLayer* self_; + + std::shared_ptr geoIcons_; + std::shared_ptr editMarkerDialog_; +}; + +void MarkerLayer::Impl::ConnectSignals() +{ + QObject::connect(markerManager_.get(), + &manager::MarkerManager::MarkersUpdated, + self_, + [this]() { ReloadMarkers(); }); + QObject::connect(markerManager_.get(), + &manager::MarkerManager::IconsReady, + self_, + [this]() { set_icon_sheets(); }); + QObject::connect(markerManager_.get(), + &manager::MarkerManager::IconAdded, + self_, + [this]() { set_icon_sheets(); }); +} + +void MarkerLayer::Impl::ReloadMarkers() +{ + logger_->debug("ReloadMarkers()"); + + geoIcons_->StartIcons(); + markerManager_->for_each( + [this](const types::MarkerInfo& marker) + { + // must use local ID, instead of reference to marker in event handler + // callback. + const types::MarkerId id = marker.id; + + const std::shared_ptr icon = + geoIcons_->AddIcon(); + + const std::string latitudeString = + common::GetLatitudeString(marker.latitude); + const std::string longitudeString = + common::GetLongitudeString(marker.longitude); + + const std::string hoverText = + marker.name != "" ? + fmt::format( + "{}\n{}, {}", marker.name, latitudeString, longitudeString) : + fmt::format("{}, {}", latitudeString, longitudeString); + + auto iconInfo = markerManager_->get_icon(marker.iconName); + if (iconInfo) + { + geoIcons_->SetIconTexture(icon, iconInfo->name, 0); + } + else + { + geoIcons_->SetIconTexture(icon, marker.iconName, 0); + } + + geoIcons_->SetIconLocation(icon, marker.latitude, marker.longitude); + geoIcons_->SetIconHoverText(icon, hoverText); + geoIcons_->SetIconModulate(icon, marker.iconColor); + geoIcons_->RegisterEventHandler( + icon, + [this, id](QEvent* ev) + { + switch (ev->type()) + { + case QEvent::Type::MouseButtonPress: + { + auto* mouseEvent = reinterpret_cast(ev); + if (mouseEvent->buttons() == Qt::MouseButton::RightButton) + { + editMarkerDialog_->setup(id); + editMarkerDialog_->show(); + } + } + break; + + default: + break; + } + }); + }); + + geoIcons_->FinishIcons(); + Q_EMIT self_->NeedsRendering(); +} + +MarkerLayer::MarkerLayer(const std::shared_ptr& glContext) : + DrawLayer(glContext, "MarkerLayer"), + p(std::make_unique(this, glContext)) +{ + AddDrawItem(p->geoIcons_); +} + +MarkerLayer::~MarkerLayer() = default; + +void MarkerLayer::Initialize(const std::shared_ptr& mapContext) +{ + logger_->debug("Initialize()"); + DrawLayer::Initialize(mapContext); + + p->set_icon_sheets(); + p->ReloadMarkers(); +} + +void MarkerLayer::Impl::set_icon_sheets() +{ + geoIcons_->StartIconSheets(); + for (auto& markerIcon : markerManager_->get_icons()) + { + geoIcons_->AddIconSheet(markerIcon.second.name, + 0, + 0, + markerIcon.second.hotX, + markerIcon.second.hotY); + } + geoIcons_->FinishIconSheets(); +} + +void MarkerLayer::Render(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters& params) +{ + DrawLayer::Render(mapContext, params); + + SCWX_GL_CHECK_ERROR(); +} + +void MarkerLayer::Deinitialize() +{ + logger_->debug("Deinitialize()"); + + DrawLayer::Deinitialize(); +} + +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/marker_layer.hpp b/scwx-qt/source/scwx/qt/map/marker_layer.hpp new file mode 100644 index 00000000..a5f67d2b --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/marker_layer.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +namespace scwx::qt::map +{ + +class MarkerLayer : public DrawLayer +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(MarkerLayer) + +public: + explicit MarkerLayer(const std::shared_ptr& context); + ~MarkerLayer(); + + void Initialize(const std::shared_ptr& mapContext) final; + void Render(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters&) final; + void Deinitialize() final; + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp index b2fada95..04b0890e 100644 --- a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp @@ -1,18 +1,18 @@ -#include +#include #include #include #include #include #include +#include #include +#include #include #include #include #include #include -#include - #if defined(_MSC_VER) # pragma warning(push, 0) #endif @@ -22,34 +22,27 @@ #include #include -#if !defined(_MSC_VER) -# include -#endif - #if defined(_MSC_VER) # pragma warning(pop) #endif -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { static const std::string logPrefix_ = "scwx::qt::map::overlay_layer"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -class OverlayLayerImpl +class OverlayLayer::Impl { public: - explicit OverlayLayerImpl(OverlayLayer* self, - std::shared_ptr context) : + explicit Impl(OverlayLayer* self, + const std::shared_ptr& glContext) : self_ {self}, - activeBoxOuter_ {std::make_shared(context)}, - activeBoxInner_ {std::make_shared(context)}, - geoIcons_ {std::make_shared(context)}, - icons_ {std::make_shared(context)} + activeBoxOuter_ {std::make_shared(glContext)}, + activeBoxInner_ {std::make_shared(glContext)}, + geoIcons_ {std::make_shared(glContext)}, + icons_ {std::make_shared(glContext)}, + renderMutex_ {} { auto& generalSettings = settings::GeneralSettings::Instance(); @@ -78,7 +71,7 @@ public: [this](const bool&) { Q_EMIT self_->NeedsRendering(); }); } - ~OverlayLayerImpl() + ~Impl() { auto& generalSettings = settings::GeneralSettings::Instance(); @@ -94,6 +87,14 @@ public: showMapLogoCallbackUuid_); } + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + + void SetupGeoIcons(); + void SetCusorLocation(common::Coordinate coordinate); + OverlayLayer* self_; boost::uuids::uuid clockFormatCallbackUuid_; @@ -121,11 +122,13 @@ public: types::GetTextureName(types::ImageTexture::CardinalPoint24)}; const std::string& compassIconName_ { types::GetTextureName(types::ImageTexture::Compass24)}; - const std::string& cursorIconName_ { + std::string cursorIconName_ { types::GetTextureName(types::ImageTexture::Dot3)}; const std::string& mapCenterIconName_ { types::GetTextureName(types::ImageTexture::Cursor17)}; + std::shared_ptr cursorIconImage_ {nullptr}; + const std::string& mapboxLogoImageName_ { types::GetTextureName(types::ImageTexture::MapboxLogo)}; const std::string& mapTilerLogoImageName_ { @@ -143,13 +146,19 @@ public: float lastFontSize_ {0.0f}; QMargins lastColorTableMargins_ {}; + double cursorScale_ {1}; + boost::signals2::scoped_connection cursorScaleConnection_; + + std::mutex renderMutex_; + std::string sweepTimeString_ {}; bool sweepTimeNeedsUpdate_ {true}; bool sweepTimePicked_ {false}; }; -OverlayLayer::OverlayLayer(std::shared_ptr context) : - DrawLayer(context), p(std::make_unique(this, context)) +OverlayLayer::OverlayLayer(const std::shared_ptr& glContext) : + DrawLayer(glContext, "OverlayLayer"), + p(std::make_unique(this, glContext)) { AddDrawItem(p->activeBoxOuter_); AddDrawItem(p->activeBoxInner_); @@ -159,15 +168,58 @@ OverlayLayer::OverlayLayer(std::shared_ptr context) : p->activeBoxOuter_->SetPosition(0.0f, 0.0f); } -OverlayLayer::~OverlayLayer() = default; +OverlayLayer::~OverlayLayer() +{ + p->cursorScaleConnection_.disconnect(); +} -void OverlayLayer::Initialize() +void OverlayLayer::Impl::SetCusorLocation(common::Coordinate coordinate) +{ + geoIcons_->SetIconLocation( + cursorIcon_, coordinate.latitude_, coordinate.longitude_); +} + +void OverlayLayer::Impl::SetupGeoIcons() +{ + const std::unique_lock lock {renderMutex_}; + + auto& generalSettings = settings::GeneralSettings::Instance(); + cursorScale_ = generalSettings.cursor_icon_scale().GetValue(); + + const std::string& texturePath = + types::GetTexturePath(types::ImageTexture::Dot3); + cursorIconName_ = fmt::format( + "{}x{}", types::GetTextureName(types::ImageTexture::Dot3), cursorScale_); + cursorIconImage_ = manager::ResourceManager::LoadImageResource( + texturePath, cursorIconName_, cursorScale_); + manager::ResourceManager::BuildAtlas(); + + auto coordinate = currentPosition_.coordinate(); + geoIcons_->StartIconSheets(); + geoIcons_->AddIconSheet(cursorIconName_); + geoIcons_->AddIconSheet(locationIconName_); + geoIcons_->FinishIconSheets(); + + geoIcons_->StartIcons(); + + cursorIcon_ = geoIcons_->AddIcon(); + geoIcons_->SetIconTexture(cursorIcon_, cursorIconName_, 0); + + locationIcon_ = geoIcons_->AddIcon(); + geoIcons_->SetIconTexture(locationIcon_, locationIconName_, 0); + geoIcons_->SetIconLocation( + locationIcon_, coordinate.latitude(), coordinate.longitude()); + + geoIcons_->FinishIcons(); +} + +void OverlayLayer::Initialize(const std::shared_ptr& mapContext) { logger_->debug("Initialize()"); - DrawLayer::Initialize(); + DrawLayer::Initialize(mapContext); - auto radarProductView = context()->radar_product_view(); + auto radarProductView = mapContext->radar_product_view(); if (radarProductView != nullptr) { @@ -178,27 +230,17 @@ void OverlayLayer::Initialize() } p->currentPosition_ = p->positionManager_->position(); - auto coordinate = p->currentPosition_.coordinate(); // Geo Icons - p->geoIcons_->StartIconSheets(); - p->geoIcons_->AddIconSheet(p->cursorIconName_); - p->geoIcons_->AddIconSheet(p->locationIconName_); - p->geoIcons_->FinishIconSheets(); - - p->geoIcons_->StartIcons(); - - p->cursorIcon_ = p->geoIcons_->AddIcon(); - p->geoIcons_->SetIconTexture(p->cursorIcon_, p->cursorIconName_, 0); - - p->locationIcon_ = p->geoIcons_->AddIcon(); - p->geoIcons_->SetIconTexture(p->locationIcon_, p->locationIconName_, 0); - p->geoIcons_->SetIconAngle(p->locationIcon_, - units::angle::degrees {45.0}); - p->geoIcons_->SetIconLocation( - p->locationIcon_, coordinate.latitude(), coordinate.longitude()); - - p->geoIcons_->FinishIcons(); + auto& generalSettings = settings::GeneralSettings::Instance(); + p->SetupGeoIcons(); + p->cursorScaleConnection_ = + generalSettings.cursor_icon_scale().changed_signal().connect( + [this]() + { + p->SetupGeoIcons(); + Q_EMIT NeedsRendering(); + }); // Icons p->icons_->StartIconSheets(); @@ -214,7 +256,7 @@ void OverlayLayer::Initialize() p->icons_->SetIconTexture(p->compassIcon_, p->cardinalPointIconName_, 0); gl::draw::Icons::RegisterEventHandler( p->compassIcon_, - [this](QEvent* ev) + [this, mapContext](QEvent* ev) { switch (ev->type()) { @@ -239,7 +281,7 @@ void OverlayLayer::Initialize() if (mouseEvent->buttons() == Qt::MouseButton::LeftButton && p->lastBearing_ != 0.0) { - auto map = context()->map().lock(); + auto map = mapContext->map().lock(); if (map != nullptr) { map->setBearing(0.0); @@ -258,11 +300,11 @@ void OverlayLayer::Initialize() p->icons_->SetIconTexture(p->mapCenterIcon_, p->mapCenterIconName_, 0); p->mapLogoIcon_ = p->icons_->AddIcon(); - if (context()->map_provider() == MapProvider::Mapbox) + if (mapContext->map_provider() == MapProvider::Mapbox) { p->icons_->SetIconTexture(p->mapLogoIcon_, p->mapboxLogoImageName_, 0); } - else if (context()->map_provider() == MapProvider::MapTiler) + else if (mapContext->map_provider() == MapProvider::MapTiler) { p->icons_->SetIconTexture(p->mapLogoIcon_, p->mapTilerLogoImageName_, 0); } @@ -291,14 +333,16 @@ void OverlayLayer::Initialize() }); } -void OverlayLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) +void OverlayLayer::Render(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters& params) { - gl::OpenGLFunctions& gl = context()->gl(); - auto radarProductView = context()->radar_product_view(); - auto& settings = context()->settings(); - const float pixelRatio = context()->pixel_ratio(); + const std::unique_lock lock {p->renderMutex_}; - context()->set_render_parameters(params); + auto radarProductView = mapContext->radar_product_view(); + auto& settings = mapContext->settings(); + const float pixelRatio = mapContext->pixel_ratio(); + + ImGuiFrameStart(mapContext); p->sweepTimePicked_ = false; @@ -334,15 +378,17 @@ void OverlayLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) p->activeBoxInner_->SetBorder(1.0f * pixelRatio, {255, 255, 255, 255}); } + auto& generalSettings = settings::GeneralSettings::Instance(); + // Cursor Icon - bool cursorIconVisible = QGuiApplication::keyboardModifiers() & - Qt::KeyboardModifier::ControlModifier; + bool cursorIconVisible = + generalSettings.cursor_icon_always_on().GetValue() || + (QGuiApplication::keyboardModifiers() & + Qt::KeyboardModifier::ControlModifier); p->geoIcons_->SetIconVisible(p->cursorIcon_, cursorIconVisible); if (cursorIconVisible) { - common::Coordinate mouseCoordinate = context()->mouse_coordinate(); - p->geoIcons_->SetIconLocation( - p->cursorIcon_, mouseCoordinate.latitude_, mouseCoordinate.longitude_); + p->SetCusorLocation(mapContext->mouse_coordinate()); } // Location Icon @@ -382,7 +428,9 @@ void OverlayLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) if (radarProductView != nullptr) { // Render product name - std::string productName = radarProductView->GetRadarProductName(); + const std::string productName = radarProductView->GetRadarProductName(); + const std::optional elevation = radarProductView->elevation(); + if (productName.length() > 0 && !productName.starts_with('?')) { ImGui::SetNextWindowPos(ImVec2 {0.0f, 0.0f}); @@ -390,7 +438,21 @@ void OverlayLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize); - ImGui::TextUnformatted(productName.c_str()); + + if (elevation.has_value()) + { + const std::string elevationString = + (QString::number(*elevation, 'f', 1) + + common::Characters::DEGREE) + .toStdString(); + ImGui::TextUnformatted( + fmt::format("{} ({})", productName, elevationString).c_str()); + } + else + { + ImGui::TextUnformatted(productName.c_str()); + } + ImGui::End(); } } @@ -440,8 +502,6 @@ void OverlayLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) ImGui::End(); } - auto& generalSettings = settings::GeneralSettings::Instance(); - // Map Center Icon if (params.width != p->lastWidth_ || params.height != p->lastHeight_) { @@ -452,7 +512,7 @@ void OverlayLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) p->icons_->SetIconVisible(p->mapCenterIcon_, generalSettings.show_map_center().GetValue()); - QMargins colorTableMargins = context()->color_table_margins(); + const QMargins colorTableMargins = mapContext->color_table_margins(); if (colorTableMargins != p->lastColorTableMargins_ || p->firstRender_) { // Draw map logo with a 10x10 indent from the bottom left @@ -463,9 +523,9 @@ void OverlayLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) p->icons_->SetIconVisible(p->mapLogoIcon_, generalSettings.show_map_logo().GetValue()); - DrawLayer::Render(params); + DrawLayer::RenderWithoutImGui(params); - auto mapCopyrights = context()->map_copyrights(); + auto mapCopyrights = mapContext->map_copyrights(); if (mapCopyrights.length() > 0 && generalSettings.show_map_attribution().GetValue()) { @@ -479,7 +539,8 @@ void OverlayLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) ImVec2 {1.0f, 1.0f}); ImGui::SetNextWindowBgAlpha(0.5f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2 {3.0f, 2.0f}); - ImGui::PushFont(attributionFont->font()); + ImGui::PushFont(attributionFont.first->font(), + attributionFont.second.value()); ImGui::Begin("Attribution", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | @@ -497,6 +558,8 @@ void OverlayLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) p->lastFontSize_ = ImGui::GetFontSize(); p->lastColorTableMargins_ = colorTableMargins; + ImGuiFrameEnd(); + SCWX_GL_CHECK_ERROR(); } @@ -506,29 +569,13 @@ void OverlayLayer::Deinitialize() DrawLayer::Deinitialize(); - auto radarProductView = context()->radar_product_view(); - - if (radarProductView != nullptr) - { - disconnect(radarProductView.get(), - &view::RadarProductView::SweepComputed, - this, - &OverlayLayer::UpdateSweepTimeNextFrame); - } - - disconnect(p->positionManager_.get(), - &manager::PositionManager::LocationTrackingChanged, - this, - nullptr); - disconnect(p->positionManager_.get(), - &manager::PositionManager::PositionUpdated, - this, - nullptr); + disconnect(this); p->locationIcon_ = nullptr; } bool OverlayLayer::RunMousePicking( + const std::shared_ptr& mapContext, const QMapLibre::CustomLayerRenderParameters& params, const QPointF& mouseLocalPos, const QPointF& mouseGlobalPos, @@ -542,7 +589,8 @@ bool OverlayLayer::RunMousePicking( return true; } - return DrawLayer::RunMousePicking(params, + return DrawLayer::RunMousePicking(mapContext, + params, mouseLocalPos, mouseGlobalPos, mouseCoords, @@ -555,6 +603,4 @@ void OverlayLayer::UpdateSweepTimeNextFrame() p->sweepTimeNeedsUpdate_ = true; } -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/overlay_layer.hpp b/scwx-qt/source/scwx/qt/map/overlay_layer.hpp index f842e81b..89e49b6b 100644 --- a/scwx-qt/source/scwx/qt/map/overlay_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/overlay_layer.hpp @@ -2,42 +2,37 @@ #include -namespace scwx +namespace scwx::qt::map { -namespace qt -{ -namespace map -{ - -class OverlayLayerImpl; class OverlayLayer : public DrawLayer { Q_DISABLE_COPY_MOVE(OverlayLayer) public: - explicit OverlayLayer(std::shared_ptr context); + explicit OverlayLayer(const std::shared_ptr& glContext); ~OverlayLayer(); - void Initialize() override final; - void Render(const QMapLibre::CustomLayerRenderParameters&) override final; - void Deinitialize() override final; + void Initialize(const std::shared_ptr& mapContext) final; + void Render(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters&) final; + void Deinitialize() final; - bool RunMousePicking( - const QMapLibre::CustomLayerRenderParameters& params, - const QPointF& mouseLocalPos, - const QPointF& mouseGlobalPos, - const glm::vec2& mouseCoords, - const common::Coordinate& mouseGeoCoords, - std::shared_ptr& eventHandler) override final; + bool + RunMousePicking(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters& params, + const QPointF& mouseLocalPos, + const QPointF& mouseGlobalPos, + const glm::vec2& mouseCoords, + const common::Coordinate& mouseGeoCoords, + std::shared_ptr& eventHandler) final; public slots: void UpdateSweepTimeNextFrame(); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/overlay_product_layer.cpp b/scwx-qt/source/scwx/qt/map/overlay_product_layer.cpp index 64745fd5..457ebccd 100644 --- a/scwx-qt/source/scwx/qt/map/overlay_product_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/overlay_product_layer.cpp @@ -11,11 +11,7 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { static const std::string logPrefix_ = "scwx::qt::map::overlay_product_layer"; @@ -24,10 +20,10 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class OverlayProductLayer::Impl { public: - explicit Impl(OverlayProductLayer* self, - const std::shared_ptr& context) : + explicit Impl(OverlayProductLayer* self, + const std::shared_ptr& glContext) : self_ {self}, - linkedVectors_ {std::make_shared(context)} + linkedVectors_ {std::make_shared(glContext)} { auto& productSettings = settings::ProductSettings::Instance(); @@ -64,7 +60,8 @@ public: stiPastEnabledCallbackUuid_); } - void UpdateStormTrackingInformation(); + void UpdateStormTrackingInformation( + const std::shared_ptr& mapContext); static void HandleLinkedVectorPacket( const std::shared_ptr& packet, @@ -108,10 +105,22 @@ public: std::shared_ptr linkedVectors_; }; -OverlayProductLayer::OverlayProductLayer(std::shared_ptr context) : - DrawLayer(context), p(std::make_unique(this, context)) +OverlayProductLayer::OverlayProductLayer( + const std::shared_ptr& glContext) : + DrawLayer(glContext, "OverlayProductLayer"), + p(std::make_unique(this, glContext)) { - auto overlayProductView = context->overlay_product_view(); + AddDrawItem(p->linkedVectors_); +} + +OverlayProductLayer::~OverlayProductLayer() = default; + +void OverlayProductLayer::Initialize( + const std::shared_ptr& mapContext) +{ + logger_->debug("Initialize()"); + + auto overlayProductView = mapContext->overlay_product_view(); connect(overlayProductView.get(), &view::OverlayProductView::ProductUpdated, this, @@ -124,31 +133,21 @@ OverlayProductLayer::OverlayProductLayer(std::shared_ptr context) : } }); - AddDrawItem(p->linkedVectors_); -} + p->UpdateStormTrackingInformation(mapContext); -OverlayProductLayer::~OverlayProductLayer() = default; - -void OverlayProductLayer::Initialize() -{ - logger_->debug("Initialize()"); - - p->UpdateStormTrackingInformation(); - - DrawLayer::Initialize(); + DrawLayer::Initialize(mapContext); } void OverlayProductLayer::Render( + const std::shared_ptr& mapContext, const QMapLibre::CustomLayerRenderParameters& params) { - gl::OpenGLFunctions& gl = context()->gl(); - if (p->stiNeedsUpdate_) { - p->UpdateStormTrackingInformation(); + p->UpdateStormTrackingInformation(mapContext); } - DrawLayer::Render(params); + DrawLayer::Render(mapContext, params); SCWX_GL_CHECK_ERROR(); } @@ -157,16 +156,19 @@ void OverlayProductLayer::Deinitialize() { logger_->debug("Deinitialize()"); + disconnect(this); + DrawLayer::Deinitialize(); } -void OverlayProductLayer::Impl::UpdateStormTrackingInformation() +void OverlayProductLayer::Impl::UpdateStormTrackingInformation( + const std::shared_ptr& mapContext) { logger_->debug("Update Storm Tracking Information"); stiNeedsUpdate_ = false; - auto overlayProductView = self_->context()->overlay_product_view(); + auto overlayProductView = mapContext->overlay_product_view(); auto radarProductManager = overlayProductView->radar_product_manager(); auto message = overlayProductView->radar_product_message("NST"); @@ -433,6 +435,7 @@ std::string OverlayProductLayer::Impl::BuildHoverText( } bool OverlayProductLayer::RunMousePicking( + const std::shared_ptr& mapContext, const QMapLibre::CustomLayerRenderParameters& params, const QPointF& mouseLocalPos, const QPointF& mouseGlobalPos, @@ -440,7 +443,8 @@ bool OverlayProductLayer::RunMousePicking( const common::Coordinate& mouseGeoCoords, std::shared_ptr& eventHandler) { - return DrawLayer::RunMousePicking(params, + return DrawLayer::RunMousePicking(mapContext, + params, mouseLocalPos, mouseGlobalPos, mouseCoords, @@ -448,6 +452,4 @@ bool OverlayProductLayer::RunMousePicking( eventHandler); } -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/overlay_product_layer.hpp b/scwx-qt/source/scwx/qt/map/overlay_product_layer.hpp index 8f65c2d6..5c2fd73e 100644 --- a/scwx-qt/source/scwx/qt/map/overlay_product_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/overlay_product_layer.hpp @@ -2,36 +2,35 @@ #include -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { class OverlayProductLayer : public DrawLayer { + Q_DISABLE_COPY_MOVE(OverlayProductLayer) + public: - explicit OverlayProductLayer(std::shared_ptr context); + explicit OverlayProductLayer( + const std::shared_ptr& glContext); ~OverlayProductLayer(); - void Initialize() override final; - void Render(const QMapLibre::CustomLayerRenderParameters&) override final; - void Deinitialize() override final; + void Initialize(const std::shared_ptr& mapContext) final; + void Render(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters&) final; + void Deinitialize() final; - bool RunMousePicking( - const QMapLibre::CustomLayerRenderParameters& params, - const QPointF& mouseLocalPos, - const QPointF& mouseGlobalPos, - const glm::vec2& mouseCoords, - const common::Coordinate& mouseGeoCoords, - std::shared_ptr& eventHandler) override final; + bool + RunMousePicking(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters& params, + const QPointF& mouseLocalPos, + const QPointF& mouseGlobalPos, + const glm::vec2& mouseCoords, + const common::Coordinate& mouseGeoCoords, + std::shared_ptr& eventHandler) final; private: class Impl; std::unique_ptr p; }; -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index be2d9a18..a5bcdc58 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -12,11 +12,7 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { static const std::string logPrefix_ = "scwx::qt::map::placefile_layer"; @@ -25,25 +21,30 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class PlacefileLayer::Impl { public: - explicit Impl(PlacefileLayer* self, - const std::shared_ptr& context, - const std::string& placefileName) : + explicit Impl(PlacefileLayer* self, + const std::shared_ptr& glContext, + const std::string& placefileName) : self_ {self}, placefileName_ {placefileName}, - placefileIcons_ {std::make_shared(context)}, - placefileImages_ {std::make_shared(context)}, - placefileLines_ {std::make_shared(context)}, + placefileIcons_ {std::make_shared(glContext)}, + placefileImages_ { + std::make_shared(glContext)}, + placefileLines_ {std::make_shared(glContext)}, placefilePolygons_ { - std::make_shared(context)}, + std::make_shared(glContext)}, placefileTriangles_ { - std::make_shared(context)}, - placefileText_ { - std::make_shared(context, placefileName)} + std::make_shared(glContext)}, + placefileText_ {std::make_shared(placefileName)} { ConnectSignals(); } ~Impl() { threadPool_.join(); } + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + void ConnectSignals(); void ReloadDataSync(); @@ -64,10 +65,10 @@ public: std::chrono::system_clock::time_point selectedTime_ {}; }; -PlacefileLayer::PlacefileLayer(const std::shared_ptr& context, +PlacefileLayer::PlacefileLayer(const std::shared_ptr& glContext, const std::string& placefileName) : - DrawLayer(context), - p(std::make_unique(this, context, placefileName)) + DrawLayer(glContext, fmt::format("PlacefileLayer {}", placefileName)), + p(std::make_unique(this, glContext, placefileName)) { AddDrawItem(p->placefileImages_); AddDrawItem(p->placefilePolygons_); @@ -117,18 +118,19 @@ void PlacefileLayer::set_placefile_name(const std::string& placefileName) ReloadData(); } -void PlacefileLayer::Initialize() +void PlacefileLayer::Initialize(const std::shared_ptr& mapContext) { logger_->debug("Initialize()"); - DrawLayer::Initialize(); + DrawLayer::Initialize(mapContext); + + p->selectedTime_ = manager::TimelineManager::Instance()->GetSelectedTime(); } void PlacefileLayer::Render( + const std::shared_ptr& mapContext, const QMapLibre::CustomLayerRenderParameters& params) { - gl::OpenGLFunctions& gl = context()->gl(); - std::shared_ptr placefileManager = manager::PlacefileManager::Instance(); @@ -154,7 +156,7 @@ void PlacefileLayer::Render( p->placefileText_->set_selected_time(p->selectedTime_); } - DrawLayer::Render(params); + DrawLayer::Render(mapContext, params); SCWX_GL_CHECK_ERROR(); } @@ -259,6 +261,4 @@ void PlacefileLayer::Impl::ReloadDataSync() Q_EMIT self_->DataReloaded(); } -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.hpp b/scwx-qt/source/scwx/qt/map/placefile_layer.hpp index 981c3c12..35f5a81b 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.hpp @@ -4,29 +4,27 @@ #include -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { class PlacefileLayer : public DrawLayer { Q_OBJECT + Q_DISABLE_COPY_MOVE(PlacefileLayer) public: - explicit PlacefileLayer(const std::shared_ptr& context, - const std::string& placefileName); + explicit PlacefileLayer(const std::shared_ptr& glContext, + const std::string& placefileName); ~PlacefileLayer(); std::string placefile_name() const; void set_placefile_name(const std::string& placefileName); - void Initialize() override final; - void Render(const QMapLibre::CustomLayerRenderParameters&) override final; - void Deinitialize() override final; + void Initialize(const std::shared_ptr& mapContext) final; + void Render(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters&) final; + void Deinitialize() final; void ReloadData(); @@ -38,6 +36,4 @@ private: std::unique_ptr p; }; -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp b/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp index 79d03ce6..22b43a87 100644 --- a/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp @@ -1,12 +1,14 @@ #include +#include #include +#include +#include +#include #include #include #include #include -#include - #if defined(_MSC_VER) # pragma warning(push, 0) #endif @@ -24,63 +26,118 @@ # pragma warning(pop) #endif -namespace scwx +namespace scwx::qt::map { -namespace qt -{ -namespace map -{ - -static constexpr uint32_t MAX_RADIALS = 720; -static constexpr uint32_t MAX_DATA_MOMENT_GATES = 1840; static const std::string logPrefix_ = "scwx::qt::map::radar_product_layer"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -class RadarProductLayerImpl +class RadarProductLayer::Impl { public: - explicit RadarProductLayerImpl() : - shaderProgram_(nullptr), - uMVPMatrixLocation_(GL_INVALID_INDEX), - uMapScreenCoordLocation_(GL_INVALID_INDEX), - uDataMomentOffsetLocation_(GL_INVALID_INDEX), - uDataMomentScaleLocation_(GL_INVALID_INDEX), - uCFPEnabledLocation_(GL_INVALID_INDEX), - vbo_ {GL_INVALID_INDEX}, - vao_ {GL_INVALID_INDEX}, - texture_ {GL_INVALID_INDEX}, - numVertices_ {0}, - cfpEnabled_ {false}, - colorTableNeedsUpdate_ {false}, - sweepNeedsUpdate_ {false} - { - } - ~RadarProductLayerImpl() = default; + explicit Impl() = default; + ~Impl() = default; - std::shared_ptr shaderProgram_; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; - GLint uMVPMatrixLocation_; - GLint uMapScreenCoordLocation_; - GLint uDataMomentOffsetLocation_; - GLint uDataMomentScaleLocation_; - GLint uCFPEnabledLocation_; - std::array vbo_; - GLuint vao_; - GLuint texture_; + std::shared_ptr shaderProgram_ {nullptr}; - GLsizeiptr numVertices_; + GLint uMVPMatrixLocation_ {static_cast(GL_INVALID_INDEX)}; + GLint uMapScreenCoordLocation_ {static_cast(GL_INVALID_INDEX)}; + GLint uDataMomentOffsetLocation_ {static_cast(GL_INVALID_INDEX)}; + GLint uDataMomentScaleLocation_ {static_cast(GL_INVALID_INDEX)}; + GLint uCFPEnabledLocation_ {static_cast(GL_INVALID_INDEX)}; + std::array vbo_ {GL_INVALID_INDEX}; + GLuint vao_ {GL_INVALID_INDEX}; + GLuint texture_ {GL_INVALID_INDEX}; - bool cfpEnabled_; + GLsizeiptr numVertices_ {0}; - bool colorTableNeedsUpdate_; - bool sweepNeedsUpdate_; + bool cfpEnabled_ {false}; + + std::uint16_t rangeMin_ {0}; + float scale_ {1.0f}; + + bool colorTableNeedsUpdate_ {false}; + bool sweepNeedsUpdate_ {false}; }; -RadarProductLayer::RadarProductLayer(std::shared_ptr context) : - GenericLayer(context), p(std::make_unique()) +RadarProductLayer::RadarProductLayer(std::shared_ptr glContext) : + GenericLayer(std::move(glContext)), p(std::make_unique()) { - auto radarProductView = context->radar_product_view(); +} +RadarProductLayer::~RadarProductLayer() = default; + +void RadarProductLayer::Initialize( + const std::shared_ptr& mapContext) +{ + logger_->debug("Initialize()"); + + auto glContext = gl_context(); + + // Load and configure radar shader + p->shaderProgram_ = + glContext->GetShaderProgram(":/gl/radar.vert", ":/gl/radar.frag"); + + p->uMVPMatrixLocation_ = + glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); + if (p->uMVPMatrixLocation_ == -1) + { + logger_->warn("Could not find uMVPMatrix"); + } + + p->uMapScreenCoordLocation_ = + glGetUniformLocation(p->shaderProgram_->id(), "uMapScreenCoord"); + if (p->uMapScreenCoordLocation_ == -1) + { + logger_->warn("Could not find uMapScreenCoord"); + } + + p->uDataMomentOffsetLocation_ = + glGetUniformLocation(p->shaderProgram_->id(), "uDataMomentOffset"); + if (p->uDataMomentOffsetLocation_ == -1) + { + logger_->warn("Could not find uDataMomentOffset"); + } + + p->uDataMomentScaleLocation_ = + glGetUniformLocation(p->shaderProgram_->id(), "uDataMomentScale"); + if (p->uDataMomentScaleLocation_ == -1) + { + logger_->warn("Could not find uDataMomentScale"); + } + + p->uCFPEnabledLocation_ = + glGetUniformLocation(p->shaderProgram_->id(), "uCFPEnabled"); + if (p->uCFPEnabledLocation_ == -1) + { + logger_->warn("Could not find uCFPEnabled"); + } + + p->shaderProgram_->Use(); + + // Generate a vertex array object + glGenVertexArrays(1, &p->vao_); + + // Generate vertex buffer objects + glGenBuffers(3, p->vbo_.data()); + + // Update radar sweep + p->sweepNeedsUpdate_ = true; + UpdateSweep(mapContext); + + // Create color table + glGenTextures(1, &p->texture_); + p->colorTableNeedsUpdate_ = true; + UpdateColorTable(mapContext); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + + auto radarProductView = mapContext->radar_product_view(); connect(radarProductView.get(), &view::RadarProductView::ColorTableLutUpdated, this, @@ -90,112 +147,46 @@ RadarProductLayer::RadarProductLayer(std::shared_ptr context) : this, [this]() { p->sweepNeedsUpdate_ = true; }); } -RadarProductLayer::~RadarProductLayer() = default; -void RadarProductLayer::Initialize() +void RadarProductLayer::UpdateSweep( + const std::shared_ptr& mapContext) { - logger_->debug("Initialize()"); - - gl::OpenGLFunctions& gl = context()->gl(); - - // Load and configure radar shader - p->shaderProgram_ = - context()->GetShaderProgram(":/gl/radar.vert", ":/gl/radar.frag"); - - p->uMVPMatrixLocation_ = - gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); - if (p->uMVPMatrixLocation_ == -1) - { - logger_->warn("Could not find uMVPMatrix"); - } - - p->uMapScreenCoordLocation_ = - gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapScreenCoord"); - if (p->uMapScreenCoordLocation_ == -1) - { - logger_->warn("Could not find uMapScreenCoord"); - } - - p->uDataMomentOffsetLocation_ = - gl.glGetUniformLocation(p->shaderProgram_->id(), "uDataMomentOffset"); - if (p->uDataMomentOffsetLocation_ == -1) - { - logger_->warn("Could not find uDataMomentOffset"); - } - - p->uDataMomentScaleLocation_ = - gl.glGetUniformLocation(p->shaderProgram_->id(), "uDataMomentScale"); - if (p->uDataMomentScaleLocation_ == -1) - { - logger_->warn("Could not find uDataMomentScale"); - } - - p->uCFPEnabledLocation_ = - gl.glGetUniformLocation(p->shaderProgram_->id(), "uCFPEnabled"); - if (p->uCFPEnabledLocation_ == -1) - { - logger_->warn("Could not find uCFPEnabled"); - } - - p->shaderProgram_->Use(); - - // Generate a vertex array object - gl.glGenVertexArrays(1, &p->vao_); - - // Generate vertex buffer objects - gl.glGenBuffers(3, p->vbo_.data()); - - // Update radar sweep - p->sweepNeedsUpdate_ = true; - UpdateSweep(); - - // Create color table - gl.glGenTextures(1, &p->texture_); - p->colorTableNeedsUpdate_ = true; - UpdateColorTable(); - gl.glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - gl.glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - gl.glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); -} - -void RadarProductLayer::UpdateSweep() -{ - logger_->debug("UpdateSweep()"); - - gl::OpenGLFunctions& gl = context()->gl(); + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + // NOLINTBEGIN(modernize-use-nullptr) boost::timer::cpu_timer timer; std::shared_ptr radarProductView = - context()->radar_product_view(); + mapContext->radar_product_view(); std::unique_lock sweepLock(radarProductView->sweep_mutex(), std::try_to_lock); if (!sweepLock.owns_lock()) { - logger_->debug("Sweep locked, deferring update"); + logger_->trace("Sweep locked, deferring update"); return; } + logger_->debug("UpdateSweep()"); p->sweepNeedsUpdate_ = false; const std::vector& vertices = radarProductView->vertices(); // Bind a vertex array object - gl.glBindVertexArray(p->vao_); + glBindVertexArray(p->vao_); // Buffer vertices - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); timer.start(); - gl.glBufferData(GL_ARRAY_BUFFER, - vertices.size() * sizeof(GLfloat), - vertices.data(), - GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, + static_cast(vertices.size() * sizeof(GLfloat)), + vertices.data(), + GL_STATIC_DRAW); timer.stop(); logger_->debug("Vertices buffered in {}", timer.format(6, "%ws")); - gl.glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, static_cast(0)); - gl.glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, static_cast(0)); + glEnableVertexAttribArray(0); // Buffer data moments const GLvoid* data; @@ -214,14 +205,14 @@ void RadarProductLayer::UpdateSweep() type = GL_UNSIGNED_SHORT; } - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); timer.start(); - gl.glBufferData(GL_ARRAY_BUFFER, dataSize, data, GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, dataSize, data, GL_STATIC_DRAW); timer.stop(); logger_->debug("Data moments buffered in {}", timer.format(6, "%ws")); - gl.glVertexAttribIPointer(1, 1, type, 0, static_cast(0)); - gl.glEnableVertexAttribArray(1); + glVertexAttribIPointer(1, 1, type, 0, static_cast(0)); + glEnableVertexAttribArray(1); // Buffer CFP data const GLvoid* cfpData; @@ -243,41 +234,51 @@ void RadarProductLayer::UpdateSweep() cfpType = GL_UNSIGNED_SHORT; } - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[2]); + glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[2]); timer.start(); - gl.glBufferData(GL_ARRAY_BUFFER, cfpDataSize, cfpData, GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, cfpDataSize, cfpData, GL_STATIC_DRAW); timer.stop(); logger_->debug("CFP moments buffered in {}", timer.format(6, "%ws")); - gl.glVertexAttribIPointer(2, 1, cfpType, 0, static_cast(0)); - gl.glEnableVertexAttribArray(2); + glVertexAttribIPointer(2, 1, cfpType, 0, static_cast(0)); + glEnableVertexAttribArray(2); } else { - gl.glDisableVertexAttribArray(2); + glDisableVertexAttribArray(2); } - p->numVertices_ = vertices.size() / 2; + p->numVertices_ = static_cast(vertices.size() / 2); + + // NOLINTEND(modernize-use-nullptr) + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) } void RadarProductLayer::Render( + const std::shared_ptr& mapContext, const QMapLibre::CustomLayerRenderParameters& params) { - gl::OpenGLFunctions& gl = context()->gl(); p->shaderProgram_->Use(); // Set OpenGL blend mode for transparency - gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + const bool wireframeEnabled = mapContext->settings().radarWireframeEnabled_; + if (wireframeEnabled) + { + // Set polygon mode to draw wireframe + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } if (p->colorTableNeedsUpdate_) { - UpdateColorTable(); + UpdateColorTable(mapContext); } if (p->sweepNeedsUpdate_) { - UpdateSweep(); + UpdateSweep(mapContext); } const float scale = std::pow(2.0, params.zoom) * 2.0f * @@ -291,20 +292,29 @@ void RadarProductLayer::Render( glm::radians(params.bearing), glm::vec3(0.0f, 0.0f, 1.0f)); - gl.glUniform2fv(p->uMapScreenCoordLocation_, - 1, - glm::value_ptr(util::maplibre::LatLongToScreenCoordinate( - {params.latitude, params.longitude}))); + glUniform2fv(p->uMapScreenCoordLocation_, + 1, + glm::value_ptr(util::maplibre::LatLongToScreenCoordinate( + {params.latitude, params.longitude}))); - gl.glUniformMatrix4fv( + glUniformMatrix4fv( p->uMVPMatrixLocation_, 1, GL_FALSE, glm::value_ptr(uMVPMatrix)); - gl.glUniform1i(p->uCFPEnabledLocation_, p->cfpEnabled_ ? 1 : 0); + glUniform1i(p->uCFPEnabledLocation_, p->cfpEnabled_ ? 1 : 0); - gl.glActiveTexture(GL_TEXTURE0); - gl.glBindTexture(GL_TEXTURE_1D, p->texture_); - gl.glBindVertexArray(p->vao_); - gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + glUniform1ui(p->uDataMomentOffsetLocation_, p->rangeMin_); + glUniform1f(p->uDataMomentScaleLocation_, p->scale_); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_1D, p->texture_); + glBindVertexArray(p->vao_); + glDrawArrays(GL_TRIANGLES, 0, static_cast(p->numVertices_)); + + if (wireframeEnabled) + { + // Restore polygon mode to default + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } SCWX_GL_CHECK_ERROR(); } @@ -313,10 +323,8 @@ void RadarProductLayer::Deinitialize() { logger_->debug("Deinitialize()"); - gl::OpenGLFunctions& gl = context()->gl(); - - gl.glDeleteVertexArrays(1, &p->vao_); - gl.glDeleteBuffers(3, p->vbo_.data()); + glDeleteVertexArrays(1, &p->vao_); + glDeleteBuffers(3, p->vbo_.data()); p->uMVPMatrixLocation_ = GL_INVALID_INDEX; p->uMapScreenCoordLocation_ = GL_INVALID_INDEX; @@ -329,6 +337,7 @@ void RadarProductLayer::Deinitialize() } bool RadarProductLayer::RunMousePicking( + const std::shared_ptr& mapContext, const QMapLibre::CustomLayerRenderParameters& /* params */, const QPointF& /* mouseLocalPos */, const QPointF& mouseGlobalPos, @@ -342,13 +351,68 @@ bool RadarProductLayer::RunMousePicking( Qt::KeyboardModifier::ShiftModifier) { std::shared_ptr radarProductView = - context()->radar_product_view(); + mapContext->radar_product_view(); + + if (mapContext->radar_site() == nullptr) + { + return itemPicked; + } + + // Get distance and altitude of point + const double radarLatitude = mapContext->radar_site()->latitude(); + const double radarLongitude = mapContext->radar_site()->longitude(); + + const auto distanceMeters = + util::GeographicLib::GetDistance(mouseGeoCoords.latitude_, + mouseGeoCoords.longitude_, + radarLatitude, + radarLongitude); + + const std::string distanceUnitName = + settings::UnitSettings::Instance().distance_units().GetValue(); + const types::DistanceUnits distanceUnits = + types::GetDistanceUnitsFromName(distanceUnitName); + const double distanceScale = types::GetDistanceUnitsScale(distanceUnits); + const std::string distanceAbbrev = + types::GetDistanceUnitsAbbreviation(distanceUnits); + + const double distance = distanceMeters.value() * + scwx::common::kKilometersPerMeter * distanceScale; + std::string distanceHeightStr = + fmt::format("{:.2f} {}", distance, distanceAbbrev); if (radarProductView == nullptr) { + util::tooltip::Show(distanceHeightStr, mouseGlobalPos); + itemPicked = true; return itemPicked; } + std::optional elevation = radarProductView->elevation(); + if (elevation.has_value()) + { + const auto altitudeMeters = + util::GeographicLib::GetRadarBeamAltititude( + distanceMeters, + units::angle::degrees(*elevation), + mapContext->radar_site()->altitude()); + + const std::string heightUnitName = + settings::UnitSettings::Instance().echo_tops_units().GetValue(); + const types::EchoTopsUnits heightUnits = + types::GetEchoTopsUnitsFromName(heightUnitName); + const double heightScale = types::GetEchoTopsUnitsScale(heightUnits); + const std::string heightAbbrev = + types::GetEchoTopsUnitsAbbreviation(heightUnits); + + const double altitude = altitudeMeters.value() * + scwx::common::kKilometersPerMeter * + heightScale; + + distanceHeightStr = fmt::format( + "{}\n{:.2f} {}", distanceHeightStr, altitude, heightAbbrev); + } + std::optional binLevel = radarProductView->GetBinLevel(mouseGeoCoords); @@ -374,12 +438,13 @@ bool RadarProductLayer::RunMousePicking( if (codeName != codeShortName && !codeShortName.empty()) { // There is a unique long and short name for the code - hoverText = fmt::format("{}: {}", codeShortName, codeName); + hoverText = fmt::format( + "{}: {}\n{}", codeShortName, codeName, distanceHeightStr); } else { // Otherwise, only use the long name (always present) - hoverText = codeName; + hoverText = fmt::format("{}\n{}", codeName, distanceHeightStr); } // Show the tooltip @@ -430,17 +495,20 @@ bool RadarProductLayer::RunMousePicking( { // Don't display a units value that wasn't intended to be // displayed - hoverText = fmt::format("{}{}", f, suffix); + hoverText = + fmt::format("{}{}\n{}", f, suffix, distanceHeightStr); } else if (std::isalpha(static_cast(units.at(0)))) { // dBZ, Kts, etc. - hoverText = fmt::format("{} {}{}", f, units, suffix); + hoverText = fmt::format( + "{} {}{}\n{}", f, units, suffix, distanceHeightStr); } else { // %, etc. - hoverText = fmt::format("{}{}{}", f, units, suffix); + hoverText = fmt::format( + "{}{}{}\n{}", f, units, suffix, distanceHeightStr); } // Show the tooltip @@ -449,20 +517,26 @@ bool RadarProductLayer::RunMousePicking( itemPicked = true; } } + else + { + // Always show tooltip for distance and altitude + util::tooltip::Show(distanceHeightStr, mouseGlobalPos); + itemPicked = true; + } } return itemPicked; } -void RadarProductLayer::UpdateColorTable() +void RadarProductLayer::UpdateColorTable( + const std::shared_ptr& mapContext) { logger_->debug("UpdateColorTable()"); p->colorTableNeedsUpdate_ = false; - gl::OpenGLFunctions& gl = context()->gl(); std::shared_ptr radarProductView = - context()->radar_product_view(); + mapContext->radar_product_view(); const std::vector& colorTable = radarProductView->color_table_lut(); @@ -471,22 +545,20 @@ void RadarProductLayer::UpdateColorTable() const float scale = rangeMax - rangeMin; - gl.glActiveTexture(GL_TEXTURE0); - gl.glBindTexture(GL_TEXTURE_1D, p->texture_); - gl.glTexImage1D(GL_TEXTURE_1D, - 0, - GL_RGBA, - (GLsizei) colorTable.size(), - 0, - GL_RGBA, - GL_UNSIGNED_BYTE, - colorTable.data()); - gl.glGenerateMipmap(GL_TEXTURE_1D); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_1D, p->texture_); + glTexImage1D(GL_TEXTURE_1D, + 0, + GL_RGBA, + (GLsizei) colorTable.size(), + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + colorTable.data()); + glGenerateMipmap(GL_TEXTURE_1D); - gl.glUniform1ui(p->uDataMomentOffsetLocation_, rangeMin); - gl.glUniform1f(p->uDataMomentScaleLocation_, scale); + p->rangeMin_ = rangeMin; + p->scale_ = scale; } -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/radar_product_layer.hpp b/scwx-qt/source/scwx/qt/map/radar_product_layer.hpp index 1e53eba8..df5adcc9 100644 --- a/scwx-qt/source/scwx/qt/map/radar_product_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/radar_product_layer.hpp @@ -2,27 +2,25 @@ #include -namespace scwx +namespace scwx::qt::map { -namespace qt -{ -namespace map -{ - -class RadarProductLayerImpl; class RadarProductLayer : public GenericLayer { + Q_DISABLE_COPY_MOVE(RadarProductLayer) + public: - explicit RadarProductLayer(std::shared_ptr context); + explicit RadarProductLayer(std::shared_ptr glContext); ~RadarProductLayer(); - void Initialize() override final; - void Render(const QMapLibre::CustomLayerRenderParameters&) override final; - void Deinitialize() override final; + void Initialize(const std::shared_ptr& mapContext) final; + void Render(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters&) final; + void Deinitialize() final; - virtual bool - RunMousePicking(const QMapLibre::CustomLayerRenderParameters& params, + bool + RunMousePicking(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters& params, const QPointF& mouseLocalPos, const QPointF& mouseGlobalPos, const glm::vec2& mouseCoords, @@ -30,13 +28,12 @@ public: std::shared_ptr& eventHandler) override; private: - void UpdateColorTable(); - void UpdateSweep(); + void UpdateColorTable(const std::shared_ptr& mapContext); + void UpdateSweep(const std::shared_ptr& mapContext); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/radar_range_layer.cpp b/scwx-qt/source/scwx/qt/map/radar_range_layer.cpp index e660c266..b23232a3 100644 --- a/scwx-qt/source/scwx/qt/map/radar_range_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/radar_range_layer.cpp @@ -5,11 +5,7 @@ #include -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { static const std::string logPrefix_ = "scwx::qt::map::radar_range_layer"; @@ -98,6 +94,4 @@ GetRangeCircle(float range, QMapLibre::Coordinate center) return rangeCircle; } -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/radar_range_layer.hpp b/scwx-qt/source/scwx/qt/map/radar_range_layer.hpp index d900f01e..e11ea6fc 100644 --- a/scwx-qt/source/scwx/qt/map/radar_range_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/radar_range_layer.hpp @@ -2,13 +2,7 @@ #include -namespace scwx -{ -namespace qt -{ -namespace map -{ -namespace RadarRangeLayer +namespace scwx::qt::map::RadarRangeLayer { void Add(std::shared_ptr map, @@ -19,7 +13,4 @@ void Update(std::shared_ptr map, float range, QMapLibre::Coordinate center); -} // namespace RadarRangeLayer -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map::RadarRangeLayer diff --git a/scwx-qt/source/scwx/qt/map/radar_site_layer.cpp b/scwx-qt/source/scwx/qt/map/radar_site_layer.cpp index 33486185..9f6866b0 100644 --- a/scwx-qt/source/scwx/qt/map/radar_site_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/radar_site_layer.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include #include @@ -9,11 +11,9 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace map +#include + +namespace scwx::qt::map { static const std::string logPrefix_ = "scwx::qt::map::radar_site_layer"; @@ -22,11 +22,21 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class RadarSiteLayer::Impl { public: - explicit Impl(RadarSiteLayer* self) : self_ {self} {} + explicit Impl(RadarSiteLayer* self, + const std::shared_ptr& glContext) : + self_ {self}, geoLines_ {std::make_shared(glContext)} + { + } ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + void RenderRadarSite(const QMapLibre::CustomLayerRenderParameters& params, std::shared_ptr& radarSite); + void RenderRadarLine(const std::shared_ptr& mapContext); RadarSiteLayer* self_; @@ -40,28 +50,62 @@ public: float halfHeight_ {}; std::string hoverText_ {}; + + std::shared_ptr geoLines_; + std::array, 2> radarSiteLines_ { + nullptr, nullptr}; }; -RadarSiteLayer::RadarSiteLayer(std::shared_ptr context) : - DrawLayer(context), p(std::make_unique(this)) +RadarSiteLayer::RadarSiteLayer( + const std::shared_ptr& glContext) : + DrawLayer(glContext, "RadarSiteLayer"), + p(std::make_unique(this, glContext)) { } RadarSiteLayer::~RadarSiteLayer() = default; -void RadarSiteLayer::Initialize() +void RadarSiteLayer::Initialize(const std::shared_ptr& mapContext) { logger_->debug("Initialize()"); p->radarSites_ = config::RadarSite::GetAll(); + + p->geoLines_->StartLines(); + p->radarSiteLines_[0] = p->geoLines_->AddLine(); + p->radarSiteLines_[1] = p->geoLines_->AddLine(); + p->geoLines_->FinishLines(); + + static const boost::gil::rgba32f_pixel_t color0 {0.0f, 0.0f, 0.0f, 1.0f}; + static const boost::gil::rgba32f_pixel_t color1 {1.0f, 1.0f, 1.0f, 1.0f}; + static const float width = 1; + p->geoLines_->SetLineModulate(p->radarSiteLines_[0], color0); + p->geoLines_->SetLineWidth(p->radarSiteLines_[0], width + 2); + + p->geoLines_->SetLineModulate(p->radarSiteLines_[1], color1); + p->geoLines_->SetLineWidth(p->radarSiteLines_[1], width); + + AddDrawItem(p->geoLines_); + p->geoLines_->set_thresholded(false); + + DrawLayer::Initialize(mapContext); } void RadarSiteLayer::Render( + const std::shared_ptr& mapContext, const QMapLibre::CustomLayerRenderParameters& params) { - gl::OpenGLFunctions& gl = context()->gl(); + p->hoverText_.clear(); - context()->set_render_parameters(params); + auto mapDistance = util::maplibre::GetMapDistance(params); + auto threshold = units::length::kilometers( + settings::GeneralSettings::Instance().radar_site_threshold().GetValue()); + + if (!(threshold.value() == 0.0 || mapDistance <= threshold || + (threshold.value() < 0 && mapDistance >= -threshold))) + { + return; + } // Update map screen coordinate and scale information p->mapScreenCoordLocation_ = util::maplibre::LatLongToScreenCoordinate( @@ -73,8 +117,7 @@ void RadarSiteLayer::Render( p->halfWidth_ = params.width * 0.5f; p->halfHeight_ = params.height * 0.5f; - p->hoverText_.clear(); - + ImGuiFrameStart(mapContext); // Radar site ImGui windows shouldn't have padding ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2 {0.0f, 0.0f}); @@ -85,6 +128,11 @@ void RadarSiteLayer::Render( ImGui::PopStyleVar(); + p->RenderRadarLine(mapContext); + + DrawLayer::RenderWithoutImGui(params); + + ImGuiFrameEnd(); SCWX_GL_CHECK_ERROR(); } @@ -127,6 +175,7 @@ void RadarSiteLayer::Impl::RenderRadarSite( if (ImGui::Button(radarSite->id().c_str())) { Q_EMIT self_->RadarSiteSelected(radarSite->id()); + self_->ImGuiSelectContext(); } // Store hover text for mouse picking pass @@ -149,6 +198,38 @@ void RadarSiteLayer::Impl::RenderRadarSite( } } +void RadarSiteLayer::Impl::RenderRadarLine( + const std::shared_ptr& mapContext) +{ + if ((QGuiApplication::keyboardModifiers() & + Qt::KeyboardModifier::ShiftModifier) && + mapContext->radar_site() != nullptr) + { + const auto& mouseCoord = mapContext->mouse_coordinate(); + const double radarLatitude = mapContext->radar_site()->latitude(); + const double radarLongitude = mapContext->radar_site()->longitude(); + + geoLines_->SetLineLocation(radarSiteLines_[0], + static_cast(mouseCoord.latitude_), + static_cast(mouseCoord.longitude_), + static_cast(radarLatitude), + static_cast(radarLongitude)); + geoLines_->SetLineVisible(radarSiteLines_[0], true); + + geoLines_->SetLineLocation(radarSiteLines_[1], + static_cast(mouseCoord.latitude_), + static_cast(mouseCoord.longitude_), + static_cast(radarLatitude), + static_cast(radarLongitude)); + geoLines_->SetLineVisible(radarSiteLines_[1], true); + } + else + { + geoLines_->SetLineVisible(radarSiteLines_[0], false); + geoLines_->SetLineVisible(radarSiteLines_[1], false); + } +} + void RadarSiteLayer::Deinitialize() { logger_->debug("Deinitialize()"); @@ -157,6 +238,7 @@ void RadarSiteLayer::Deinitialize() } bool RadarSiteLayer::RunMousePicking( + const std::shared_ptr& /* mapContext */, const QMapLibre::CustomLayerRenderParameters& /* params */, const QPointF& /* mouseLocalPos */, const QPointF& mouseGlobalPos, @@ -173,6 +255,4 @@ bool RadarSiteLayer::RunMousePicking( return false; } -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/map/radar_site_layer.hpp b/scwx-qt/source/scwx/qt/map/radar_site_layer.hpp index f88786f4..74fc6398 100644 --- a/scwx-qt/source/scwx/qt/map/radar_site_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/radar_site_layer.hpp @@ -2,11 +2,7 @@ #include -namespace scwx -{ -namespace qt -{ -namespace map +namespace scwx::qt::map { class RadarSiteLayer : public DrawLayer @@ -15,20 +11,22 @@ class RadarSiteLayer : public DrawLayer Q_DISABLE_COPY_MOVE(RadarSiteLayer) public: - explicit RadarSiteLayer(std::shared_ptr context); + explicit RadarSiteLayer(const std::shared_ptr& glContext); ~RadarSiteLayer(); - void Initialize() override final; - void Render(const QMapLibre::CustomLayerRenderParameters&) override final; - void Deinitialize() override final; + void Initialize(const std::shared_ptr& mapContext) final; + void Render(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters&) final; + void Deinitialize() final; - bool RunMousePicking( - const QMapLibre::CustomLayerRenderParameters& params, - const QPointF& mouseLocalPos, - const QPointF& mouseGlobalPos, - const glm::vec2& mouseCoords, - const common::Coordinate& mouseGeoCoords, - std::shared_ptr& eventHandler) override final; + bool + RunMousePicking(const std::shared_ptr& mapContext, + const QMapLibre::CustomLayerRenderParameters& params, + const QPointF& mouseLocalPos, + const QPointF& mouseGlobalPos, + const glm::vec2& mouseCoords, + const common::Coordinate& mouseGeoCoords, + std::shared_ptr& eventHandler) final; signals: void RadarSiteSelected(const std::string& id); @@ -38,6 +36,4 @@ private: std::unique_ptr p; }; -} // namespace map -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::map diff --git a/scwx-qt/source/scwx/qt/model/alert_model.cpp b/scwx-qt/source/scwx/qt/model/alert_model.cpp index d4bb1111..20af05f8 100644 --- a/scwx-qt/source/scwx/qt/model/alert_model.cpp +++ b/scwx-qt/source/scwx/qt/model/alert_model.cpp @@ -10,17 +10,10 @@ #include #include - -#include - #include #include -namespace scwx -{ -namespace qt -{ -namespace model +namespace scwx::qt::model { static const std::string logPrefix_ = "scwx::qt::model::alert_model"; @@ -37,9 +30,9 @@ public: explicit AlertModelImpl(); ~AlertModelImpl() = default; - bool GetObserved(const types::TextEventKey& key); - awips::ThreatCategory GetThreatCategory(const types::TextEventKey& key); - bool GetTornadoPossible(const types::TextEventKey& key); + bool GetObserved(const types::TextEventKey& key); + awips::ibw::ThreatCategory GetThreatCategory(const types::TextEventKey& key); + bool GetTornadoPossible(const types::TextEventKey& key); static std::string GetCounties(const types::TextEventKey& key); static std::string GetState(const types::TextEventKey& key); @@ -61,7 +54,7 @@ public: types::TextEventHash> observedMap_; std::unordered_map> threatCategoryMap_; std::unordered_map> - distanceMap_; - scwx::common::Coordinate previousPosition_; + distanceMap_; + scwx::common::Coordinate previousPosition_; }; AlertModel::AlertModel(QObject* parent) : @@ -158,7 +151,7 @@ QVariant AlertModel::data(const QModelIndex& index, int role) const case static_cast(Column::ThreatCategory): if (role == Qt::DisplayRole) { - return QString::fromStdString(awips::GetThreatCategoryName( + return QString::fromStdString(awips::ibw::GetThreatCategoryName( p->GetThreatCategory(textEventKey))); } else @@ -330,16 +323,45 @@ AlertModel::headerData(int section, Qt::Orientation orientation, int role) const } void AlertModel::HandleAlert(const types::TextEventKey& alertKey, - size_t messageIndex) + std::size_t messageIndex, + boost::uuids::uuid uuid) { logger_->trace("Handle alert: {}", alertKey.ToString()); double distanceInMeters; + const auto& alertMessages = p->textEventManager_->message_list(alertKey); + + // Find message by UUID instead of index, as the message index could have + // changed between the signal being emitted and the handler being called + auto messageIt = std::find_if(alertMessages.cbegin(), + alertMessages.cend(), + [&uuid](const auto& message) + { return uuid == message->uuid(); }); + + if (messageIt == alertMessages.cend()) + { + logger_->warn("Could not find alert uuid: {} ({})", + alertKey.ToString(), + messageIndex); + return; + } + + auto& message = *messageIt; + + // Store the current message index + messageIndex = static_cast( + std::distance(alertMessages.cbegin(), messageIt)); + + // Skip alert if this is not the most recent message + if (messageIndex + 1 < alertMessages.size()) + { + return; + } + // Get the most recent segment for the event - auto alertMessages = p->textEventManager_->message_list(alertKey); - std::shared_ptr alertSegment = - alertMessages[messageIndex]->segments().back(); + const std::shared_ptr alertSegment = + message->segments().back(); p->observedMap_.insert_or_assign(alertKey, alertSegment->observed_); p->threatCategoryMap_.insert_or_assign(alertKey, @@ -387,6 +409,36 @@ void AlertModel::HandleAlert(const types::TextEventKey& alertKey, } } +void AlertModel::HandleAlertsRemoved( + const std::unordered_set>& + alertKeys) +{ + logger_->trace("Handle alerts removed"); + + for (const auto& alertKey : alertKeys) + { + // Remove from the list of text event keys + auto it = std::find( + p->textEventKeys_.begin(), p->textEventKeys_.end(), alertKey); + if (it != p->textEventKeys_.end()) + { + const int row = + static_cast(std::distance(p->textEventKeys_.begin(), it)); + beginRemoveRows(QModelIndex(), row, row); + p->textEventKeys_.erase(it); + endRemoveRows(); + } + + // Remove from internal maps + p->observedMap_.erase(alertKey); + p->threatCategoryMap_.erase(alertKey); + p->tornadoPossibleMap_.erase(alertKey); + p->centroidMap_.erase(alertKey); + p->distanceMap_.erase(alertKey); + } +} + void AlertModel::HandleMapUpdate(double latitude, double longitude) { logger_->trace("Handle map update: {}, {}", latitude, longitude); @@ -439,10 +491,10 @@ bool AlertModelImpl::GetObserved(const types::TextEventKey& key) return observed; } -awips::ThreatCategory +awips::ibw::ThreatCategory AlertModelImpl::GetThreatCategory(const types::TextEventKey& key) { - awips::ThreatCategory threatCategory = awips::ThreatCategory::Base; + awips::ibw::ThreatCategory threatCategory = awips::ibw::ThreatCategory::Base; auto it = threatCategoryMap_.find(key); if (it != threatCategoryMap_.cend()) @@ -489,8 +541,8 @@ std::string AlertModelImpl::GetCounties(const types::TextEventKey& key) } else { - logger_->warn("GetCounties(): No message associated with key: {}", - key.ToString()); + logger_->trace("GetCounties(): No message associated with key: {}", + key.ToString()); return {}; } } @@ -508,8 +560,8 @@ std::string AlertModelImpl::GetState(const types::TextEventKey& key) } else { - logger_->warn("GetState(): No message associated with key: {}", - key.ToString()); + logger_->trace("GetState(): No message associated with key: {}", + key.ToString()); return {}; } } @@ -526,8 +578,8 @@ AlertModelImpl::GetStartTime(const types::TextEventKey& key) } else { - logger_->warn("GetStartTime(): No message associated with key: {}", - key.ToString()); + logger_->trace("GetStartTime(): No message associated with key: {}", + key.ToString()); return {}; } } @@ -551,8 +603,8 @@ AlertModelImpl::GetEndTime(const types::TextEventKey& key) } else { - logger_->warn("GetEndTime(): No message associated with key: {}", - key.ToString()); + logger_->trace("GetEndTime(): No message associated with key: {}", + key.ToString()); return {}; } } @@ -562,6 +614,4 @@ std::string AlertModelImpl::GetEndTimeString(const types::TextEventKey& key) return scwx::util::TimeString(GetEndTime(key)); } -} // namespace model -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::model diff --git a/scwx-qt/source/scwx/qt/model/alert_model.hpp b/scwx-qt/source/scwx/qt/model/alert_model.hpp index df6d561e..443ca9bb 100644 --- a/scwx-qt/source/scwx/qt/model/alert_model.hpp +++ b/scwx-qt/source/scwx/qt/model/alert_model.hpp @@ -4,7 +4,9 @@ #include #include +#include +#include #include namespace scwx @@ -50,7 +52,13 @@ public: int role = Qt::DisplayRole) const override; public slots: - void HandleAlert(const types::TextEventKey& alertKey, size_t messageIndex); + void HandleAlert(const types::TextEventKey& alertKey, + std::size_t messageIndex, + boost::uuids::uuid uuid); + void HandleAlertsRemoved( + const std::unordered_set>& + alertKeys); void HandleMapUpdate(double latitude, double longitude); private: diff --git a/scwx-qt/source/scwx/qt/model/alert_proxy_model.cpp b/scwx-qt/source/scwx/qt/model/alert_proxy_model.cpp index a2afee55..0fa55d96 100644 --- a/scwx-qt/source/scwx/qt/model/alert_proxy_model.cpp +++ b/scwx-qt/source/scwx/qt/model/alert_proxy_model.cpp @@ -3,27 +3,29 @@ #include #include #include +#include #include #include #include -namespace scwx -{ -namespace qt -{ -namespace model +namespace scwx::qt::model { static const std::string logPrefix_ = "scwx::qt::model::alert_proxy_model"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -class AlertProxyModelImpl +class AlertProxyModel::Impl { public: - explicit AlertProxyModelImpl(AlertProxyModel* self); - ~AlertProxyModelImpl(); + explicit Impl(AlertProxyModel* self); + ~Impl(); + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; void UpdateAlerts(); @@ -36,8 +38,7 @@ public: }; AlertProxyModel::AlertProxyModel(QObject* parent) : - QSortFilterProxyModel(parent), - p(std::make_unique(this)) + QSortFilterProxyModel(parent), p(std::make_unique(this)) { } AlertProxyModel::~AlertProxyModel() = default; @@ -67,7 +68,7 @@ bool AlertProxyModel::filterAcceptsRow(int sourceRow, .value(); // Compare end time to current - if (endTime < std::chrono::system_clock::now()) + if (endTime < scwx::util::time::now()) { acceptAlertActiveFilter = false; } @@ -77,7 +78,7 @@ bool AlertProxyModel::filterAcceptsRow(int sourceRow, QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); } -AlertProxyModelImpl::AlertProxyModelImpl(AlertProxyModel* self) : +AlertProxyModel::Impl::Impl(AlertProxyModel* self) : self_ {self}, alertActiveFilterEnabled_ {false}, alertUpdateTimer_ {scwx::util::io_context()} @@ -86,26 +87,37 @@ AlertProxyModelImpl::AlertProxyModelImpl(AlertProxyModel* self) : UpdateAlerts(); } -AlertProxyModelImpl::~AlertProxyModelImpl() +AlertProxyModel::Impl::~Impl() { - std::unique_lock lock(alertMutex_); - alertUpdateTimer_.cancel(); + try + { + const std::unique_lock lock(alertMutex_); + alertUpdateTimer_.cancel(); + } + catch (const std::exception& ex) + { + logger_->error(ex.what()); + } } -void AlertProxyModelImpl::UpdateAlerts() +void AlertProxyModel::Impl::UpdateAlerts() { logger_->trace("UpdateAlerts"); // Take a unique lock before modifying feature lists - std::unique_lock lock(alertMutex_); + const std::unique_lock lock(alertMutex_); // Re-evaluate for expired alerts if (alertActiveFilterEnabled_) { - self_->invalidateRowsFilter(); + QMetaObject::invokeMethod(self_, + static_cast( + &QSortFilterProxyModel::invalidate)); } using namespace std::chrono; + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers): Readability alertUpdateTimer_.expires_after(15s); alertUpdateTimer_.async_wait( [this](const boost::system::error_code& e) @@ -132,6 +144,4 @@ void AlertProxyModelImpl::UpdateAlerts() }); } -} // namespace model -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::model diff --git a/scwx-qt/source/scwx/qt/model/alert_proxy_model.hpp b/scwx-qt/source/scwx/qt/model/alert_proxy_model.hpp index ee8b81c1..1ee6a138 100644 --- a/scwx-qt/source/scwx/qt/model/alert_proxy_model.hpp +++ b/scwx-qt/source/scwx/qt/model/alert_proxy_model.hpp @@ -4,11 +4,7 @@ #include -namespace scwx -{ -namespace qt -{ -namespace model +namespace scwx::qt::model { class AlertProxyModelImpl; @@ -16,7 +12,7 @@ class AlertProxyModelImpl; class AlertProxyModel : public QSortFilterProxyModel { private: - Q_DISABLE_COPY(AlertProxyModel) + Q_DISABLE_COPY_MOVE(AlertProxyModel) public: explicit AlertProxyModel(QObject* parent = nullptr); @@ -24,15 +20,13 @@ public: void SetAlertActiveFilter(bool enabled); - bool filterAcceptsRow(int sourceRow, - const QModelIndex& sourceParent) const override; + [[nodiscard]] bool + filterAcceptsRow(int sourceRow, + const QModelIndex& sourceParent) const override; private: - std::unique_ptr p; - - friend class AlertProxyModelImpl; + class Impl; + std::unique_ptr p; }; -} // namespace model -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::model diff --git a/scwx-qt/source/scwx/qt/model/imgui_context_model.cpp b/scwx-qt/source/scwx/qt/model/imgui_context_model.cpp index 37ca523f..33cbfe79 100644 --- a/scwx-qt/source/scwx/qt/model/imgui_context_model.cpp +++ b/scwx-qt/source/scwx/qt/model/imgui_context_model.cpp @@ -4,11 +4,12 @@ #include -namespace scwx -{ -namespace qt -{ -namespace model +// Expose required functions from internal API +void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, + int frame_count, + bool renderer_has_textures); + +namespace scwx::qt::model { static const std::string logPrefix_ = "scwx::qt::model::imgui_context_model"; @@ -23,6 +24,8 @@ public: std::vector contexts_ {}; ImFontAtlas fontAtlas_ {}; + + int frameCount_ {0}; }; ImGuiContextModel::ImGuiContextModel() : @@ -135,6 +138,14 @@ void ImGuiContextModel::DestroyContext(const std::string& name) } } +void ImGuiContextModel::NewFrame() +{ + static constexpr bool kRendererHasTextures_ = true; + + ImFontAtlasUpdateNewFrame( + &p->fontAtlas_, ++p->frameCount_, kRendererHasTextures_); +} + std::vector ImGuiContextModel::contexts() const { return p->contexts_; @@ -153,6 +164,4 @@ ImGuiContextModel& ImGuiContextModel::Instance() bool ImGuiContextInfo::operator==(const ImGuiContextInfo& o) const = default; -} // namespace model -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::model diff --git a/scwx-qt/source/scwx/qt/model/imgui_context_model.hpp b/scwx-qt/source/scwx/qt/model/imgui_context_model.hpp index 894931c5..926501fc 100644 --- a/scwx-qt/source/scwx/qt/model/imgui_context_model.hpp +++ b/scwx-qt/source/scwx/qt/model/imgui_context_model.hpp @@ -8,11 +8,7 @@ struct ImFontAtlas; struct ImGuiContext; -namespace scwx -{ -namespace qt -{ -namespace model +namespace scwx::qt::model { class ImGuiContextModelImpl; @@ -46,6 +42,8 @@ public: ImGuiContext* CreateContext(const std::string& name); void DestroyContext(const std::string& name); + void NewFrame(); + std::vector contexts() const; ImFontAtlas* font_atlas(); @@ -59,6 +57,4 @@ private: std::unique_ptr p; }; -} // namespace model -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::model diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index 35f188a9..6be8eb9d 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include @@ -43,6 +43,7 @@ static const std::vector kDefaultLayers_ { types::InformationLayer::RadarSite, false, {false, false, false, false}}, + {types::LayerType::Information, types::InformationLayer::Markers, true}, {types::LayerType::Data, types::DataLayer::RadarRange, true}, {types::LayerType::Alert, awips::Phenomenon::Tornado, true}, {types::LayerType::Alert, awips::Phenomenon::SnowSquall, true}, @@ -66,13 +67,6 @@ static const std::vector kImmovableLayers_ { {types::LayerType::Map, types::MapLayer::MapUnderlay, false}, }; -static const std::array kAlertPhenomena_ { - awips::Phenomenon::Tornado, - awips::Phenomenon::SnowSquall, - awips::Phenomenon::SevereThunderstorm, - awips::Phenomenon::FlashFlood, - awips::Phenomenon::Marine}; - class LayerModel::Impl { public: @@ -102,6 +96,8 @@ public: manager::PlacefileManager::Instance()}; types::LayerVector layers_ {}; + + bool fileRead_ {false}; }; LayerModel::LayerModel(QObject* parent) : @@ -207,6 +203,8 @@ void LayerModel::Impl::ReadLayerSettings() // Assign read layers layers_.swap(newLayers); } + + fileRead_ = true; } void LayerModel::Impl::ValidateLayerSettings(types::LayerVector& layers) @@ -320,6 +318,10 @@ void LayerModel::Impl::ValidateLayerSettings(types::LayerVector& layers) void LayerModel::Impl::WriteLayerSettings() { + if (!fileRead_) + { + return; + } logger_->info("Saving layer settings"); auto layerJson = boost::json::value_from(layers_); @@ -780,6 +782,13 @@ bool LayerModel::dropMimeData(const QMimeData* data, QDataStream stream(&mimeData, QIODevice::ReadOnly); std::vector sourceRows {}; + // Validate parent row + if (parent.row() < 0 || parent.row() >= static_cast(p->layers_.size())) + { + logger_->warn("Cannot perform drop action, invalid parent row"); + return false; + } + // Read source rows from QMimeData while (!stream.atEnd()) { diff --git a/scwx-qt/source/scwx/qt/model/marker_model.cpp b/scwx-qt/source/scwx/qt/model/marker_model.cpp new file mode 100644 index 00000000..6c232177 --- /dev/null +++ b/scwx-qt/source/scwx/qt/model/marker_model.cpp @@ -0,0 +1,288 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace model +{ + +static const std::string logPrefix_ = "scwx::qt::model::marker_model"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); +static const int iconSize_ = 30; + +static constexpr int kFirstColumn = + static_cast(MarkerModel::Column::Latitude); +static constexpr int kLastColumn = + static_cast(MarkerModel::Column::Name); +static constexpr int kNumColumns = kLastColumn - kFirstColumn + 1; + +class MarkerModel::Impl +{ +public: + explicit Impl() {} + ~Impl() = default; + std::shared_ptr markerManager_ { + manager::MarkerManager::Instance()}; + std::vector markerIds_; +}; + +MarkerModel::MarkerModel(QObject* parent) : + QAbstractTableModel(parent), p(std::make_unique()) +{ + connect(p->markerManager_.get(), + &manager::MarkerManager::MarkersInitialized, + this, + &MarkerModel::HandleMarkersInitialized); + + connect(p->markerManager_.get(), + &manager::MarkerManager::MarkerAdded, + this, + &MarkerModel::HandleMarkerAdded); + + connect(p->markerManager_.get(), + &manager::MarkerManager::MarkerChanged, + this, + &MarkerModel::HandleMarkerChanged); + + connect(p->markerManager_.get(), + &manager::MarkerManager::MarkerRemoved, + this, + &MarkerModel::HandleMarkerRemoved); +} + +MarkerModel::~MarkerModel() = default; + +int MarkerModel::rowCount(const QModelIndex& parent) const +{ + return parent.isValid() ? + 0 : + static_cast(p->markerIds_.size()); +} + +int MarkerModel::columnCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : kNumColumns; +} + +Qt::ItemFlags MarkerModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags flags = QAbstractTableModel::flags(index); + + return flags; +} + +QVariant MarkerModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || index.row() < 0 || + static_cast(index.row()) >= p->markerIds_.size()) + { + logger_->debug("Failed to get data index {}", index.row()); + return QVariant(); + } + + types::MarkerId id = p->markerIds_[index.row()]; + std::optional markerInfo = + p->markerManager_->get_marker(id); + if (!markerInfo) + { + logger_->debug("Failed to get data index {} id {}", index.row(), id); + return QVariant(); + } + + if (role == Qt::ItemDataRole::UserRole) + { + return qulonglong(id); + } + + switch(index.column()) + { + case static_cast(Column::Name): + if (role == Qt::ItemDataRole::DisplayRole || + role == Qt::ItemDataRole::ToolTipRole) + { + return QString::fromStdString(markerInfo->name); + } + break; + + case static_cast(Column::Latitude): + if (role == Qt::ItemDataRole::DisplayRole || + role == Qt::ItemDataRole::ToolTipRole) + { + return QString::fromStdString( + common::GetLatitudeString(markerInfo->latitude)); + } + break; + + case static_cast(Column::Longitude): + if (role == Qt::ItemDataRole::DisplayRole || + role == Qt::ItemDataRole::ToolTipRole) + { + return QString::fromStdString( + common::GetLongitudeString(markerInfo->longitude)); + } + break; + break; + case static_cast(Column::Icon): + if (role == Qt::ItemDataRole::DisplayRole) + { + std::optional icon = + p->markerManager_->get_icon(markerInfo->iconName); + if (icon) + { + return QString::fromStdString(icon->shortName); + } + else + { + return {}; + } + } + else if (role == Qt::ItemDataRole::DecorationRole) + { + std::optional icon = + p->markerManager_->get_icon(markerInfo->iconName); + if (icon) + { + return util::modulateColors(icon->qIcon, + QSize(iconSize_, iconSize_), + QColor(markerInfo->iconColor[0], + markerInfo->iconColor[1], + markerInfo->iconColor[2], + markerInfo->iconColor[3])); + } + else + { + return {}; + } + } + break; + + default: + break; + } + + return QVariant(); +} + +std::optional MarkerModel::getId(int index) +{ + if (index < 0 || static_cast(index) >= p->markerIds_.size()) + { + return {}; + } + + return p->markerIds_[index]; +} + +QVariant MarkerModel::headerData(int section, + Qt::Orientation orientation, + int role) const +{ + if (role == Qt::ItemDataRole::DisplayRole) + { + if (orientation == Qt::Horizontal) + { + switch (section) + { + case static_cast(Column::Name): + return tr("Name"); + + case static_cast(Column::Latitude): + return tr("Latitude"); + + case static_cast(Column::Longitude): + return tr("Longitude"); + + case static_cast(Column::Icon): + return tr("Icon"); + + default: + break; + } + } + } + + return QVariant(); +} + +bool MarkerModel::setData(const QModelIndex&, const QVariant&, int) +{ + return false; +} + +void MarkerModel::HandleMarkersInitialized(size_t count) +{ + if (count == 0) + { + return; + } + const int index = static_cast(count - 1); + + p->markerIds_.reserve(count); + beginInsertRows(QModelIndex(), 0, index); + p->markerManager_->for_each( + [this](const types::MarkerInfo& info) + { + p->markerIds_.push_back(info.id); + }); + endInsertRows(); +} + +void MarkerModel::HandleMarkerAdded(types::MarkerId id) +{ + std::optional index = p->markerManager_->get_index(id); + if (!index) + { + return; + } + const int newIndex = static_cast(*index); + + beginInsertRows(QModelIndex(), newIndex, newIndex); + auto it = std::next(p->markerIds_.begin(), newIndex); + p->markerIds_.emplace(it, id); + endInsertRows(); +} + +void MarkerModel::HandleMarkerChanged(types::MarkerId id) +{ + auto it = std::find(p->markerIds_.begin(), p->markerIds_.end(), id); + if (it == p->markerIds_.end()) + { + return; + } + const int changedIndex = std::distance(p->markerIds_.begin(), it); + + QModelIndex topLeft = createIndex(changedIndex, kFirstColumn); + QModelIndex bottomRight = createIndex(changedIndex, kLastColumn); + + Q_EMIT dataChanged(topLeft, bottomRight); +} + +void MarkerModel::HandleMarkerRemoved(types::MarkerId id) +{ + auto it = std::find(p->markerIds_.begin(), p->markerIds_.end(), id); + if (it == p->markerIds_.end()) + { + return; + } + + const int removedIndex = std::distance(p->markerIds_.begin(), it); + + beginRemoveRows(QModelIndex(), removedIndex, removedIndex); + p->markerIds_.erase(it); + endRemoveRows(); +} + +} // namespace model +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/model/marker_model.hpp b/scwx-qt/source/scwx/qt/model/marker_model.hpp new file mode 100644 index 00000000..91c8854f --- /dev/null +++ b/scwx-qt/source/scwx/qt/model/marker_model.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace model +{ + +class MarkerModel : public QAbstractTableModel +{ +public: + enum class Column : int + { + Latitude = 0, + Longitude = 1, + Icon = 2, + Name = 3, + }; + + explicit MarkerModel(QObject* parent = nullptr); + ~MarkerModel(); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + QVariant data(const QModelIndex& index, + int role = Qt::DisplayRole) const override; + QVariant headerData(int section, + Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + bool setData(const QModelIndex& index, + const QVariant& value, + int role = Qt::EditRole) override; + + std::optional getId(int index); + +public slots: + void HandleMarkersInitialized(size_t count); + void HandleMarkerAdded(types::MarkerId id); + void HandleMarkerChanged(types::MarkerId id); + void HandleMarkerRemoved(types::MarkerId id); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace model +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/model/radar_site_model.cpp b/scwx-qt/source/scwx/qt/model/radar_site_model.cpp index 482c9828..f131a1cd 100644 --- a/scwx-qt/source/scwx/qt/model/radar_site_model.cpp +++ b/scwx-qt/source/scwx/qt/model/radar_site_model.cpp @@ -4,8 +4,8 @@ #include #include #include -#include #include +#include #include #include @@ -68,6 +68,8 @@ public: scwx::common::Coordinate previousPosition_; QIcon starIcon_ {":/res/icons/font-awesome-6/star-solid.svg"}; + + bool presetsRead_ {false}; }; RadarSiteModel::RadarSiteModel(QObject* parent) : @@ -115,7 +117,7 @@ void RadarSiteModelImpl::ReadPresets() // Determine if presets exists if (std::filesystem::exists(presetsPath_)) { - presetsJson = util::json::ReadJsonFile(presetsPath_); + presetsJson = scwx::util::json::ReadJsonFile(presetsPath_); } // If presets was successfully read @@ -146,14 +148,19 @@ void RadarSiteModelImpl::ReadPresets() } } } + presetsRead_ = true; } void RadarSiteModelImpl::WritePresets() { + if (!presetsRead_) + { + return; + } logger_->info("Saving presets"); auto presetsJson = boost::json::value_from(presets_); - util::json::WriteJsonFile(presetsPath_, presetsJson); + scwx::util::json::WriteJsonFile(presetsPath_, presetsJson); } int RadarSiteModel::rowCount(const QModelIndex& parent) const diff --git a/scwx-qt/source/scwx/qt/settings/alert_palette_settings.cpp b/scwx-qt/source/scwx/qt/settings/alert_palette_settings.cpp new file mode 100644 index 00000000..1c7aca31 --- /dev/null +++ b/scwx-qt/source/scwx/qt/settings/alert_palette_settings.cpp @@ -0,0 +1,240 @@ +#include +#include + +#include + +#include +#include +#include + +namespace scwx::qt::settings +{ + +static const std::string logPrefix_ = + "scwx::qt::settings::alert_palette_settings"; + +static const boost::gil::rgba8_pixel_t kColorBlack_ {0, 0, 0, 255}; + +struct LineData +{ + boost::gil::rgba8_pixel_t borderColor_ {kColorBlack_}; + boost::gil::rgba8_pixel_t highlightColor_ {kColorBlack_}; + boost::gil::rgba8_pixel_t lineColor_; + std::int64_t borderWidth_ {1}; + std::int64_t highlightWidth_ {0}; + std::int64_t lineWidth_ {3}; +}; + +typedef boost::unordered_flat_map + ThreatCategoryPalette; + +static const boost::unordered_flat_map + kThreatCategoryPalettes_ // + {{awips::Phenomenon::Marine, + {{awips::ibw::ThreatCategory::Base, {.lineColor_ {255, 127, 0, 255}}}}}, + {awips::Phenomenon::FlashFlood, + {{awips::ibw::ThreatCategory::Base, {.lineColor_ {0, 255, 0, 255}}}, + {awips::ibw::ThreatCategory::Considerable, + {.highlightColor_ {0, 255, 0, 255}, + .lineColor_ {kColorBlack_}, + .highlightWidth_ = 1, + .lineWidth_ = 1}}, + {awips::ibw::ThreatCategory::Catastrophic, + {.highlightColor_ {0, 255, 0, 255}, + .lineColor_ {255, 0, 0, 255}, + .highlightWidth_ = 1, + .lineWidth_ = 1}}}}, + {awips::Phenomenon::SevereThunderstorm, + {{awips::ibw::ThreatCategory::Base, {.lineColor_ {255, 255, 0, 255}}}, + {awips::ibw::ThreatCategory::Considerable, + {.highlightColor_ {255, 255, 0, 255}, + .lineColor_ {255, 0, 0, 255}, + .highlightWidth_ = 1, + .lineWidth_ = 1}}, + {awips::ibw::ThreatCategory::Destructive, + {.highlightColor_ {255, 255, 0, 255}, + .lineColor_ {255, 0, 0, 255}, + .highlightWidth_ = 1, + .lineWidth_ = 2}}}}, + {awips::Phenomenon::SnowSquall, + {{awips::ibw::ThreatCategory::Base, {.lineColor_ {0, 255, 255, 255}}}}}, + {awips::Phenomenon::Tornado, + {{awips::ibw::ThreatCategory::Base, {.lineColor_ {255, 0, 0, 255}}}, + {awips::ibw::ThreatCategory::Considerable, + {.lineColor_ {255, 0, 255, 255}}}, + {awips::ibw::ThreatCategory::Catastrophic, + {.highlightColor_ {255, 0, 255, 255}, + .lineColor_ {kColorBlack_}, + .highlightWidth_ = 1, + .lineWidth_ = 1}}}}}; + +static const boost::unordered_flat_map + kObservedPalettes_ // + {{awips::Phenomenon::Tornado, + {.highlightColor_ {255, 0, 0, 255}, + .lineColor_ {kColorBlack_}, + .highlightWidth_ = 1, + .lineWidth_ = 1}}}; + +static const boost::unordered_flat_map + kTornadoPossiblePalettes_ // + {{awips::Phenomenon::Marine, + {.highlightColor_ {255, 127, 0, 255}, + .lineColor_ {kColorBlack_}, + .highlightWidth_ = 1, + .lineWidth_ = 1}}, + {awips::Phenomenon::SevereThunderstorm, + {.highlightColor_ {255, 255, 0, 255}, + .lineColor_ {kColorBlack_}, + .highlightWidth_ = 1, + .lineWidth_ = 1}}}; + +static const boost::unordered_flat_map + kInactivePalettes_ // + { + {awips::Phenomenon::Marine, {.lineColor_ {127, 63, 0, 255}}}, + {awips::Phenomenon::FlashFlood, {.lineColor_ {0, 127, 0, 255}}}, + {awips::Phenomenon::SevereThunderstorm, {.lineColor_ {127, 127, 0, 255}}}, + {awips::Phenomenon::SnowSquall, {.lineColor_ {0, 127, 127, 255}}}, + {awips::Phenomenon::Tornado, {.lineColor_ {127, 0, 0, 255}}}, + }; + +class AlertPaletteSettings::Impl +{ +public: + explicit Impl(awips::Phenomenon phenomenon) : phenomenon_ {phenomenon} + { + const auto& info = awips::ibw::GetImpactBasedWarningInfo(phenomenon); + + const auto& threatCategoryPalettes = + kThreatCategoryPalettes_.at(phenomenon); + + for (auto& threatCategory : info.threatCategories_) + { + std::string threatCategoryName = + awips::ibw::GetThreatCategoryName(threatCategory); + boost::algorithm::to_lower(threatCategoryName); + auto result = + threatCategoryMap_.emplace(threatCategory, threatCategoryName); + auto& lineSettings = result.first->second; + + SetDefaultLineData(lineSettings, + threatCategoryPalettes.at(threatCategory)); + } + + if (info.hasObservedTag_) + { + SetDefaultLineData(observed_, kObservedPalettes_.at(phenomenon)); + } + + if (info.hasTornadoPossibleTag_) + { + SetDefaultLineData(tornadoPossible_, + kTornadoPossiblePalettes_.at(phenomenon)); + } + + SetDefaultLineData(inactive_, kInactivePalettes_.at(phenomenon)); + } + + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + + static void SetDefaultLineData(LineSettings& lineSettings, + const LineData& lineData); + + awips::Phenomenon phenomenon_; + + std::map threatCategoryMap_ {}; + + LineSettings observed_ {"observed"}; + LineSettings tornadoPossible_ {"tornado_possible"}; + LineSettings inactive_ {"inactive"}; +}; + +AlertPaletteSettings::AlertPaletteSettings(awips::Phenomenon phenomenon) : + SettingsCategory(awips::GetPhenomenonCode(phenomenon)), + p(std::make_unique(phenomenon)) +{ + auto& info = awips::ibw::GetImpactBasedWarningInfo(p->phenomenon_); + for (auto& threatCategory : p->threatCategoryMap_) + { + RegisterSubcategory(threatCategory.second); + } + + if (info.hasObservedTag_) + { + RegisterSubcategory(p->observed_); + } + + if (info.hasTornadoPossibleTag_) + { + RegisterSubcategory(p->tornadoPossible_); + } + + RegisterSubcategory(p->inactive_); + + SetDefaults(); +} + +AlertPaletteSettings::~AlertPaletteSettings() = default; + +AlertPaletteSettings::AlertPaletteSettings(AlertPaletteSettings&&) noexcept = + default; +AlertPaletteSettings& +AlertPaletteSettings::operator=(AlertPaletteSettings&&) noexcept = default; + +LineSettings& AlertPaletteSettings::threat_category( + awips::ibw::ThreatCategory threatCategory) const +{ + auto it = p->threatCategoryMap_.find(threatCategory); + if (it != p->threatCategoryMap_.cend()) + { + return it->second; + } + return p->threatCategoryMap_.at(awips::ibw::ThreatCategory::Base); +} + +LineSettings& AlertPaletteSettings::inactive() const +{ + return p->inactive_; +} + +LineSettings& AlertPaletteSettings::observed() const +{ + return p->observed_; +} + +LineSettings& AlertPaletteSettings::tornado_possible() const +{ + return p->tornadoPossible_; +} + +void AlertPaletteSettings::Impl::SetDefaultLineData(LineSettings& lineSettings, + const LineData& lineData) +{ + lineSettings.border_color().SetDefault( + util::color::ToArgbString(lineData.borderColor_)); + lineSettings.highlight_color().SetDefault( + util::color::ToArgbString(lineData.highlightColor_)); + lineSettings.line_color().SetDefault( + util::color::ToArgbString(lineData.lineColor_)); + + lineSettings.border_width().SetDefault(lineData.borderWidth_); + lineSettings.highlight_width().SetDefault(lineData.highlightWidth_); + lineSettings.line_width().SetDefault(lineData.lineWidth_); +} + +bool operator==(const AlertPaletteSettings& lhs, + const AlertPaletteSettings& rhs) +{ + return (lhs.p->phenomenon_ == rhs.p->phenomenon_ && + lhs.p->threatCategoryMap_ == rhs.p->threatCategoryMap_ && + lhs.p->inactive_ == rhs.p->inactive_ && + lhs.p->observed_ == rhs.p->observed_ && + lhs.p->tornadoPossible_ == rhs.p->tornadoPossible_); +} + +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/alert_palette_settings.hpp b/scwx-qt/source/scwx/qt/settings/alert_palette_settings.hpp new file mode 100644 index 00000000..c4a12e58 --- /dev/null +++ b/scwx-qt/source/scwx/qt/settings/alert_palette_settings.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace scwx::qt::settings +{ + +class AlertPaletteSettings : public SettingsCategory +{ +public: + explicit AlertPaletteSettings(awips::Phenomenon phenomenon); + ~AlertPaletteSettings() override; + + AlertPaletteSettings(const AlertPaletteSettings&) = delete; + AlertPaletteSettings& operator=(const AlertPaletteSettings&) = delete; + + AlertPaletteSettings(AlertPaletteSettings&&) noexcept; + AlertPaletteSettings& operator=(AlertPaletteSettings&&) noexcept; + + [[nodiscard]] LineSettings& + threat_category(awips::ibw::ThreatCategory threatCategory) const; + [[nodiscard]] LineSettings& inactive() const; + [[nodiscard]] LineSettings& observed() const; + [[nodiscard]] LineSettings& tornado_possible() const; + + friend bool operator==(const AlertPaletteSettings& lhs, + const AlertPaletteSettings& rhs); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/audio_settings.cpp b/scwx-qt/source/scwx/qt/settings/audio_settings.cpp index a799793b..c4b07ff8 100644 --- a/scwx-qt/source/scwx/qt/settings/audio_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/audio_settings.cpp @@ -9,11 +9,7 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { static const std::string logPrefix_ = "scwx::qt::settings::audio_settings"; @@ -33,6 +29,8 @@ public: boost::to_lower(defaultAlertLocationMethodValue); + // SetDefault, SetMinimum and SetMaximum are descriptive + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) alertSoundFile_.SetDefault(defaultAlertSoundFileValue); alertLocationMethod_.SetDefault(defaultAlertLocationMethodValue); alertLatitude_.SetDefault(0.0); @@ -48,7 +46,7 @@ public: alertLongitude_.SetMaximum(180.0); alertRadius_.SetMinimum(0.0); alertRadius_.SetMaximum(9999999999); - + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) alertLocationMethod_.SetValidator( SCWX_SETTINGS_ENUM_VALIDATOR(types::LocationMethod, @@ -94,7 +92,11 @@ public: SettingsVariable {"alert_disabled"}); } - ~Impl() {} + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; SettingsVariable alertSoundFile_ {"alert_sound_file"}; SettingsVariable alertLocationMethod_ {"alert_location_method"}; @@ -208,6 +210,4 @@ bool operator==(const AudioSettings& lhs, const AudioSettings& rhs) lhs.p->alertEnabled_ == rhs.p->alertEnabled_); } -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/audio_settings.hpp b/scwx-qt/source/scwx/qt/settings/audio_settings.hpp index 579d3599..50606001 100644 --- a/scwx-qt/source/scwx/qt/settings/audio_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/audio_settings.hpp @@ -7,18 +7,14 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { class AudioSettings : public SettingsCategory { public: explicit AudioSettings(); - ~AudioSettings(); + ~AudioSettings() override; AudioSettings(const AudioSettings&) = delete; AudioSettings& operator=(const AudioSettings&) = delete; @@ -26,16 +22,17 @@ public: AudioSettings(AudioSettings&&) noexcept; AudioSettings& operator=(AudioSettings&&) noexcept; - SettingsVariable& alert_sound_file() const; - SettingsVariable& alert_location_method() const; - SettingsVariable& alert_latitude() const; - SettingsVariable& alert_longitude() const; - SettingsVariable& alert_radius() const; - SettingsVariable& alert_radar_site() const; - SettingsVariable& alert_county() const; - SettingsVariable& alert_wfo() const; - SettingsVariable& alert_enabled(awips::Phenomenon phenomenon) const; - SettingsVariable& ignore_missing_codecs() const; + [[nodiscard]] SettingsVariable& alert_sound_file() const; + [[nodiscard]] SettingsVariable& alert_location_method() const; + [[nodiscard]] SettingsVariable& alert_latitude() const; + [[nodiscard]] SettingsVariable& alert_longitude() const; + [[nodiscard]] SettingsVariable& alert_radius() const; + [[nodiscard]] SettingsVariable& alert_radar_site() const; + [[nodiscard]] SettingsVariable& alert_county() const; + [[nodiscard]] SettingsVariable& alert_wfo() const; + [[nodiscard]] SettingsVariable& + alert_enabled(awips::Phenomenon phenomenon) const; + [[nodiscard]] SettingsVariable& ignore_missing_codecs() const; static AudioSettings& Instance(); @@ -46,6 +43,4 @@ private: std::unique_ptr p; }; -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/general_settings.cpp b/scwx-qt/source/scwx/qt/settings/general_settings.cpp index fe3982e2..86ac3d6d 100644 --- a/scwx-qt/source/scwx/qt/settings/general_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/general_settings.cpp @@ -8,16 +8,10 @@ #include #include -#include - #include #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { static const std::string logPrefix_ = "scwx::qt::settings::general_settings"; @@ -50,7 +44,10 @@ public: boost::to_lower(defaultPositioningPlugin); boost::to_lower(defaultThemeValue); + // SetDefault, SetMinimum, and SetMaximum are descriptive + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) antiAliasingEnabled_.SetDefault(true); + centerOnRadarSelection_.SetDefault(false); clockFormat_.SetDefault(defaultClockFormatValue); customStyleDrawLayer_.SetDefault(".*\\.annotations\\.points"); debugEnabled_.SetDefault(false); @@ -69,14 +66,22 @@ public: nmeaBaudRate_.SetDefault(9600); nmeaSource_.SetDefault(""); positioningPlugin_.SetDefault(defaultPositioningPlugin); + processModuleWarningsEnabled_.SetDefault(true); showMapAttribution_.SetDefault(true); showMapCenter_.SetDefault(false); showMapLogo_.SetDefault(true); theme_.SetDefault(defaultThemeValue); + themeFile_.SetDefault(""); trackLocation_.SetDefault(false); updateNotificationsEnabled_.SetDefault(true); warningsProvider_.SetDefault(defaultWarningsProviderValue); + cursorIconAlwaysOn_.SetDefault(false); + radarSiteThreshold_.SetDefault(0.0); + highPrivilegeWarningEnabled_.SetDefault(true); + cursorIconScale_.SetDefault(1.0); + cursorIconScale_.SetMinimum(1.0); + cursorIconScale_.SetMaximum(5.0); fontSizes_.SetElementMinimum(1); fontSizes_.SetElementMaximum(72); fontSizes_.SetValidator([](const std::vector& value) @@ -90,9 +95,12 @@ public: loopSpeed_.SetMinimum(1.0); loopSpeed_.SetMaximum(99.99); loopTime_.SetMinimum(1); - loopTime_.SetMaximum(1440); + loopTime_.SetMaximum(2880); nmeaBaudRate_.SetMinimum(1); nmeaBaudRate_.SetMaximum(999999999); + radarSiteThreshold_.SetMinimum(-10000); + radarSiteThreshold_.SetMaximum(10000); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) customStyleDrawLayer_.SetTransform([](const std::string& value) { return boost::trim_copy(value); }); @@ -103,6 +111,10 @@ public: SCWX_SETTINGS_ENUM_VALIDATOR(scwx::util::ClockFormat, scwx::util::ClockFormatIterator(), scwx::util::GetClockFormatName)); + + customStyleUrl_.SetValidator( + [](const std::string& value) + { return value.find("key=") == std::string::npos; }); customStyleDrawLayer_.SetValidator([](const std::string& value) { return !value.empty(); }); defaultAlertAction_.SetValidator( @@ -134,9 +146,14 @@ public: { return QUrl {QString::fromStdString(value)}.isValid(); }); } - ~Impl() {} + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; - SettingsVariable antiAliasingEnabled_ {"anti_aliasing_enabled"}; + SettingsVariable antiAliasingEnabled_ {"anti_aliasing_enabled"}; + SettingsVariable centerOnRadarSelection_ {"center_on_radar_selection"}; SettingsVariable clockFormat_ {"clock_format"}; SettingsVariable customStyleDrawLayer_ { "custom_style_draw_layer"}; @@ -157,19 +174,28 @@ public: SettingsVariable nmeaBaudRate_ {"nmea_baud_rate"}; SettingsVariable nmeaSource_ {"nmea_source"}; SettingsVariable positioningPlugin_ {"positioning_plugin"}; - SettingsVariable showMapAttribution_ {"show_map_attribution"}; - SettingsVariable showMapCenter_ {"show_map_center"}; - SettingsVariable showMapLogo_ {"show_map_logo"}; - SettingsVariable theme_ {"theme"}; - SettingsVariable trackLocation_ {"track_location"}; + SettingsVariable processModuleWarningsEnabled_ { + "process_module_warnings_enabled"}; + SettingsVariable showMapAttribution_ {"show_map_attribution"}; + SettingsVariable showMapCenter_ {"show_map_center"}; + SettingsVariable showMapLogo_ {"show_map_logo"}; + SettingsVariable theme_ {"theme"}; + SettingsVariable themeFile_ {"theme_file"}; + SettingsVariable trackLocation_ {"track_location"}; SettingsVariable updateNotificationsEnabled_ {"update_notifications"}; SettingsVariable warningsProvider_ {"warnings_provider"}; + SettingsVariable cursorIconAlwaysOn_ {"cursor_icon_always_on"}; + SettingsVariable radarSiteThreshold_ {"radar_site_threshold"}; + SettingsVariable highPrivilegeWarningEnabled_ { + "high_privilege_warning_enabled"}; + SettingsVariable cursorIconScale_ {"cursor_icon_scale"}; }; GeneralSettings::GeneralSettings() : SettingsCategory("general"), p(std::make_unique()) { RegisterVariables({&p->antiAliasingEnabled_, + &p->centerOnRadarSelection_, &p->clockFormat_, &p->customStyleDrawLayer_, &p->customStyleUrl_, @@ -189,13 +215,19 @@ GeneralSettings::GeneralSettings() : &p->nmeaBaudRate_, &p->nmeaSource_, &p->positioningPlugin_, + &p->processModuleWarningsEnabled_, &p->showMapAttribution_, &p->showMapCenter_, &p->showMapLogo_, &p->theme_, + &p->themeFile_, &p->trackLocation_, &p->updateNotificationsEnabled_, - &p->warningsProvider_}); + &p->warningsProvider_, + &p->cursorIconAlwaysOn_, + &p->radarSiteThreshold_, + &p->highPrivilegeWarningEnabled_, + &p->cursorIconScale_}); SetDefaults(); } GeneralSettings::~GeneralSettings() = default; @@ -209,6 +241,11 @@ SettingsVariable& GeneralSettings::anti_aliasing_enabled() const return p->antiAliasingEnabled_; } +SettingsVariable& GeneralSettings::center_on_radar_selection() const +{ + return p->centerOnRadarSelection_; +} + SettingsVariable& GeneralSettings::clock_format() const { return p->clockFormat_; @@ -305,6 +342,11 @@ SettingsVariable& GeneralSettings::positioning_plugin() const return p->positioningPlugin_; } +SettingsVariable& GeneralSettings::process_module_warnings_enabled() const +{ + return p->processModuleWarningsEnabled_; +} + SettingsVariable& GeneralSettings::show_map_attribution() const { return p->showMapAttribution_; @@ -325,6 +367,11 @@ SettingsVariable& GeneralSettings::theme() const return p->theme_; } +SettingsVariable& GeneralSettings::theme_file() const +{ + return p->themeFile_; +} + SettingsVariable& GeneralSettings::track_location() const { return p->trackLocation_; @@ -340,6 +387,26 @@ SettingsVariable& GeneralSettings::warnings_provider() const return p->warningsProvider_; } +SettingsVariable& GeneralSettings::cursor_icon_always_on() const +{ + return p->cursorIconAlwaysOn_; +} + +SettingsVariable& GeneralSettings::radar_site_threshold() const +{ + return p->radarSiteThreshold_; +} + +SettingsVariable& GeneralSettings::high_privilege_warning_enabled() const +{ + return p->highPrivilegeWarningEnabled_; +} + +SettingsVariable& GeneralSettings::cursor_icon_scale() const +{ + return p->cursorIconScale_; +} + bool GeneralSettings::Shutdown() { bool dataChanged = false; @@ -348,7 +415,9 @@ bool GeneralSettings::Shutdown() dataChanged |= p->loopDelay_.Commit(); dataChanged |= p->loopSpeed_.Commit(); dataChanged |= p->loopTime_.Commit(); + dataChanged |= p->processModuleWarningsEnabled_.Commit(); dataChanged |= p->trackLocation_.Commit(); + dataChanged |= p->highPrivilegeWarningEnabled_.Commit(); return dataChanged; } @@ -362,6 +431,7 @@ GeneralSettings& GeneralSettings::Instance() bool operator==(const GeneralSettings& lhs, const GeneralSettings& rhs) { return (lhs.p->antiAliasingEnabled_ == rhs.p->antiAliasingEnabled_ && + lhs.p->centerOnRadarSelection_ == rhs.p->centerOnRadarSelection_ && lhs.p->clockFormat_ == rhs.p->clockFormat_ && lhs.p->customStyleDrawLayer_ == rhs.p->customStyleDrawLayer_ && lhs.p->customStyleUrl_ == rhs.p->customStyleUrl_ && @@ -381,16 +451,22 @@ bool operator==(const GeneralSettings& lhs, const GeneralSettings& rhs) lhs.p->nmeaBaudRate_ == rhs.p->nmeaBaudRate_ && lhs.p->nmeaSource_ == rhs.p->nmeaSource_ && lhs.p->positioningPlugin_ == rhs.p->positioningPlugin_ && + lhs.p->processModuleWarningsEnabled_ == + rhs.p->processModuleWarningsEnabled_ && lhs.p->showMapAttribution_ == rhs.p->showMapAttribution_ && lhs.p->showMapCenter_ == rhs.p->showMapCenter_ && lhs.p->showMapLogo_ == rhs.p->showMapLogo_ && lhs.p->theme_ == rhs.p->theme_ && + lhs.p->themeFile_ == rhs.p->themeFile_ && lhs.p->trackLocation_ == rhs.p->trackLocation_ && lhs.p->updateNotificationsEnabled_ == rhs.p->updateNotificationsEnabled_ && - lhs.p->warningsProvider_ == rhs.p->warningsProvider_); + lhs.p->warningsProvider_ == rhs.p->warningsProvider_ && + lhs.p->cursorIconAlwaysOn_ == rhs.p->cursorIconAlwaysOn_ && + lhs.p->radarSiteThreshold_ == rhs.p->radarSiteThreshold_ && + lhs.p->highPrivilegeWarningEnabled_ == + rhs.p->highPrivilegeWarningEnabled_ && + lhs.p->cursorIconScale_ == rhs.p->cursorIconScale_); } -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/general_settings.hpp b/scwx-qt/source/scwx/qt/settings/general_settings.hpp index 2628aef1..59f29275 100644 --- a/scwx-qt/source/scwx/qt/settings/general_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/general_settings.hpp @@ -6,18 +6,14 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { class GeneralSettings : public SettingsCategory { public: explicit GeneralSettings(); - ~GeneralSettings(); + ~GeneralSettings() override; GeneralSettings(const GeneralSettings&) = delete; GeneralSettings& operator=(const GeneralSettings&) = delete; @@ -25,33 +21,42 @@ public: GeneralSettings(GeneralSettings&&) noexcept; GeneralSettings& operator=(GeneralSettings&&) noexcept; - SettingsVariable& anti_aliasing_enabled() const; - SettingsVariable& clock_format() const; - SettingsVariable& custom_style_draw_layer() const; - SettingsVariable& custom_style_url() const; - SettingsVariable& debug_enabled() const; - SettingsVariable& default_alert_action() const; - SettingsVariable& default_radar_site() const; - SettingsVariable& default_time_zone() const; - SettingsContainer>& font_sizes() const; - SettingsVariable& grid_height() const; - SettingsVariable& grid_width() const; - SettingsVariable& loop_delay() const; - SettingsVariable& loop_speed() const; - SettingsVariable& loop_time() const; - SettingsVariable& map_provider() const; - SettingsVariable& mapbox_api_key() const; - SettingsVariable& maptiler_api_key() const; - SettingsVariable& nmea_baud_rate() const; - SettingsVariable& nmea_source() const; - SettingsVariable& positioning_plugin() const; - SettingsVariable& show_map_attribution() const; - SettingsVariable& show_map_center() const; - SettingsVariable& show_map_logo() const; - SettingsVariable& theme() const; - SettingsVariable& track_location() const; - SettingsVariable& update_notifications_enabled() const; - SettingsVariable& warnings_provider() const; + [[nodiscard]] SettingsVariable& anti_aliasing_enabled() const; + [[nodiscard]] SettingsVariable& center_on_radar_selection() const; + [[nodiscard]] SettingsVariable& clock_format() const; + [[nodiscard]] SettingsVariable& custom_style_draw_layer() const; + [[nodiscard]] SettingsVariable& custom_style_url() const; + [[nodiscard]] SettingsVariable& debug_enabled() const; + [[nodiscard]] SettingsVariable& default_alert_action() const; + [[nodiscard]] SettingsVariable& default_radar_site() const; + [[nodiscard]] SettingsVariable& default_time_zone() const; + [[nodiscard]] SettingsContainer>& + font_sizes() const; + [[nodiscard]] SettingsVariable& grid_height() const; + [[nodiscard]] SettingsVariable& grid_width() const; + [[nodiscard]] SettingsVariable& loop_delay() const; + [[nodiscard]] SettingsVariable& loop_speed() const; + [[nodiscard]] SettingsVariable& loop_time() const; + [[nodiscard]] SettingsVariable& map_provider() const; + [[nodiscard]] SettingsVariable& mapbox_api_key() const; + [[nodiscard]] SettingsVariable& maptiler_api_key() const; + [[nodiscard]] SettingsVariable& nmea_baud_rate() const; + [[nodiscard]] SettingsVariable& nmea_source() const; + [[nodiscard]] SettingsVariable& positioning_plugin() const; + [[nodiscard]] SettingsVariable& + process_module_warnings_enabled() const; + [[nodiscard]] SettingsVariable& show_map_attribution() const; + [[nodiscard]] SettingsVariable& show_map_center() const; + [[nodiscard]] SettingsVariable& show_map_logo() const; + [[nodiscard]] SettingsVariable& theme() const; + [[nodiscard]] SettingsVariable& theme_file() const; + [[nodiscard]] SettingsVariable& track_location() const; + [[nodiscard]] SettingsVariable& update_notifications_enabled() const; + [[nodiscard]] SettingsVariable& warnings_provider() const; + [[nodiscard]] SettingsVariable& cursor_icon_always_on() const; + [[nodiscard]] SettingsVariable& radar_site_threshold() const; + [[nodiscard]] SettingsVariable& high_privilege_warning_enabled() const; + [[nodiscard]] SettingsVariable& cursor_icon_scale() const; static GeneralSettings& Instance(); @@ -65,6 +70,4 @@ private: std::unique_ptr p; }; -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/hotkey_settings.cpp b/scwx-qt/source/scwx/qt/settings/hotkey_settings.cpp index 0edaf840..39f21379 100644 --- a/scwx-qt/source/scwx/qt/settings/hotkey_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/hotkey_settings.cpp @@ -2,16 +2,13 @@ #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { static const std::string logPrefix_ = "scwx::qt::settings::hotkey_settings"; static const std::unordered_map kDefaultHotkeys_ { + {types::Hotkey::AddLocationMarker, QKeySequence {Qt::Key::Key_M}}, {types::Hotkey::ChangeMapStyle, QKeySequence {Qt::Key::Key_Z}}, {types::Hotkey::CopyCursorCoordinates, QKeySequence {QKeyCombination {Qt::KeyboardModifier::ControlModifier, @@ -28,6 +25,12 @@ static const std::unordered_map kDefaultHotkeys_ { {types::Hotkey::MapRotateCounterclockwise, QKeySequence {Qt::Key::Key_Q}}, {types::Hotkey::MapZoomIn, QKeySequence {Qt::Key::Key_Equal}}, {types::Hotkey::MapZoomOut, QKeySequence {Qt::Key::Key_Minus}}, + {types::Hotkey::ProductCategoryNext, + QKeySequence {QKeyCombination {Qt::KeyboardModifier::ControlModifier, + Qt::Key::Key_BracketRight}}}, + {types::Hotkey::ProductCategoryPrevious, + QKeySequence {QKeyCombination {Qt::KeyboardModifier::ControlModifier, + Qt::Key::Key_BracketLeft}}}, {types::Hotkey::ProductTiltDecrease, QKeySequence {Qt::Key::Key_BracketLeft}}, {types::Hotkey::ProductTiltIncrease, @@ -93,7 +96,11 @@ public: SettingsVariable {"?"}); } - ~Impl() {} + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; std::unordered_map> hotkey_ {}; std::vector variables_ {}; @@ -141,6 +148,4 @@ static bool IsHotkeyValid(const std::string& value) .toStdString() == value; } -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/hotkey_settings.hpp b/scwx-qt/source/scwx/qt/settings/hotkey_settings.hpp index 9fa56cc4..13e56b7d 100644 --- a/scwx-qt/source/scwx/qt/settings/hotkey_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/hotkey_settings.hpp @@ -7,18 +7,14 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { class HotkeySettings : public SettingsCategory { public: explicit HotkeySettings(); - ~HotkeySettings(); + ~HotkeySettings() override; HotkeySettings(const HotkeySettings&) = delete; HotkeySettings& operator=(const HotkeySettings&) = delete; @@ -26,7 +22,8 @@ public: HotkeySettings(HotkeySettings&&) noexcept; HotkeySettings& operator=(HotkeySettings&&) noexcept; - SettingsVariable& hotkey(scwx::qt::types::Hotkey hotkey) const; + [[nodiscard]] SettingsVariable& + hotkey(scwx::qt::types::Hotkey hotkey) const; static HotkeySettings& Instance(); @@ -37,6 +34,4 @@ private: std::unique_ptr p; }; -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/line_settings.cpp b/scwx-qt/source/scwx/qt/settings/line_settings.cpp new file mode 100644 index 00000000..2b013105 --- /dev/null +++ b/scwx-qt/source/scwx/qt/settings/line_settings.cpp @@ -0,0 +1,157 @@ +#include +#include + +namespace scwx::qt::settings +{ + +static const std::string logPrefix_ = "scwx::qt::settings::line_settings"; + +static const boost::gil::rgba8_pixel_t kTransparentColor_ {0, 0, 0, 0}; +static const std::string kTransparentColorString_ { + util::color::ToArgbString(kTransparentColor_)}; + +static const boost::gil::rgba8_pixel_t kBlackColor_ {0, 0, 0, 255}; +static const std::string kBlackColorString_ { + util::color::ToArgbString(kBlackColor_)}; + +static const boost::gil::rgba8_pixel_t kWhiteColor_ {255, 255, 255, 255}; +static const std::string kWhiteColorString_ { + util::color::ToArgbString(kWhiteColor_)}; + +class LineSettings::Impl +{ +public: + explicit Impl() + { + // SetDefault, SetMinimum, and SetMaximum are descriptive + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + lineColor_.SetDefault(kWhiteColorString_); + highlightColor_.SetDefault(kTransparentColorString_); + borderColor_.SetDefault(kBlackColorString_); + + lineWidth_.SetDefault(3); + highlightWidth_.SetDefault(0); + borderWidth_.SetDefault(1); + + lineWidth_.SetMinimum(1); + highlightWidth_.SetMinimum(0); + borderWidth_.SetMinimum(0); + + lineWidth_.SetMaximum(9); + highlightWidth_.SetMaximum(9); + borderWidth_.SetMaximum(9); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + + lineColor_.SetValidator(&util::color::ValidateArgbString); + highlightColor_.SetValidator(&util::color::ValidateArgbString); + borderColor_.SetValidator(&util::color::ValidateArgbString); + } + + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + + SettingsVariable lineColor_ {"line_color"}; + SettingsVariable highlightColor_ {"highlight_color"}; + SettingsVariable borderColor_ {"border_color"}; + + SettingsVariable lineWidth_ {"line_width"}; + SettingsVariable highlightWidth_ {"highlight_width"}; + SettingsVariable borderWidth_ {"border_width"}; +}; + +LineSettings::LineSettings(const std::string& name) : + SettingsCategory(name), p(std::make_unique()) +{ + RegisterVariables({&p->lineColor_, + &p->highlightColor_, + &p->borderColor_, + &p->lineWidth_, + &p->highlightWidth_, + &p->borderWidth_}); + SetDefaults(); +} +LineSettings::~LineSettings() = default; + +LineSettings::LineSettings(LineSettings&&) noexcept = default; +LineSettings& LineSettings::operator=(LineSettings&&) noexcept = default; + +SettingsVariable& LineSettings::border_color() const +{ + return p->borderColor_; +} + +SettingsVariable& LineSettings::highlight_color() const +{ + return p->highlightColor_; +} + +SettingsVariable& LineSettings::line_color() const +{ + return p->lineColor_; +} + +SettingsVariable& LineSettings::border_width() const +{ + return p->borderWidth_; +} + +SettingsVariable& LineSettings::highlight_width() const +{ + return p->highlightWidth_; +} + +SettingsVariable& LineSettings::line_width() const +{ + return p->lineWidth_; +} + +boost::gil::rgba32f_pixel_t LineSettings::GetBorderColorRgba32f() const +{ + return util::color::ToRgba32fPixelT(p->borderColor_.GetValue()); +} + +boost::gil::rgba32f_pixel_t LineSettings::GetHighlightColorRgba32f() const +{ + return util::color::ToRgba32fPixelT(p->highlightColor_.GetValue()); +} + +boost::gil::rgba32f_pixel_t LineSettings::GetLineColorRgba32f() const +{ + return util::color::ToRgba32fPixelT(p->lineColor_.GetValue()); +} + +void LineSettings::StageValues(boost::gil::rgba8_pixel_t borderColor, + boost::gil::rgba8_pixel_t highlightColor, + boost::gil::rgba8_pixel_t lineColor, + std::int64_t borderWidth, + std::int64_t highlightWidth, + std::int64_t lineWidth) +{ + set_block_signals(true); + + p->borderColor_.StageValue(util::color::ToArgbString(borderColor)); + p->highlightColor_.StageValue(util::color::ToArgbString(highlightColor)); + p->lineColor_.StageValue(util::color::ToArgbString(lineColor)); + p->borderWidth_.StageValue(borderWidth); + p->highlightWidth_.StageValue(highlightWidth); + p->lineWidth_.StageValue(lineWidth); + + set_block_signals(false); + + staged_signal()(); +} + +bool operator==(const LineSettings& lhs, const LineSettings& rhs) +{ + return (lhs.p->borderColor_ == rhs.p->borderColor_ && + lhs.p->highlightColor_ == rhs.p->highlightColor_ && + lhs.p->lineColor_ == rhs.p->lineColor_ && + lhs.p->borderWidth_ == rhs.p->borderWidth_ && + lhs.p->highlightWidth_ == rhs.p->highlightWidth_ && + lhs.p->lineWidth_ == rhs.p->lineWidth_); +} + +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/line_settings.hpp b/scwx-qt/source/scwx/qt/settings/line_settings.hpp new file mode 100644 index 00000000..3f13c601 --- /dev/null +++ b/scwx-qt/source/scwx/qt/settings/line_settings.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +namespace scwx::qt::settings +{ + +class LineSettings : public SettingsCategory +{ +public: + explicit LineSettings(const std::string& name); + ~LineSettings() override; + + LineSettings(const LineSettings&) = delete; + LineSettings& operator=(const LineSettings&) = delete; + + LineSettings(LineSettings&&) noexcept; + LineSettings& operator=(LineSettings&&) noexcept; + + [[nodiscard]] SettingsVariable& border_color() const; + [[nodiscard]] SettingsVariable& highlight_color() const; + [[nodiscard]] SettingsVariable& line_color() const; + + [[nodiscard]] SettingsVariable& border_width() const; + [[nodiscard]] SettingsVariable& highlight_width() const; + [[nodiscard]] SettingsVariable& line_width() const; + + [[nodiscard]] boost::gil::rgba32f_pixel_t GetBorderColorRgba32f() const; + [[nodiscard]] boost::gil::rgba32f_pixel_t GetHighlightColorRgba32f() const; + [[nodiscard]] boost::gil::rgba32f_pixel_t GetLineColorRgba32f() const; + + void StageValues(boost::gil::rgba8_pixel_t borderColor, + boost::gil::rgba8_pixel_t highlightColor, + boost::gil::rgba8_pixel_t lineColor, + std::int64_t borderWidth, + std::int64_t highlightWidth, + std::int64_t lineWidth); + + friend bool operator==(const LineSettings& lhs, const LineSettings& rhs); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/map_settings.cpp b/scwx-qt/source/scwx/qt/settings/map_settings.cpp index 2d8e93b0..76c09d30 100644 --- a/scwx-qt/source/scwx/qt/settings/map_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/map_settings.cpp @@ -6,15 +6,10 @@ #include #include -#include #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { static const std::string logPrefix_ = "scwx::qt::settings::map_settings"; @@ -27,14 +22,15 @@ static const std::string kMapStyleName_ {"map_style"}; static const std::string kRadarSiteName_ {"radar_site"}; static const std::string kRadarProductGroupName_ {"radar_product_group"}; static const std::string kRadarProductName_ {"radar_product"}; +static const std::string kSmoothingEnabledName_ {"smoothing_enabled"}; -static const std::string kDefaultMapStyle_ {"?"}; -static constexpr common::RadarProductGroup kDefaultRadarProductGroup_ = - common::RadarProductGroup::Level3; +static const std::string kDefaultMapStyle_ {"?"}; static const std::string kDefaultRadarProductGroupString_ = "L3"; static const std::array kDefaultRadarProduct_ { "N0B", "N0G", "N0C", "N0X"}; +static constexpr bool kDefaultSmoothingEnabled_ {false}; + class MapSettings::Impl { public: @@ -45,26 +41,28 @@ public: SettingsVariable radarProductGroup_ { kRadarProductGroupName_}; SettingsVariable radarProduct_ {kRadarProductName_}; + SettingsVariable smoothingEnabled_ {kSmoothingEnabledName_}; }; explicit Impl() { for (std::size_t i = 0; i < kCount_; i++) { - map_[i].mapStyle_.SetDefault(kDefaultMapStyle_); - map_[i].radarSite_.SetDefault(kDefaultRadarSite_); - map_[i].radarProductGroup_.SetDefault( + map_.at(i).mapStyle_.SetDefault(kDefaultMapStyle_); + map_.at(i).radarSite_.SetDefault(kDefaultRadarSite_); + map_.at(i).radarProductGroup_.SetDefault( kDefaultRadarProductGroupString_); - map_[i].radarProduct_.SetDefault(kDefaultRadarProduct_[i]); + map_.at(i).radarProduct_.SetDefault(kDefaultRadarProduct_.at(i)); + map_.at(i).smoothingEnabled_.SetDefault(kDefaultSmoothingEnabled_); - map_[i].radarSite_.SetValidator( + map_.at(i).radarSite_.SetValidator( [](const std::string& value) { // Radar site must exist return config::RadarSite::Get(value) != nullptr; }); - map_[i].radarProductGroup_.SetValidator( + map_.at(i).radarProductGroup_.SetValidator( [](const std::string& value) { // Radar product group must be valid @@ -73,12 +71,12 @@ public: return radarProductGroup != common::RadarProductGroup::Unknown; }); - map_[i].radarProduct_.SetValidator( + map_.at(i).radarProduct_.SetValidator( [this, i](const std::string& value) { common::RadarProductGroup radarProductGroup = common::GetRadarProductGroup( - map_[i].radarProductGroup_.GetValue()); + map_.at(i).radarProductGroup_.GetStagedOrValue()); if (radarProductGroup == common::RadarProductGroup::Level2) { @@ -94,21 +92,27 @@ public: }); variables_.insert(variables_.cend(), - {&map_[i].mapStyle_, - &map_[i].radarSite_, - &map_[i].radarProductGroup_, - &map_[i].radarProduct_}); + {&map_.at(i).mapStyle_, + &map_.at(i).radarSite_, + &map_.at(i).radarProductGroup_, + &map_.at(i).radarProduct_, + &map_.at(i).smoothingEnabled_}); } } - ~Impl() {} + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; void SetDefaults(std::size_t i) { - map_[i].mapStyle_.SetValueToDefault(); - map_[i].radarSite_.SetValueToDefault(); - map_[i].radarProductGroup_.SetValueToDefault(); - map_[i].radarProduct_.SetValueToDefault(); + map_.at(i).mapStyle_.SetValueToDefault(); + map_.at(i).radarSite_.SetValueToDefault(); + map_.at(i).radarProductGroup_.SetValueToDefault(); + map_.at(i).radarProduct_.SetValueToDefault(); + map_.at(i).smoothingEnabled_.SetValueToDefault(); } friend void tag_invoke(boost::json::value_from_tag, @@ -118,7 +122,8 @@ public: jv = {{kMapStyleName_, data.mapStyle_.GetValue()}, {kRadarSiteName_, data.radarSite_.GetValue()}, {kRadarProductGroupName_, data.radarProductGroup_.GetValue()}, - {kRadarProductName_, data.radarProduct_.GetValue()}}; + {kRadarProductName_, data.radarProduct_.GetValue()}, + {kSmoothingEnabledName_, data.smoothingEnabled_.GetValue()}}; } friend bool operator==(const MapData& lhs, const MapData& rhs) @@ -126,7 +131,8 @@ public: return (lhs.mapStyle_ == rhs.mapStyle_ && // lhs.radarSite_ == rhs.radarSite_ && lhs.radarProductGroup_ == rhs.radarProductGroup_ && - lhs.radarProduct_ == rhs.radarProduct_); + lhs.radarProduct_ == rhs.radarProduct_ && + lhs.smoothingEnabled_ == rhs.smoothingEnabled_); } std::array map_ {}; @@ -151,25 +157,29 @@ std::size_t MapSettings::count() const return kCount_; } -SettingsVariable& MapSettings::map_style(std::size_t i) const +SettingsVariable& MapSettings::map_style(std::size_t i) { - return p->map_[i].mapStyle_; + return p->map_.at(i).mapStyle_; } -SettingsVariable& MapSettings::radar_site(std::size_t i) const +SettingsVariable& MapSettings::radar_site(std::size_t i) { - return p->map_[i].radarSite_; + return p->map_.at(i).radarSite_; } -SettingsVariable& -MapSettings::radar_product_group(std::size_t i) const +SettingsVariable& MapSettings::radar_product_group(std::size_t i) { - return p->map_[i].radarProductGroup_; + return p->map_.at(i).radarProductGroup_; } -SettingsVariable& MapSettings::radar_product(std::size_t i) const +SettingsVariable& MapSettings::radar_product(std::size_t i) { - return p->map_[i].radarProduct_; + return p->map_.at(i).radarProduct_; +} + +SettingsVariable& MapSettings::smoothing_enabled(std::size_t i) +{ + return p->map_.at(i).smoothingEnabled_; } bool MapSettings::Shutdown() @@ -179,9 +189,12 @@ bool MapSettings::Shutdown() // Commit settings that are managed separate from the settings dialog for (std::size_t i = 0; i < kCount_; ++i) { - Impl::MapData& mapRecordSettings = p->map_[i]; + Impl::MapData& mapRecordSettings = p->map_.at(i); dataChanged |= mapRecordSettings.mapStyle_.Commit(); + dataChanged |= mapRecordSettings.smoothingEnabled_.Commit(); + dataChanged |= mapRecordSettings.radarProductGroup_.Commit(); + dataChanged |= mapRecordSettings.radarProduct_.Commit(); } return dataChanged; @@ -202,13 +215,15 @@ bool MapSettings::ReadJson(const boost::json::object& json) if (i < mapArray.size() && mapArray.at(i).is_object()) { const boost::json::object& mapRecord = mapArray.at(i).as_object(); - Impl::MapData& mapRecordSettings = p->map_[i]; + Impl::MapData& mapRecordSettings = p->map_.at(i); // Load JSON Elements validated &= mapRecordSettings.mapStyle_.ReadValue(mapRecord); validated &= mapRecordSettings.radarSite_.ReadValue(mapRecord); validated &= mapRecordSettings.radarProductGroup_.ReadValue(mapRecord); + validated &= + mapRecordSettings.smoothingEnabled_.ReadValue(mapRecord); bool productValidated = mapRecordSettings.radarProduct_.ReadValue(mapRecord); @@ -263,6 +278,4 @@ bool operator==(const MapSettings& lhs, const MapSettings& rhs) return (lhs.p->map_ == rhs.p->map_); } -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/map_settings.hpp b/scwx-qt/source/scwx/qt/settings/map_settings.hpp index c8726491..9ffb3540 100644 --- a/scwx-qt/source/scwx/qt/settings/map_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/map_settings.hpp @@ -6,18 +6,14 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { class MapSettings : public SettingsCategory { public: explicit MapSettings(); - ~MapSettings(); + ~MapSettings() override; MapSettings(const MapSettings&) = delete; MapSettings& operator=(const MapSettings&) = delete; @@ -25,11 +21,12 @@ public: MapSettings(MapSettings&&) noexcept; MapSettings& operator=(MapSettings&&) noexcept; - std::size_t count() const; - SettingsVariable& map_style(std::size_t i) const; - SettingsVariable& radar_site(std::size_t i) const; - SettingsVariable& radar_product_group(std::size_t i) const; - SettingsVariable& radar_product(std::size_t i) const; + [[nodiscard]] std::size_t count() const; + SettingsVariable& map_style(std::size_t i); + SettingsVariable& radar_site(std::size_t i); + SettingsVariable& radar_product_group(std::size_t i); + SettingsVariable& radar_product(std::size_t i); + SettingsVariable& smoothing_enabled(std::size_t i); bool Shutdown(); @@ -59,6 +56,4 @@ private: std::unique_ptr p; }; -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/palette_settings.cpp b/scwx-qt/source/scwx/qt/settings/palette_settings.cpp index 9d9c93fa..0b88b687 100644 --- a/scwx-qt/source/scwx/qt/settings/palette_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/palette_settings.cpp @@ -2,15 +2,11 @@ #include #include +#include #include #include -#include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { static const std::string logPrefix_ = "scwx::qt::settings::palette_settings"; @@ -76,59 +72,24 @@ static const awips::Phenomenon kDefaultPhenomenon_ {awips::Phenomenon::Marine}; class PaletteSettings::Impl { public: - explicit Impl() + explicit Impl(PaletteSettings* self) : self_ {self} { - palette_.reserve(kPaletteKeys_.size()); - - for (const auto& name : kPaletteKeys_) - { - const std::string& defaultValue = kDefaultPalettes_.at(name); - - auto result = - palette_.emplace(name, SettingsVariable {name}); - - SettingsVariable& settingsVariable = result.first->second; - - settingsVariable.SetDefault(defaultValue); - - variables_.push_back(&settingsVariable); - }; - - activeAlertColor_.reserve(kAlertColors_.size()); - inactiveAlertColor_.reserve(kAlertColors_.size()); - - for (auto& alert : kAlertColors_) - { - std::string phenomenonCode = awips::GetPhenomenonCode(alert.first); - std::string activeName = fmt::format("{}-active", phenomenonCode); - std::string inactiveName = fmt::format("{}-inactive", phenomenonCode); - - auto activeResult = activeAlertColor_.emplace( - alert.first, SettingsVariable {activeName}); - auto inactiveResult = inactiveAlertColor_.emplace( - alert.first, SettingsVariable {inactiveName}); - - SettingsVariable& activeVariable = - activeResult.first->second; - SettingsVariable& inactiveVariable = - inactiveResult.first->second; - - activeVariable.SetDefault( - util::color::ToArgbString(alert.second.first)); - inactiveVariable.SetDefault( - util::color::ToArgbString(alert.second.second)); - - activeVariable.SetValidator(&ValidateColor); - inactiveVariable.SetValidator(&ValidateColor); - - variables_.push_back(&activeVariable); - variables_.push_back(&inactiveVariable); - } + InitializeColorTables(); + InitializeLegacyAlerts(); + InitializeAlerts(); } - ~Impl() {} + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; - static bool ValidateColor(const std::string& value); + void InitializeColorTables(); + void InitializeLegacyAlerts(); + void InitializeAlerts(); + + PaletteSettings* self_; std::unordered_map> palette_ {}; std::unordered_map> @@ -136,16 +97,13 @@ public: std::unordered_map> inactiveAlertColor_ {}; std::vector variables_ {}; + + std::unordered_map + alertPaletteMap_ {}; }; -bool PaletteSettings::Impl::ValidateColor(const std::string& value) -{ - static constexpr LazyRE2 re = {"#[0-9A-Fa-f]{8}"}; - return RE2::FullMatch(value, *re); -} - PaletteSettings::PaletteSettings() : - SettingsCategory("palette"), p(std::make_unique()) + SettingsCategory("palette"), p(std::make_unique(this)) { RegisterVariables(p->variables_); SetDefaults(); @@ -158,6 +116,75 @@ PaletteSettings::PaletteSettings(PaletteSettings&&) noexcept = default; PaletteSettings& PaletteSettings::operator=(PaletteSettings&&) noexcept = default; +void PaletteSettings::Impl::InitializeColorTables() +{ + palette_.reserve(kPaletteKeys_.size()); + + for (const auto& name : kPaletteKeys_) + { + const std::string& defaultValue = kDefaultPalettes_.at(name); + + auto result = + palette_.emplace(name, SettingsVariable {name}); + + SettingsVariable& settingsVariable = result.first->second; + + settingsVariable.SetDefault(defaultValue); + + variables_.push_back(&settingsVariable); + }; +} + +void PaletteSettings::Impl::InitializeLegacyAlerts() +{ + activeAlertColor_.reserve(kAlertColors_.size()); + inactiveAlertColor_.reserve(kAlertColors_.size()); + + for (auto& alert : kAlertColors_) + { + std::string phenomenonCode = awips::GetPhenomenonCode(alert.first); + std::string activeName = fmt::format("{}-active", phenomenonCode); + std::string inactiveName = fmt::format("{}-inactive", phenomenonCode); + + auto activeResult = activeAlertColor_.emplace( + alert.first, SettingsVariable {activeName}); + auto inactiveResult = inactiveAlertColor_.emplace( + alert.first, SettingsVariable {inactiveName}); + + SettingsVariable& activeVariable = + activeResult.first->second; + SettingsVariable& inactiveVariable = + inactiveResult.first->second; + + activeVariable.SetDefault(util::color::ToArgbString(alert.second.first)); + inactiveVariable.SetDefault( + util::color::ToArgbString(alert.second.second)); + + activeVariable.SetValidator(&util::color::ValidateArgbString); + inactiveVariable.SetValidator(&util::color::ValidateArgbString); + + variables_.push_back(&activeVariable); + variables_.push_back(&inactiveVariable); + } +} + +void PaletteSettings::Impl::InitializeAlerts() +{ + std::vector alertSettings {}; + + for (auto phenomenon : PaletteSettings::alert_phenomena()) + { + auto result = alertPaletteMap_.emplace(phenomenon, phenomenon); + auto& it = result.first; + AlertPaletteSettings& alertPaletteSettings = it->second; + + // Variable registration + alertSettings.push_back(&alertPaletteSettings); + } + + self_->RegisterSubcategoryArray("alerts", alertSettings); +} + SettingsVariable& PaletteSettings::palette(const std::string& name) const { @@ -194,6 +221,12 @@ PaletteSettings::alert_color(awips::Phenomenon phenomenon, bool active) const } } +AlertPaletteSettings& +PaletteSettings::alert_palette(awips::Phenomenon phenomenon) +{ + return p->alertPaletteMap_.at(phenomenon); +} + const std::vector& PaletteSettings::alert_phenomena() { static const std::vector kAlertPhenomena_ { @@ -219,6 +252,4 @@ bool operator==(const PaletteSettings& lhs, const PaletteSettings& rhs) lhs.p->inactiveAlertColor_ == rhs.p->inactiveAlertColor_); } -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/palette_settings.hpp b/scwx-qt/source/scwx/qt/settings/palette_settings.hpp index c0f7985a..c7147a30 100644 --- a/scwx-qt/source/scwx/qt/settings/palette_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/palette_settings.hpp @@ -1,24 +1,22 @@ #pragma once +#include #include #include +#include #include #include #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { class PaletteSettings : public SettingsCategory { public: explicit PaletteSettings(); - ~PaletteSettings(); + ~PaletteSettings() override; PaletteSettings(const PaletteSettings&) = delete; PaletteSettings& operator=(const PaletteSettings&) = delete; @@ -26,9 +24,11 @@ public: PaletteSettings(PaletteSettings&&) noexcept; PaletteSettings& operator=(PaletteSettings&&) noexcept; - SettingsVariable& palette(const std::string& name) const; - SettingsVariable& alert_color(awips::Phenomenon phenomenon, - bool active) const; + [[nodiscard]] SettingsVariable& + palette(const std::string& name) const; + [[nodiscard]] SettingsVariable& + alert_color(awips::Phenomenon phenomenon, bool active) const; + AlertPaletteSettings& alert_palette(awips::Phenomenon); static const std::vector& alert_phenomena(); @@ -42,6 +42,4 @@ private: std::unique_ptr p; }; -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/product_settings.cpp b/scwx-qt/source/scwx/qt/settings/product_settings.cpp index 3cf47ef7..1221d717 100644 --- a/scwx-qt/source/scwx/qt/settings/product_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/product_settings.cpp @@ -1,11 +1,9 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace settings +#include + +namespace scwx::qt::settings { static const std::string logPrefix_ = "scwx::qt::settings::product_settings"; @@ -15,12 +13,22 @@ class ProductSettings::Impl public: explicit Impl() { + // SetDefault, SetMinimum and SetMaximum are descriptive + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + showSmoothedRangeFolding_.SetDefault(false); stiForecastEnabled_.SetDefault(true); stiPastEnabled_.SetDefault(true); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) } - ~Impl() {} + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + SettingsVariable showSmoothedRangeFolding_ { + "show_smoothed_range_folding"}; SettingsVariable stiForecastEnabled_ {"sti_forecast_enabled"}; SettingsVariable stiPastEnabled_ {"sti_past_enabled"}; }; @@ -28,7 +36,9 @@ public: ProductSettings::ProductSettings() : SettingsCategory("product"), p(std::make_unique()) { - RegisterVariables({&p->stiForecastEnabled_, &p->stiPastEnabled_}); + RegisterVariables({&p->showSmoothedRangeFolding_, + &p->stiForecastEnabled_, + &p->stiPastEnabled_}); SetDefaults(); } ProductSettings::~ProductSettings() = default; @@ -37,12 +47,17 @@ ProductSettings::ProductSettings(ProductSettings&&) noexcept = default; ProductSettings& ProductSettings::operator=(ProductSettings&&) noexcept = default; -SettingsVariable& ProductSettings::sti_forecast_enabled() const +SettingsVariable& ProductSettings::show_smoothed_range_folding() +{ + return p->showSmoothedRangeFolding_; +} + +SettingsVariable& ProductSettings::sti_forecast_enabled() { return p->stiForecastEnabled_; } -SettingsVariable& ProductSettings::sti_past_enabled() const +SettingsVariable& ProductSettings::sti_past_enabled() { return p->stiPastEnabled_; } @@ -66,10 +81,10 @@ ProductSettings& ProductSettings::Instance() bool operator==(const ProductSettings& lhs, const ProductSettings& rhs) { - return (lhs.p->stiForecastEnabled_ == rhs.p->stiForecastEnabled_ && + return (lhs.p->showSmoothedRangeFolding_ == + rhs.p->showSmoothedRangeFolding_ && + lhs.p->stiForecastEnabled_ == rhs.p->stiForecastEnabled_ && lhs.p->stiPastEnabled_ == rhs.p->stiPastEnabled_); } -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/product_settings.hpp b/scwx-qt/source/scwx/qt/settings/product_settings.hpp index c7c09dd8..d267b127 100644 --- a/scwx-qt/source/scwx/qt/settings/product_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/product_settings.hpp @@ -4,20 +4,15 @@ #include #include -#include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { class ProductSettings : public SettingsCategory { public: explicit ProductSettings(); - ~ProductSettings(); + ~ProductSettings() override; ProductSettings(const ProductSettings&) = delete; ProductSettings& operator=(const ProductSettings&) = delete; @@ -25,8 +20,9 @@ public: ProductSettings(ProductSettings&&) noexcept; ProductSettings& operator=(ProductSettings&&) noexcept; - SettingsVariable& sti_forecast_enabled() const; - SettingsVariable& sti_past_enabled() const; + SettingsVariable& show_smoothed_range_folding(); + SettingsVariable& sti_forecast_enabled(); + SettingsVariable& sti_past_enabled(); static ProductSettings& Instance(); @@ -40,6 +36,4 @@ private: std::unique_ptr p; }; -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/settings_category.cpp b/scwx-qt/source/scwx/qt/settings/settings_category.cpp index e6c929f8..b0e1b416 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_category.cpp +++ b/scwx-qt/source/scwx/qt/settings/settings_category.cpp @@ -4,11 +4,7 @@ #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { static const std::string logPrefix_ = "scwx::qt::settings::settings_category"; @@ -19,13 +15,27 @@ class SettingsCategory::Impl public: explicit Impl(const std::string& name) : name_ {name} {} - ~Impl() {} + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + + void ConnectSubcategory(SettingsCategory& category); + void ConnectVariable(SettingsVariableBase* variable); const std::string name_; std::vector>> subcategoryArrays_; + std::vector subcategories_; std::vector variables_; + + boost::signals2::signal changedSignal_ {}; + boost::signals2::signal stagedSignal_ {}; + bool blockSignals_ {false}; + + std::vector connections_ {}; }; SettingsCategory::SettingsCategory(const std::string& name) : @@ -38,13 +48,88 @@ SettingsCategory::SettingsCategory(SettingsCategory&&) noexcept = default; SettingsCategory& SettingsCategory::operator=(SettingsCategory&&) noexcept = default; +bool SettingsCategory::IsDefault() const +{ + bool isDefault = true; + + // Get subcategory array defaults + for (auto& subcategoryArray : p->subcategoryArrays_) + { + for (auto& subcategory : subcategoryArray.second) + { + isDefault = isDefault && subcategory->IsDefault(); + } + } + + // Get subcategory defaults + for (auto& subcategory : p->subcategories_) + { + isDefault = isDefault && subcategory->IsDefault(); + } + + // Get variable defaults + for (auto& variable : p->variables_) + { + isDefault = isDefault && variable->IsDefault(); + } + + return isDefault; +} + +bool SettingsCategory::IsDefaultStaged() const +{ + bool isDefaultStaged = true; + + // Get subcategory array defaults + for (auto& subcategoryArray : p->subcategoryArrays_) + { + for (auto& subcategory : subcategoryArray.second) + { + isDefaultStaged = isDefaultStaged && subcategory->IsDefaultStaged(); + } + } + + // Get subcategory defaults + for (auto& subcategory : p->subcategories_) + { + isDefaultStaged = isDefaultStaged && subcategory->IsDefaultStaged(); + } + + // Get variable defaults + for (auto& variable : p->variables_) + { + isDefaultStaged = isDefaultStaged && variable->IsDefaultStaged(); + } + + return isDefaultStaged; +} + std::string SettingsCategory::name() const { return p->name_; } +boost::signals2::signal& SettingsCategory::changed_signal() +{ + return p->changedSignal_; +} + +boost::signals2::signal& SettingsCategory::staged_signal() +{ + return p->stagedSignal_; +} + +void SettingsCategory::set_block_signals(bool blockSignals) +{ + p->blockSignals_ = blockSignals; +} + void SettingsCategory::SetDefaults() { + // Don't allow individual variables to invoke the signal when operating over + // the entire category + p->blockSignals_ = true; + // Set subcategory array defaults for (auto& subcategoryArray : p->subcategoryArrays_) { @@ -54,11 +139,129 @@ void SettingsCategory::SetDefaults() } } + // Set subcategory defaults + for (auto& subcategory : p->subcategories_) + { + subcategory->SetDefaults(); + } + // Set variable defaults for (auto& variable : p->variables_) { variable->SetValueToDefault(); } + + // Unblock signals + p->blockSignals_ = false; + + p->changedSignal_(); + p->stagedSignal_(); +} + +void SettingsCategory::StageDefaults() +{ + // Don't allow individual variables to invoke the signal when operating over + // the entire category + p->blockSignals_ = true; + + // Stage subcategory array defaults + for (auto& subcategoryArray : p->subcategoryArrays_) + { + for (auto& subcategory : subcategoryArray.second) + { + subcategory->StageDefaults(); + } + } + + // Stage subcategory defaults + for (auto& subcategory : p->subcategories_) + { + subcategory->StageDefaults(); + } + + // Stage variable defaults + for (auto& variable : p->variables_) + { + variable->StageDefault(); + } + + // Unblock signals + p->blockSignals_ = false; + + p->stagedSignal_(); +} + +bool SettingsCategory::Commit() +{ + bool committed = false; + + // Don't allow individual variables to invoke the signal when operating over + // the entire category + p->blockSignals_ = true; + + // Commit subcategory arrays + for (auto& subcategoryArray : p->subcategoryArrays_) + { + for (auto& subcategory : subcategoryArray.second) + { + committed |= subcategory->Commit(); + } + } + + // Commit subcategories + for (auto& subcategory : p->subcategories_) + { + committed |= subcategory->Commit(); + } + + // Commit variables + for (auto& variable : p->variables_) + { + committed |= variable->Commit(); + } + + // Unblock signals + p->blockSignals_ = false; + + if (committed) + { + p->changedSignal_(); + } + + return committed; +} + +void SettingsCategory::Reset() +{ + // Don't allow individual variables to invoke the signal when operating over + // the entire category + p->blockSignals_ = true; + + // Reset subcategory arrays + for (auto& subcategoryArray : p->subcategoryArrays_) + { + for (auto& subcategory : subcategoryArray.second) + { + subcategory->Reset(); + } + } + + // Reset subcategories + for (auto& subcategory : p->subcategories_) + { + subcategory->Reset(); + } + + // Reset variables + for (auto& variable : p->variables_) + { + variable->Reset(); + } + + // Unblock signals + p->blockSignals_ = false; + + p->stagedSignal_(); } bool SettingsCategory::ReadJson(const boost::json::object& json) @@ -111,6 +314,12 @@ bool SettingsCategory::ReadJson(const boost::json::object& json) } } + // Read subcategories + for (auto& subcategory : p->subcategories_) + { + validated &= subcategory->ReadJson(object); + } + // Read variables for (auto& variable : p->variables_) { @@ -154,6 +363,12 @@ void SettingsCategory::WriteJson(boost::json::object& json) const object.insert_or_assign(subcategoryArray.first, arrayObject); } + // Write subcategories + for (auto& subcategory : p->subcategories_) + { + subcategory->WriteJson(object); + } + // Write variables for (auto& variable : p->variables_) { @@ -163,6 +378,12 @@ void SettingsCategory::WriteJson(boost::json::object& json) const json.insert_or_assign(p->name_, object); } +void SettingsCategory::RegisterSubcategory(SettingsCategory& subcategory) +{ + p->ConnectSubcategory(subcategory); + p->subcategories_.push_back(&subcategory); +} + void SettingsCategory::RegisterSubcategoryArray( const std::string& name, std::vector& subcategories) { @@ -172,22 +393,90 @@ void SettingsCategory::RegisterSubcategoryArray( std::transform(subcategories.begin(), subcategories.end(), std::back_inserter(newSubcategories.second), - [](SettingsCategory& subcategory) { return &subcategory; }); + [this](SettingsCategory& subcategory) + { + p->ConnectSubcategory(subcategory); + return &subcategory; + }); +} + +void SettingsCategory::RegisterSubcategoryArray( + const std::string& name, std::vector& subcategories) +{ + auto& newSubcategories = p->subcategoryArrays_.emplace_back( + name, std::vector {}); + + std::transform(subcategories.begin(), + subcategories.end(), + std::back_inserter(newSubcategories.second), + [this](SettingsCategory* subcategory) + { + p->ConnectSubcategory(*subcategory); + return subcategory; + }); } void SettingsCategory::RegisterVariables( std::initializer_list variables) { + for (auto& variable : variables) + { + p->ConnectVariable(variable); + } p->variables_.insert(p->variables_.end(), variables); } void SettingsCategory::RegisterVariables( std::vector variables) { + for (auto& variable : variables) + { + p->ConnectVariable(variable); + } p->variables_.insert( p->variables_.end(), variables.cbegin(), variables.cend()); } -} // namespace settings -} // namespace qt -} // namespace scwx +void SettingsCategory::Impl::ConnectSubcategory(SettingsCategory& category) +{ + connections_.emplace_back(category.changed_signal().connect( + [this]() + { + if (!blockSignals_) + { + changedSignal_(); + } + })); + + connections_.emplace_back(category.staged_signal().connect( + [this]() + { + if (!blockSignals_) + { + stagedSignal_(); + } + })); +} + +void SettingsCategory::Impl::ConnectVariable(SettingsVariableBase* variable) +{ + connections_.emplace_back(variable->changed_signal().connect( + [this]() + { + if (!blockSignals_) + { + changedSignal_(); + } + })); + + connections_.emplace_back(variable->staged_signal().connect( + [this]() + { + if (!blockSignals_) + { + stagedSignal_(); + } + })); +} + +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/settings_category.hpp b/scwx-qt/source/scwx/qt/settings/settings_category.hpp index d7c86abd..c47bfdeb 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_category.hpp +++ b/scwx-qt/source/scwx/qt/settings/settings_category.hpp @@ -6,19 +6,16 @@ #include #include +#include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { class SettingsCategory { public: explicit SettingsCategory(const std::string& name); - ~SettingsCategory(); + virtual ~SettingsCategory(); SettingsCategory(const SettingsCategory&) = delete; SettingsCategory& operator=(const SettingsCategory&) = delete; @@ -26,13 +23,63 @@ public: SettingsCategory(SettingsCategory&&) noexcept; SettingsCategory& operator=(SettingsCategory&&) noexcept; - std::string name() const; + [[nodiscard]] std::string name() const; + + /** + * Gets the signal invoked when a variable within the category is changed. + * + * @return Changed signal + */ + boost::signals2::signal& changed_signal(); + + /** + * Gets the signal invoked when a variable within the category is staged. + * + * @return Staged signal + */ + boost::signals2::signal& staged_signal(); + + /** + * Gets whether or not all settings variables are currently set to default + * values. + * + * @return true if all settings variables are currently set to default + * values, otherwise false. + */ + [[nodiscard]] bool IsDefault() const; + + /** + * Gets whether or not all settings variables currently have staged values + * set to default. + * + * @return true if all settings variables currently have staged values set + * to default, otherwise false. + */ + [[nodiscard]] bool IsDefaultStaged() const; /** * Set all variables to their defaults. */ void SetDefaults(); + /** + * Stage all variables to their defaults. + */ + void StageDefaults(); + + /** + * Sets the current value of all variables to the staged value. + * + * @return true if any staged value was committed, false if no staged values + * are present. + */ + bool Commit(); + + /** + * Clears the staged value of all variables. + */ + void Reset(); + /** * Reads the variables from the JSON object. * @@ -50,17 +97,21 @@ public: */ virtual void WriteJson(boost::json::object& json) const; + void RegisterSubcategory(SettingsCategory& subcategory); void RegisterSubcategoryArray(const std::string& name, std::vector& subcategories); + void RegisterSubcategoryArray(const std::string& name, + std::vector& subcategories); void RegisterVariables(std::initializer_list variables); void RegisterVariables(std::vector variables); +protected: + void set_block_signals(bool blockSignals); + private: class Impl; std::unique_ptr p; }; -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/settings_container.cpp b/scwx-qt/source/scwx/qt/settings/settings_container.cpp index 1eb5b06a..df0d7e41 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_container.cpp +++ b/scwx-qt/source/scwx/qt/settings/settings_container.cpp @@ -1,13 +1,7 @@ -#define SETTINGS_CONTAINER_IMPLEMENTATION - #include #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { static const std::string logPrefix_ = "scwx::qt::settings::settings_container"; @@ -17,9 +11,13 @@ template class SettingsContainer::Impl { public: - explicit Impl() {} + explicit Impl() = default; - ~Impl() {} + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; T elementDefault_ {}; std::optional elementMinimum_ {}; @@ -172,6 +170,6 @@ bool SettingsContainer::Equals(const SettingsVariableBase& o) const p->elementMaximum_ == v.p->elementMaximum_; } -} // namespace settings -} // namespace qt -} // namespace scwx +template class SettingsContainer>; + +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/settings_container.hpp b/scwx-qt/source/scwx/qt/settings/settings_container.hpp index b35201c6..0e378a81 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_container.hpp +++ b/scwx-qt/source/scwx/qt/settings/settings_container.hpp @@ -2,11 +2,7 @@ #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { template @@ -92,17 +88,12 @@ public: void SetElementValidator(std::function validator); protected: - virtual bool Equals(const SettingsVariableBase& o) const override; + [[nodiscard]] virtual bool + Equals(const SettingsVariableBase& o) const override; private: class Impl; std::unique_ptr p; }; -#ifdef SETTINGS_CONTAINER_IMPLEMENTATION -template class SettingsContainer>; -#endif - -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/settings_interface.cpp b/scwx-qt/source/scwx/qt/settings/settings_interface.cpp index 9a24c5ae..e2c280b2 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_interface.cpp +++ b/scwx-qt/source/scwx/qt/settings/settings_interface.cpp @@ -1,5 +1,3 @@ -#define SETTINGS_INTERFACE_IMPLEMENTATION - #include #include #include @@ -14,16 +12,17 @@ #include #include #include +#include +#include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { static const std::string logPrefix_ = "scwx::qt::settings::settings_interface"; +static const QString kValidStyleSheet_ = ""; +static const QString kInvalidStyleSheet_ = "border: 2px solid red;"; + template class SettingsInterface::Impl { @@ -33,7 +32,11 @@ public: context_->moveToThread(QCoreApplication::instance()->thread()); } - ~Impl() {} + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; template void SetWidgetText(U* widget, const T& currentValue); @@ -41,6 +44,7 @@ public: void UpdateEditWidget(); void UpdateResetButton(); void UpdateUnitLabel(); + void UpdateValidityDisplay(); SettingsInterface* self_; @@ -58,6 +62,10 @@ public: double unitScale_ {1}; std::optional unitAbbreviation_ {}; bool unitEnabled_ {false}; + + bool trimmingEnabled_ {false}; + + std::optional invalidTooltip_; }; template @@ -171,14 +179,13 @@ void SettingsInterface::SetEditWidget(QWidget* widget) p->context_.get(), [this](const QKeySequence& sequence) { - std::string value { + const std::string value { sequence.toString().toStdString()}; // Attempt to stage the value p->stagedValid_ = p->variable_->StageValue(value); p->UpdateResetButton(); - - // TODO: Display invalid status + p->UpdateValidityDisplay(); }); } } @@ -193,8 +200,11 @@ void SettingsInterface::SetEditWidget(QWidget* widget) p->context_.get(), [this](const QString& text) { + const QString trimmedText = + p->trimmingEnabled_ ? text.trimmed() : text; + // Map to value if required - std::string value {text.toStdString()}; + std::string value {trimmedText.toStdString()}; if (p->mapToValue_ != nullptr) { value = p->mapToValue_(value); @@ -203,8 +213,7 @@ void SettingsInterface::SetEditWidget(QWidget* widget) // Attempt to stage the value p->stagedValid_ = p->variable_->StageValue(value); p->UpdateResetButton(); - - // TODO: Display invalid status + p->UpdateValidityDisplay(); }); } else if constexpr (std::is_same_v) @@ -217,8 +226,8 @@ void SettingsInterface::SetEditWidget(QWidget* widget) [this](const QString& text) { // Convert to a double - bool ok; - double value = text.toDouble(&ok); + bool ok = false; + const double value = text.toDouble(&ok); if (ok) { // Attempt to stage the value @@ -231,6 +240,8 @@ void SettingsInterface::SetEditWidget(QWidget* widget) p->stagedValid_ = false; p->UpdateResetButton(); } + + p->UpdateValidityDisplay(); }); } else if constexpr (std::is_same_v>) @@ -275,8 +286,7 @@ void SettingsInterface::SetEditWidget(QWidget* widget) // Attempt to stage the value p->stagedValid_ = p->variable_->StageValue(value); p->UpdateResetButton(); - - // TODO: Display invalid status + p->UpdateValidityDisplay(); }); } } @@ -360,6 +370,8 @@ void SettingsInterface::SetEditWidget(QWidget* widget) p->UpdateResetButton(); } // Otherwise, don't process an unchanged value + + p->UpdateValidityDisplay(); }); } } @@ -411,6 +423,8 @@ void SettingsInterface::SetEditWidget(QWidget* widget) p->UpdateResetButton(); } // Otherwise, don't process an unchanged value + + p->UpdateValidityDisplay(); }); } } @@ -490,6 +504,19 @@ void SettingsInterface::SetUnit(const double& scale, p->UpdateUnitLabel(); } +template +void SettingsInterface::EnableTrimming(bool trimmingEnabled) +{ + p->trimmingEnabled_ = trimmingEnabled; +} + +template +void SettingsInterface::SetInvalidTooltip( + const std::optional& tooltip) +{ + p->invalidTooltip_ = std::move(tooltip); +} + template template void SettingsInterface::Impl::SetWidgetText(U* widget, const T& currentValue) @@ -607,6 +634,15 @@ void SettingsInterface::Impl::UpdateUnitLabel() unitLabel_->setText(QString::fromStdString(unitAbbreviation_.value_or(""))); } +template +void SettingsInterface::Impl::UpdateValidityDisplay() +{ + editWidget_->setStyleSheet(stagedValid_ ? kValidStyleSheet_ : + kInvalidStyleSheet_); + editWidget_->setToolTip( + invalidTooltip_ && !stagedValid_ ? invalidTooltip_->c_str() : ""); +} + template void SettingsInterface::Impl::UpdateResetButton() { @@ -616,6 +652,12 @@ void SettingsInterface::Impl::UpdateResetButton() } } -} // namespace settings -} // namespace qt -} // namespace scwx +template class SettingsInterface; +template class SettingsInterface; +template class SettingsInterface; +template class SettingsInterface; + +// Containers are not to be used directly +template class SettingsInterface>; + +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/settings_interface.hpp b/scwx-qt/source/scwx/qt/settings/settings_interface.hpp index b049dcc1..6bf586a7 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_interface.hpp +++ b/scwx-qt/source/scwx/qt/settings/settings_interface.hpp @@ -1,19 +1,15 @@ #pragma once +#include #include #include #include #include -#include class QLabel; -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { template @@ -121,26 +117,30 @@ public: /** * Sets the unit to be used by this setting. * - * @param scale The radio of the current unit to the base unit - * @param abbreviation The abreviation to be displayed + * @param scale The ratio of the current unit to the base unit + * @param abbreviation The abbreviation to be displayed */ void SetUnit(const double& scale, const std::string& abbreviation); + /** + * Enables or disables whitespace trimming of text input. + * Removes whitespace ('\t', '\n', '\v', '\f', '\r', and ' ') at the + * beginning and end of the string from a QLineEdit. + * + * @param trimmingEnabled If trimming should be enabled. + */ + void EnableTrimming(bool trimmingEnabled = true); + + /** + * Set a tooltip to be displayed when an invalid input is given. + * + * @param tooltip the tooltip to be displayed + */ + void SetInvalidTooltip(const std::optional& tooltip); + private: class Impl; std::unique_ptr p; }; -#ifdef SETTINGS_INTERFACE_IMPLEMENTATION -template class SettingsInterface; -template class SettingsInterface; -template class SettingsInterface; -template class SettingsInterface; - -// Containers are not to be used directly -template class SettingsInterface>; -#endif - -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/settings_interface_base.cpp b/scwx-qt/source/scwx/qt/settings/settings_interface_base.cpp index 37e1ec25..e7060573 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_interface_base.cpp +++ b/scwx-qt/source/scwx/qt/settings/settings_interface_base.cpp @@ -2,11 +2,7 @@ #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { static const std::string logPrefix_ = @@ -15,8 +11,12 @@ static const std::string logPrefix_ = class SettingsInterfaceBase::Impl { public: - explicit Impl() {} - ~Impl() {} + explicit Impl() = default; + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; }; SettingsInterfaceBase::SettingsInterfaceBase() : p(std::make_unique()) {} @@ -29,6 +29,4 @@ SettingsInterfaceBase::SettingsInterfaceBase(SettingsInterfaceBase&&) noexcept = SettingsInterfaceBase& SettingsInterfaceBase::operator=(SettingsInterfaceBase&&) noexcept = default; -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/settings_interface_base.hpp b/scwx-qt/source/scwx/qt/settings/settings_interface_base.hpp index d0dc2ff2..16e89469 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_interface_base.hpp +++ b/scwx-qt/source/scwx/qt/settings/settings_interface_base.hpp @@ -5,11 +5,7 @@ class QAbstractButton; class QWidget; -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { class SettingsInterfaceBase @@ -70,6 +66,4 @@ private: std::unique_ptr p; }; -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/settings_variable.cpp b/scwx-qt/source/scwx/qt/settings/settings_variable.cpp index 1a7160f5..9682cbe4 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_variable.cpp +++ b/scwx-qt/source/scwx/qt/settings/settings_variable.cpp @@ -1,5 +1,3 @@ -#define SETTINGS_VARIABLE_IMPLEMENTATION - #include #include @@ -7,12 +5,9 @@ #include #include #include +#include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { static const std::string logPrefix_ = "scwx::qt::settings::settings_variable"; @@ -22,8 +17,12 @@ template class SettingsVariable::Impl { public: - explicit Impl() {} - ~Impl() {} + explicit Impl() = default; + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; T value_ {}; T default_ {}; @@ -67,6 +66,18 @@ inline auto FormatParameter(const T& value) } } +template +bool SettingsVariable::IsDefault() const +{ + return p->value_ == p->default_; +} + +template +bool SettingsVariable::IsDefaultStaged() const +{ + return p->staged_.value_or(p->value_) == p->default_; +} + template T SettingsVariable::GetValue() const { @@ -83,10 +94,13 @@ bool SettingsVariable::SetValue(const T& value) p->value_ = (p->transform_ != nullptr) ? p->transform_(value) : value; validated = true; + changed_signal()(); for (auto& callback : p->valueChangedCallbackFunctions_) { callback.second(p->value_); } + + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(p->value_); @@ -131,10 +145,13 @@ bool SettingsVariable::SetValueOrDefault(const T& value) p->value_ = p->default_; } + changed_signal()(); for (auto& callback : p->valueChangedCallbackFunctions_) { callback.second(p->value_); } + + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(p->value_); @@ -148,10 +165,13 @@ void SettingsVariable::SetValueToDefault() { p->value_ = p->default_; + changed_signal()(); for (auto& callback : p->valueChangedCallbackFunctions_) { callback.second(p->value_); } + + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(p->value_); @@ -170,6 +190,7 @@ void SettingsVariable::StageDefault() p->staged_.reset(); } + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(p->default_); @@ -196,6 +217,7 @@ bool SettingsVariable::StageValue(const T& value) validated = true; + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(transformed); @@ -216,10 +238,13 @@ bool SettingsVariable::Commit() p->staged_.reset(); committed = true; + changed_signal()(); for (auto& callback : p->valueChangedCallbackFunctions_) { callback.second(p->value_); } + + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(p->value_); @@ -234,6 +259,7 @@ void SettingsVariable::Reset() { p->staged_.reset(); + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(p->value_); @@ -338,10 +364,13 @@ bool SettingsVariable::ReadValue(const boost::json::object& json) p->value_ = p->default_; } + changed_signal()(); for (auto& callback : p->valueChangedCallbackFunctions_) { callback.second(p->value_); } + + staged_signal()(); for (auto& callback : p->valueStagedCallbackFunctions_) { callback.second(p->value_); @@ -402,6 +431,12 @@ bool SettingsVariable::Equals(const SettingsVariableBase& o) const p->maximum_ == v.p->maximum_; } -} // namespace settings -} // namespace qt -} // namespace scwx +template class SettingsVariable; +template class SettingsVariable; +template class SettingsVariable; +template class SettingsVariable; + +// Containers are not to be used directly +template class SettingsVariable>; + +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/settings_variable.hpp b/scwx-qt/source/scwx/qt/settings/settings_variable.hpp index 581ebcbe..3fc0b81d 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_variable.hpp +++ b/scwx-qt/source/scwx/qt/settings/settings_variable.hpp @@ -10,11 +10,7 @@ class QAbstractButton; class QWidget; -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { template @@ -29,7 +25,7 @@ public: typedef std::function ValueCallbackFunction; explicit SettingsVariable(const std::string& name); - ~SettingsVariable(); + virtual ~SettingsVariable(); SettingsVariable(const SettingsVariable&) = delete; SettingsVariable& operator=(const SettingsVariable&) = delete; @@ -37,6 +33,24 @@ public: SettingsVariable(SettingsVariable&&) noexcept; SettingsVariable& operator=(SettingsVariable&&) noexcept; + /** + * Gets whether or not the settings variable is currently set to its default + * value. + * + * @return true if the settings variable is currently set to its default + * value, otherwise false. + */ + bool IsDefault() const override; + + /** + * Gets whether or not the settings variable currently has its staged value + * set to default. + * + * @return true if the settings variable currently has its staged value set + * to default, otherwise false. + */ + bool IsDefaultStaged() const override; + /** * Gets the current value of the settings variable. * @@ -91,12 +105,12 @@ public: * @return true if the staged value was committed, false if no staged value * is present. */ - bool Commit(); + bool Commit() override; /** * Clears the staged value of the settings variable. */ - void Reset(); + void Reset() override; /** * Gets the staged value of the settings variable, if defined. @@ -232,23 +246,12 @@ public: void UnregisterValueStagedCallback(boost::uuids::uuid uuid); protected: - virtual bool Equals(const SettingsVariableBase& o) const override; + [[nodiscard]] virtual bool + Equals(const SettingsVariableBase& o) const override; private: class Impl; std::unique_ptr p; }; -#ifdef SETTINGS_VARIABLE_IMPLEMENTATION -template class SettingsVariable; -template class SettingsVariable; -template class SettingsVariable; -template class SettingsVariable; - -// Containers are not to be used directly -template class SettingsVariable>; -#endif - -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/settings_variable_base.cpp b/scwx-qt/source/scwx/qt/settings/settings_variable_base.cpp index 55ce72ea..f2941b9a 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_variable_base.cpp +++ b/scwx-qt/source/scwx/qt/settings/settings_variable_base.cpp @@ -1,10 +1,6 @@ #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { static const std::string logPrefix_ = @@ -15,9 +11,16 @@ class SettingsVariableBase::Impl public: explicit Impl(const std::string& name) : name_ {name} {} - ~Impl() {} + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; const std::string name_; + + boost::signals2::signal changedSignal_ {}; + boost::signals2::signal stagedSignal_ {}; }; SettingsVariableBase::SettingsVariableBase(const std::string& name) : @@ -38,6 +41,16 @@ std::string SettingsVariableBase::name() const return p->name_; } +boost::signals2::signal& SettingsVariableBase::changed_signal() +{ + return p->changedSignal_; +} + +boost::signals2::signal& SettingsVariableBase::staged_signal() +{ + return p->stagedSignal_; +} + bool SettingsVariableBase::Equals(const SettingsVariableBase& o) const { return p->name_ == o.p->name_; @@ -49,6 +62,4 @@ bool operator==(const SettingsVariableBase& lhs, return typeid(lhs) == typeid(rhs) && lhs.Equals(rhs); } -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/settings_variable_base.hpp b/scwx-qt/source/scwx/qt/settings/settings_variable_base.hpp index f0444f45..d613da91 100644 --- a/scwx-qt/source/scwx/qt/settings/settings_variable_base.hpp +++ b/scwx-qt/source/scwx/qt/settings/settings_variable_base.hpp @@ -4,12 +4,9 @@ #include #include +#include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { /** @@ -19,7 +16,7 @@ class SettingsVariableBase { protected: explicit SettingsVariableBase(const std::string& name); - ~SettingsVariableBase(); + virtual ~SettingsVariableBase(); public: SettingsVariableBase(const SettingsVariableBase&) = delete; @@ -28,7 +25,39 @@ public: SettingsVariableBase(SettingsVariableBase&&) noexcept; SettingsVariableBase& operator=(SettingsVariableBase&&) noexcept; - std::string name() const; + [[nodiscard]] std::string name() const; + + /** + * Gets the signal invoked when the settings variable is changed. + * + * @return Changed signal + */ + boost::signals2::signal& changed_signal(); + + /** + * Gets the signal invoked when the settings variable is staged. + * + * @return Staged signal + */ + boost::signals2::signal& staged_signal(); + + /** + * Gets whether or not the settings variable is currently set to its default + * value. + * + * @return true if the settings variable is currently set to its default + * value, otherwise false. + */ + [[nodiscard]] virtual bool IsDefault() const = 0; + + /** + * Gets whether or not the settings variable currently has its staged value + * set to default. + * + * @return true if the settings variable currently has its staged value set + * to default, otherwise false. + */ + [[nodiscard]] virtual bool IsDefaultStaged() const = 0; /** * Sets the current value of the settings variable to default. @@ -48,6 +77,11 @@ public: */ virtual bool Commit() = 0; + /** + * Clears the staged value of the settings variable. + */ + virtual void Reset() = 0; + /** * Reads the value from the JSON object. If the read value is out of range, * the value is set to the minimum or maximum. If the read value fails @@ -69,7 +103,7 @@ public: protected: friend bool operator==(const SettingsVariableBase& lhs, const SettingsVariableBase& rhs); - virtual bool Equals(const SettingsVariableBase& o) const; + [[nodiscard]] virtual bool Equals(const SettingsVariableBase& o) const; private: class Impl; @@ -79,6 +113,4 @@ private: bool operator==(const SettingsVariableBase& lhs, const SettingsVariableBase& rhs); -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/text_settings.cpp b/scwx-qt/source/scwx/qt/settings/text_settings.cpp index 942ad4f8..b66268e0 100644 --- a/scwx-qt/source/scwx/qt/settings/text_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/text_settings.cpp @@ -3,11 +3,7 @@ #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { static const std::string logPrefix_ = "scwx::qt::settings::text_settings"; @@ -50,12 +46,15 @@ public: boost::to_lower(defaultTooltipMethodValue); + // SetDefault, SetMinimum and SetMaximum are descriptive + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) hoverTextWrap_.SetDefault(80); hoverTextWrap_.SetMinimum(0); hoverTextWrap_.SetMaximum(999); placefileTextDropShadowEnabled_.SetDefault(true); radarSiteHoverTextEnabled_.SetDefault(true); tooltipMethod_.SetDefault(defaultTooltipMethodValue); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) tooltipMethod_.SetValidator( [](const std::string& value) @@ -81,7 +80,11 @@ public: InitializeFontVariables(); } - ~Impl() {} + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; void InitializeFontVariables(); @@ -141,8 +144,11 @@ void TextSettings::Impl::InitializeFontVariables() { return !value.empty(); }); // Font point size must be between 6 and 72 + // SetDefault, SetMinimum and SetMaximum are descriptive + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) font.fontPointSize_.SetMinimum(6.0); font.fontPointSize_.SetMaximum(72.0); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) // Variable registration auto& settings = fontSettings_.emplace_back( @@ -210,6 +216,4 @@ bool operator==(const TextSettings& lhs, const TextSettings& rhs) lhs.p->tooltipMethod_ == rhs.p->tooltipMethod_); } -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/text_settings.hpp b/scwx-qt/source/scwx/qt/settings/text_settings.hpp index 2be5ab13..593702fe 100644 --- a/scwx-qt/source/scwx/qt/settings/text_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/text_settings.hpp @@ -7,18 +7,14 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { class TextSettings : public SettingsCategory { public: explicit TextSettings(); - ~TextSettings(); + ~TextSettings() override; TextSettings(const TextSettings&) = delete; TextSettings& operator=(const TextSettings&) = delete; @@ -26,17 +22,18 @@ public: TextSettings(TextSettings&&) noexcept; TextSettings& operator=(TextSettings&&) noexcept; - SettingsVariable& + [[nodiscard]] SettingsVariable& font_family(types::FontCategory fontCategory) const; - SettingsVariable& + [[nodiscard]] SettingsVariable& font_style(types::FontCategory fontCategory) const; - SettingsVariable& + [[nodiscard]] SettingsVariable& font_point_size(types::FontCategory fontCategory) const; - SettingsVariable& hover_text_wrap() const; - SettingsVariable& placefile_text_drop_shadow_enabled() const; - SettingsVariable& radar_site_hover_text_enabled() const; - SettingsVariable& tooltip_method() const; + [[nodiscard]] SettingsVariable& hover_text_wrap() const; + [[nodiscard]] SettingsVariable& + placefile_text_drop_shadow_enabled() const; + [[nodiscard]] SettingsVariable& radar_site_hover_text_enabled() const; + [[nodiscard]] SettingsVariable& tooltip_method() const; static TextSettings& Instance(); @@ -48,6 +45,4 @@ private: std::unique_ptr p; }; -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/ui_settings.cpp b/scwx-qt/source/scwx/qt/settings/ui_settings.cpp index dc131d96..e4689120 100644 --- a/scwx-qt/source/scwx/qt/settings/ui_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/ui_settings.cpp @@ -1,10 +1,6 @@ #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { static const std::string logPrefix_ = "scwx::qt::settings::ui_settings"; @@ -14,20 +10,31 @@ class UiSettingsImpl public: explicit UiSettingsImpl() { + // SetDefault, SetMinimum and SetMaximum are descriptive + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) level2ProductsExpanded_.SetDefault(false); level2SettingsExpanded_.SetDefault(true); level3ProductsExpanded_.SetDefault(true); mapSettingsExpanded_.SetDefault(true); timelineExpanded_.SetDefault(true); + mainUIState_.SetDefault(""); + mainUIGeometry_.SetDefault(""); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) } - ~UiSettingsImpl() {} + ~UiSettingsImpl() = default; + UiSettingsImpl(const UiSettingsImpl&) = delete; + UiSettingsImpl& operator=(const UiSettingsImpl&) = delete; + UiSettingsImpl(const UiSettingsImpl&&) = delete; + UiSettingsImpl& operator=(const UiSettingsImpl&&) = delete; SettingsVariable level2ProductsExpanded_ {"level2_products_expanded"}; SettingsVariable level2SettingsExpanded_ {"level2_settings_expanded"}; SettingsVariable level3ProductsExpanded_ {"level3_products_expanded"}; SettingsVariable mapSettingsExpanded_ {"map_settings_expanded"}; SettingsVariable timelineExpanded_ {"timeline_expanded"}; + SettingsVariable mainUIState_ {"main_ui_state"}; + SettingsVariable mainUIGeometry_ {"main_ui_geometry"}; }; UiSettings::UiSettings() : @@ -37,7 +44,9 @@ UiSettings::UiSettings() : &p->level2SettingsExpanded_, &p->level3ProductsExpanded_, &p->mapSettingsExpanded_, - &p->timelineExpanded_}); + &p->timelineExpanded_, + &p->mainUIState_, + &p->mainUIGeometry_}); SetDefaults(); } UiSettings::~UiSettings() = default; @@ -70,6 +79,16 @@ SettingsVariable& UiSettings::timeline_expanded() const return p->timelineExpanded_; } +SettingsVariable& UiSettings::main_ui_state() const +{ + return p->mainUIState_; +} + +SettingsVariable& UiSettings::main_ui_geometry() const +{ + return p->mainUIGeometry_; +} + bool UiSettings::Shutdown() { bool dataChanged = false; @@ -80,6 +99,8 @@ bool UiSettings::Shutdown() dataChanged |= p->level3ProductsExpanded_.Commit(); dataChanged |= p->mapSettingsExpanded_.Commit(); dataChanged |= p->timelineExpanded_.Commit(); + dataChanged |= p->mainUIState_.Commit(); + dataChanged |= p->mainUIGeometry_.Commit(); return dataChanged; } @@ -96,9 +117,9 @@ bool operator==(const UiSettings& lhs, const UiSettings& rhs) lhs.p->level2SettingsExpanded_ == rhs.p->level2SettingsExpanded_ && lhs.p->level3ProductsExpanded_ == rhs.p->level3ProductsExpanded_ && lhs.p->mapSettingsExpanded_ == rhs.p->mapSettingsExpanded_ && - lhs.p->timelineExpanded_ == rhs.p->timelineExpanded_); + lhs.p->timelineExpanded_ == rhs.p->timelineExpanded_ && + lhs.p->mainUIState_ == rhs.p->mainUIState_ && + lhs.p->mainUIGeometry_ == rhs.p->mainUIGeometry_); } -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/ui_settings.hpp b/scwx-qt/source/scwx/qt/settings/ui_settings.hpp index e3045bcb..d8970c73 100644 --- a/scwx-qt/source/scwx/qt/settings/ui_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/ui_settings.hpp @@ -6,11 +6,7 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { class UiSettingsImpl; @@ -19,7 +15,7 @@ class UiSettings : public SettingsCategory { public: explicit UiSettings(); - ~UiSettings(); + ~UiSettings() override; UiSettings(const UiSettings&) = delete; UiSettings& operator=(const UiSettings&) = delete; @@ -27,11 +23,13 @@ public: UiSettings(UiSettings&&) noexcept; UiSettings& operator=(UiSettings&&) noexcept; - SettingsVariable& level2_products_expanded() const; - SettingsVariable& level2_settings_expanded() const; - SettingsVariable& level3_products_expanded() const; - SettingsVariable& map_settings_expanded() const; - SettingsVariable& timeline_expanded() const; + [[nodiscard]] SettingsVariable& level2_products_expanded() const; + [[nodiscard]] SettingsVariable& level2_settings_expanded() const; + [[nodiscard]] SettingsVariable& level3_products_expanded() const; + [[nodiscard]] SettingsVariable& map_settings_expanded() const; + [[nodiscard]] SettingsVariable& timeline_expanded() const; + [[nodiscard]] SettingsVariable& main_ui_state() const; + [[nodiscard]] SettingsVariable& main_ui_geometry() const; bool Shutdown(); @@ -43,6 +41,4 @@ private: std::unique_ptr p; }; -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/unit_settings.cpp b/scwx-qt/source/scwx/qt/settings/unit_settings.cpp index c2cb0f11..542ae153 100644 --- a/scwx-qt/source/scwx/qt/settings/unit_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/unit_settings.cpp @@ -4,11 +4,7 @@ #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { static const std::string logPrefix_ = "scwx::qt::settings::unit_settings"; @@ -35,11 +31,14 @@ public: boost::to_lower(defaultSpeedUnitsValue); boost::to_lower(defaultDistanceUnitsValue); + // SetDefault, SetMinimum and SetMaximum are descriptive + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) accumulationUnits_.SetDefault(defaultAccumulationUnitsValue); echoTopsUnits_.SetDefault(defaultEchoTopsUnitsValue); otherUnits_.SetDefault(defaultOtherUnitsValue); speedUnits_.SetDefault(defaultSpeedUnitsValue); distanceUnits_.SetDefault(defaultDistanceUnitsValue); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) accumulationUnits_.SetValidator( SCWX_SETTINGS_ENUM_VALIDATOR(types::AccumulationUnits, @@ -63,7 +62,11 @@ public: types::GetDistanceUnitsName)); } - ~Impl() {} + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; SettingsVariable accumulationUnits_ {"accumulation_units"}; SettingsVariable echoTopsUnits_ {"echo_tops_units"}; @@ -127,6 +130,4 @@ bool operator==(const UnitSettings& lhs, const UnitSettings& rhs) lhs.p->distanceUnits_ == rhs.p->distanceUnits_); } -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/settings/unit_settings.hpp b/scwx-qt/source/scwx/qt/settings/unit_settings.hpp index 15518492..a7d06eed 100644 --- a/scwx-qt/source/scwx/qt/settings/unit_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/unit_settings.hpp @@ -6,18 +6,14 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace settings +namespace scwx::qt::settings { class UnitSettings : public SettingsCategory { public: explicit UnitSettings(); - ~UnitSettings(); + ~UnitSettings() override; UnitSettings(const UnitSettings&) = delete; UnitSettings& operator=(const UnitSettings&) = delete; @@ -25,11 +21,11 @@ public: UnitSettings(UnitSettings&&) noexcept; UnitSettings& operator=(UnitSettings&&) noexcept; - SettingsVariable& accumulation_units() const; - SettingsVariable& echo_tops_units() const; - SettingsVariable& other_units() const; - SettingsVariable& speed_units() const; - SettingsVariable& distance_units() const; + [[nodiscard]] SettingsVariable& accumulation_units() const; + [[nodiscard]] SettingsVariable& echo_tops_units() const; + [[nodiscard]] SettingsVariable& other_units() const; + [[nodiscard]] SettingsVariable& speed_units() const; + [[nodiscard]] SettingsVariable& distance_units() const; static UnitSettings& Instance(); @@ -40,6 +36,4 @@ private: std::unique_ptr p; }; -} // namespace settings -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::settings diff --git a/scwx-qt/source/scwx/qt/types/hotkey_types.cpp b/scwx-qt/source/scwx/qt/types/hotkey_types.cpp index 8a4d0ee5..7a121079 100644 --- a/scwx-qt/source/scwx/qt/types/hotkey_types.cpp +++ b/scwx-qt/source/scwx/qt/types/hotkey_types.cpp @@ -13,6 +13,7 @@ namespace types { static const std::unordered_map hotkeyShortName_ { + {Hotkey::AddLocationMarker, "add_location_marker"}, {Hotkey::ChangeMapStyle, "change_map_style"}, {Hotkey::CopyCursorCoordinates, "copy_cursor_coordinates"}, {Hotkey::CopyMapCoordinates, "copy_map_coordinates"}, @@ -24,6 +25,8 @@ static const std::unordered_map hotkeyShortName_ { {Hotkey::MapRotateCounterclockwise, "map_rotate_counterclockwise"}, {Hotkey::MapZoomIn, "map_zoom_in"}, {Hotkey::MapZoomOut, "map_zoom_out"}, + {Hotkey::ProductCategoryNext, "product_category_next"}, + {Hotkey::ProductCategoryPrevious, "product_category_last"}, {Hotkey::ProductTiltDecrease, "product_tilt_decrease"}, {Hotkey::ProductTiltIncrease, "product_tilt_increase"}, {Hotkey::SelectLevel2Ref, "select_l2_ref"}, @@ -52,6 +55,7 @@ static const std::unordered_map hotkeyShortName_ { {Hotkey::Unknown, "?"}}; static const std::unordered_map hotkeyLongName_ { + {Hotkey::AddLocationMarker, "Add Location Marker"}, {Hotkey::ChangeMapStyle, "Change Map Style"}, {Hotkey::CopyCursorCoordinates, "Copy Cursor Coordinates"}, {Hotkey::CopyMapCoordinates, "Copy Map Coordinates"}, @@ -63,6 +67,8 @@ static const std::unordered_map hotkeyLongName_ { {Hotkey::MapRotateCounterclockwise, "Map Rotate Counterclockwise"}, {Hotkey::MapZoomIn, "Map Zoom In"}, {Hotkey::MapZoomOut, "Map Zoom Out"}, + {Hotkey::ProductCategoryNext, "Next Product in Category"}, + {Hotkey::ProductCategoryPrevious, "Previous Product in Category"}, {Hotkey::ProductTiltDecrease, "Product Tilt Decrease"}, {Hotkey::ProductTiltIncrease, "Product Tilt Increase"}, {Hotkey::SelectLevel2Ref, "Select L2 REF"}, diff --git a/scwx-qt/source/scwx/qt/types/hotkey_types.hpp b/scwx-qt/source/scwx/qt/types/hotkey_types.hpp index c2118a4f..6e770b67 100644 --- a/scwx-qt/source/scwx/qt/types/hotkey_types.hpp +++ b/scwx-qt/source/scwx/qt/types/hotkey_types.hpp @@ -13,6 +13,7 @@ namespace types enum class Hotkey { + AddLocationMarker, ChangeMapStyle, CopyCursorCoordinates, CopyMapCoordinates, @@ -24,6 +25,8 @@ enum class Hotkey MapRotateCounterclockwise, MapZoomIn, MapZoomOut, + ProductCategoryPrevious, + ProductCategoryNext, ProductTiltDecrease, ProductTiltIncrease, SelectLevel2Ref, @@ -52,7 +55,7 @@ enum class Hotkey Unknown }; typedef scwx::util:: - Iterator + Iterator HotkeyIterator; Hotkey GetHotkeyFromShortName(const std::string& name); diff --git a/scwx-qt/source/scwx/qt/types/imgui_font.cpp b/scwx-qt/source/scwx/qt/types/imgui_font.cpp index e6f22ad1..f2cab082 100644 --- a/scwx-qt/source/scwx/qt/types/imgui_font.cpp +++ b/scwx-qt/source/scwx/qt/types/imgui_font.cpp @@ -23,10 +23,8 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class ImGuiFont::Impl { public: - explicit Impl(const std::string& fontName, - const std::vector& fontData, - units::font_size::pixels size) : - fontName_ {fontName}, size_ {size} + explicit Impl(std::string fontName, const std::vector& fontData) : + fontName_ {std::move(fontName)} { CreateImGuiFont(fontData); } @@ -35,16 +33,14 @@ public: void CreateImGuiFont(const std::vector& fontData); - const std::string fontName_; - const units::font_size::pixels size_; + std::string fontName_; ImFont* imFont_ {nullptr}; }; -ImGuiFont::ImGuiFont(const std::string& fontName, - const std::vector& fontData, - units::font_size::pixels size) : - p(std::make_unique(fontName, fontData, size)) +ImGuiFont::ImGuiFont(const std::string& fontName, + const std::vector& fontData) : + p(std::make_unique(fontName, fontData)) { } ImGuiFont::~ImGuiFont() = default; @@ -53,11 +49,12 @@ void ImGuiFont::Impl::CreateImGuiFont(const std::vector& fontData) { logger_->debug("Creating Font: {}", fontName_); + // Default render size, used in debug widget + static constexpr float kSizePixels_ = 16.0f; + ImFontAtlas* fontAtlas = model::ImGuiContextModel::Instance().font_atlas(); ImFontConfig fontConfig {}; - const float sizePixels = static_cast(size_.value()); - // Do not transfer ownership of font data to ImGui, makes const_cast safe fontConfig.FontDataOwnedByAtlas = false; @@ -69,7 +66,7 @@ void ImGuiFont::Impl::CreateImGuiFont(const std::vector& fontData) const_cast(static_cast(fontData.data())), static_cast(std::clamp( fontData.size(), 0, std::numeric_limits::max())), - sizePixels, + kSizePixels_, &fontConfig); } diff --git a/scwx-qt/source/scwx/qt/types/imgui_font.hpp b/scwx-qt/source/scwx/qt/types/imgui_font.hpp index ace8ba09..d1c28a0c 100644 --- a/scwx-qt/source/scwx/qt/types/imgui_font.hpp +++ b/scwx-qt/source/scwx/qt/types/imgui_font.hpp @@ -4,8 +4,6 @@ #include #include -#include - struct ImFont; namespace scwx @@ -18,9 +16,8 @@ namespace types class ImGuiFont { public: - explicit ImGuiFont(const std::string& fontName, - const std::vector& fontData, - units::font_size::pixels size); + explicit ImGuiFont(const std::string& fontName, + const std::vector& fontData); ~ImGuiFont(); ImGuiFont(const ImGuiFont&) = delete; diff --git a/scwx-qt/source/scwx/qt/types/layer_types.cpp b/scwx-qt/source/scwx/qt/types/layer_types.cpp index 6e66c5d1..bd607cc7 100644 --- a/scwx-qt/source/scwx/qt/types/layer_types.cpp +++ b/scwx-qt/source/scwx/qt/types/layer_types.cpp @@ -31,6 +31,7 @@ static const std::unordered_map informationLayerName_ {{InformationLayer::MapOverlay, "Map Overlay"}, {InformationLayer::RadarSite, "Radar Sites"}, {InformationLayer::ColorTable, "Color Table"}, + {InformationLayer::Markers, "Location Markers"}, {InformationLayer::Unknown, "?"}}; static const std::unordered_map mapLayerName_ { diff --git a/scwx-qt/source/scwx/qt/types/layer_types.hpp b/scwx-qt/source/scwx/qt/types/layer_types.hpp index f0561a6e..bfc10839 100644 --- a/scwx-qt/source/scwx/qt/types/layer_types.hpp +++ b/scwx-qt/source/scwx/qt/types/layer_types.hpp @@ -44,6 +44,7 @@ enum class InformationLayer MapOverlay, RadarSite, ColorTable, + Markers, Unknown }; diff --git a/scwx-qt/source/scwx/qt/types/marker_types.hpp b/scwx-qt/source/scwx/qt/types/marker_types.hpp new file mode 100644 index 00000000..661aa207 --- /dev/null +++ b/scwx-qt/source/scwx/qt/types/marker_types.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include + +namespace scwx::qt::types +{ +using MarkerId = std::uint64_t; + +struct MarkerInfo +{ + MarkerInfo(std::string name, + double latitude, + double longitude, + std::string iconName, + const boost::gil::rgba8_pixel_t& iconColor) : + name {std::move(name)}, + latitude {latitude}, + longitude {longitude}, + iconName {std::move(iconName)}, + iconColor {iconColor} + { + } + + MarkerId id {0}; + std::string name; + double latitude; + double longitude; + std::string iconName; + boost::gil::rgba8_pixel_t iconColor; +}; + +struct MarkerIconInfo +{ + // Initializer for default icons (which use a texture) + explicit MarkerIconInfo(types::ImageTexture texture, + std::int32_t hotX, + std::int32_t hotY) : + name {types::GetTextureName(texture)}, + path {types::GetTexturePath(texture)}, + hotX {hotX}, + hotY {hotY}, + qIcon {QIcon(QString::fromStdString(path))}, + image {} + { + auto qName = QString::fromStdString(name); + QStringList parts = qName.split("location-"); + shortName = parts.last().toStdString(); + } + + // Initializer for custom icons (which use a file path) + explicit MarkerIconInfo(const std::string& path, + std::int32_t hotX, + std::int32_t hotY, + std::shared_ptr image) : + name {path}, + path {path}, + shortName {QFileInfo(path.c_str()).fileName().toStdString()}, + hotX {hotX}, + hotY {hotY}, + qIcon {QIcon(QString::fromStdString(path))}, + image {image} + { + } + + std::string name; + std::string path; + std::string shortName; + std::int32_t hotX; + std::int32_t hotY; + QIcon qIcon; + std::optional> image; +}; + +} // namespace scwx::qt::types diff --git a/scwx-qt/source/scwx/qt/types/qt_types.cpp b/scwx-qt/source/scwx/qt/types/qt_types.cpp index 37717646..0c10feb2 100644 --- a/scwx-qt/source/scwx/qt/types/qt_types.cpp +++ b/scwx-qt/source/scwx/qt/types/qt_types.cpp @@ -1,4 +1,5 @@ #include +#include #include @@ -9,27 +10,76 @@ namespace qt namespace types { +static const std::unordered_map qtStyleName_ { + {UiStyle::Default, "Default"}, + {UiStyle::Fusion, "Fusion"}, + {UiStyle::FusionLight, "Fusion"}, + {UiStyle::FusionDark, "Fusion"}, + {UiStyle::FusionAiry, "Fusion"}, + {UiStyle::FusionDarker, "Fusion"}, + {UiStyle::FusionDusk, "Fusion"}, + {UiStyle::FusionIaOra, "Fusion"}, + {UiStyle::FusionSand, "Fusion"}, + {UiStyle::FusionWaves, "Fusion"}, + {UiStyle::FusionCustom, "Fusion"}, + {UiStyle::Unknown, "?"}}; + static const std::unordered_map uiStyleName_ { {UiStyle::Default, "Default"}, {UiStyle::Fusion, "Fusion"}, + {UiStyle::FusionLight, "Fusion Light"}, + {UiStyle::FusionDark, "Fusion Dark"}, + {UiStyle::FusionAiry, "Fusion Airy"}, + {UiStyle::FusionDarker, "Fusion Darker"}, + {UiStyle::FusionDusk, "Fusion Dusk"}, + {UiStyle::FusionIaOra, "Fusion IA Ora"}, + {UiStyle::FusionSand, "Fusion Sand"}, + {UiStyle::FusionWaves, "Fusion Waves"}, + {UiStyle::FusionCustom, "Fusion Custom"}, {UiStyle::Unknown, "?"}}; -UiStyle GetUiStyle(const std::string& name) -{ - auto result = - std::find_if(uiStyleName_.cbegin(), - uiStyleName_.cend(), - [&](const std::pair& pair) -> bool - { return boost::iequals(pair.second, name); }); +static const std::unordered_map qtColorSchemeMap_ { + {UiStyle::Default, Qt::ColorScheme::Unknown}, + {UiStyle::Fusion, Qt::ColorScheme::Unknown}, + {UiStyle::FusionLight, Qt::ColorScheme::Light}, + {UiStyle::FusionDark, Qt::ColorScheme::Dark}, + {UiStyle::FusionAiry, Qt::ColorScheme::Unknown}, + {UiStyle::FusionDarker, Qt::ColorScheme::Unknown}, + {UiStyle::FusionDusk, Qt::ColorScheme::Unknown}, + {UiStyle::FusionIaOra, Qt::ColorScheme::Unknown}, + {UiStyle::FusionSand, Qt::ColorScheme::Unknown}, + {UiStyle::FusionWaves, Qt::ColorScheme::Unknown}, + {UiStyle::FusionCustom, Qt::ColorScheme::Unknown}, + {UiStyle::Unknown, Qt::ColorScheme::Unknown}}; - if (result != uiStyleName_.cend()) +static const std::unordered_map paletteFile_ { + {UiStyle::FusionAiry, ":res/qt6ct_colors/airy.conf"}, + {UiStyle::FusionDarker, ":res/qt6ct_colors/darker.conf"}, + {UiStyle::FusionDusk, ":res/qt6ct_colors/dusk.conf"}, + {UiStyle::FusionIaOra, ":res/qt6ct_colors/ia_ora.conf"}, + {UiStyle::FusionSand, ":res/qt6ct_colors/sand.conf"}, + {UiStyle::FusionWaves, ":res/qt6ct_colors/waves.conf"}}; + +SCWX_GET_ENUM(UiStyle, GetUiStyle, uiStyleName_) + +Qt::ColorScheme GetQtColorScheme(UiStyle uiStyle) +{ + return qtColorSchemeMap_.at(uiStyle); +} + +std::string GetQtStyleName(UiStyle uiStyle) +{ + return qtStyleName_.at(uiStyle); +} + +std::optional GetQtPaletteFile(UiStyle uiStyle) +{ + if (paletteFile_.contains(uiStyle)) { - return result->first; - } - else - { - return UiStyle::Unknown; + return paletteFile_.at(uiStyle); } + + return std::nullopt; } std::string GetUiStyleName(UiStyle uiStyle) diff --git a/scwx-qt/source/scwx/qt/types/qt_types.hpp b/scwx-qt/source/scwx/qt/types/qt_types.hpp index 26a41c25..b5779ff1 100644 --- a/scwx-qt/source/scwx/qt/types/qt_types.hpp +++ b/scwx-qt/source/scwx/qt/types/qt_types.hpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -20,17 +21,31 @@ enum ItemDataRole RawDataRole }; -enum UiStyle +enum class UiStyle { Default, Fusion, + FusionLight, + FusionDark, + FusionAiry, + FusionDarker, + FusionDusk, + FusionIaOra, + FusionSand, + FusionWaves, + FusionCustom, Unknown }; -typedef scwx::util::Iterator +typedef scwx::util::Iterator UiStyleIterator; +Qt::ColorScheme GetQtColorScheme(UiStyle uiStyle); +std::string GetQtStyleName(UiStyle uiStyle); + +std::optional GetQtPaletteFile(UiStyle uiStyle); + UiStyle GetUiStyle(const std::string& name); -std::string GetUiStyleName(UiStyle alertAction); +std::string GetUiStyleName(UiStyle uiStyle); } // namespace types } // namespace qt diff --git a/scwx-qt/source/scwx/qt/types/text_event_key.cpp b/scwx-qt/source/scwx/qt/types/text_event_key.cpp index bebf6f63..be5d0443 100644 --- a/scwx-qt/source/scwx/qt/types/text_event_key.cpp +++ b/scwx-qt/source/scwx/qt/types/text_event_key.cpp @@ -14,26 +14,29 @@ static const std::string logPrefix_ = "scwx::qt::types::text_event_key"; std::string TextEventKey::ToFullString() const { - return fmt::format("{} {} {} {:04}", + return fmt::format("{} {} {} {:04} ({:04})", officeId_, awips::GetPhenomenonText(phenomenon_), awips::GetSignificanceText(significance_), - etn_); + etn_, + static_cast(year_)); } std::string TextEventKey::ToString() const { - return fmt::format("{}.{}.{}.{:04}", + return fmt::format("{}.{}.{}.{:04}.{:04}", officeId_, awips::GetPhenomenonCode(phenomenon_), awips::GetSignificanceCode(significance_), - etn_); + etn_, + static_cast(year_)); } bool TextEventKey::operator==(const TextEventKey& o) const { return (officeId_ == o.officeId_ && phenomenon_ == o.phenomenon_ && - significance_ == o.significance_ && etn_ == o.etn_); + significance_ == o.significance_ && etn_ == o.etn_ && + year_ == o.year_); } size_t TextEventHash::operator()(const TextEventKey& x) const @@ -43,6 +46,7 @@ size_t TextEventHash::operator()(const TextEventKey& x) const boost::hash_combine(seed, x.phenomenon_); boost::hash_combine(seed, x.significance_); boost::hash_combine(seed, x.etn_); + boost::hash_combine(seed, static_cast(x.year_)); return seed; } diff --git a/scwx-qt/source/scwx/qt/types/text_event_key.hpp b/scwx-qt/source/scwx/qt/types/text_event_key.hpp index f962bcdf..15eec31c 100644 --- a/scwx-qt/source/scwx/qt/types/text_event_key.hpp +++ b/scwx-qt/source/scwx/qt/types/text_event_key.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include namespace scwx { @@ -12,12 +13,34 @@ namespace types struct TextEventKey { TextEventKey() : TextEventKey(awips::PVtec {}) {} - TextEventKey(const awips::PVtec& pvtec) : + TextEventKey(const awips::PVtec& pvtec, std::chrono::year yearHint = {}) : officeId_ {pvtec.office_id()}, phenomenon_ {pvtec.phenomenon()}, significance_ {pvtec.significance()}, etn_ {pvtec.event_tracking_number()} { + using namespace std::chrono_literals; + + static constexpr std::chrono::year kMinYear_ = 1970y; + + std::chrono::year_month_day ymd = + std::chrono::floor(pvtec.event_begin()); + if (ymd.year() > kMinYear_) + { + // Prefer the year from the event begin + year_ = ymd.year(); + } + else if (yearHint > kMinYear_) + { + // Otherwise, use the year hint + year_ = yearHint; + } + else + { + // If there was no year hint, use the event end + ymd = std::chrono::floor(pvtec.event_end()); + year_ = ymd.year(); + } } std::string ToFullString() const; @@ -27,7 +50,8 @@ struct TextEventKey std::string officeId_; awips::Phenomenon phenomenon_; awips::Significance significance_; - int16_t etn_; + std::int16_t etn_; + std::chrono::year year_ {}; }; template diff --git a/scwx-qt/source/scwx/qt/types/texture_types.cpp b/scwx-qt/source/scwx/qt/types/texture_types.cpp index 5f7da52b..2369dc34 100644 --- a/scwx-qt/source/scwx/qt/types/texture_types.cpp +++ b/scwx-qt/source/scwx/qt/types/texture_types.cpp @@ -24,7 +24,34 @@ static const std::unordered_map imageTextureInfo_ { {"images/crosshairs-24", ":/res/textures/images/crosshairs-24.png"}}, {ImageTexture::Cursor17, {"images/cursor-17", ":/res/textures/images/cursor-17.png"}}, - {ImageTexture::Dot3, {"images/dot-3", ":/res/textures/images/dot-3.png"}}, + {ImageTexture::Dot3, {"images/dot-3", ":/res/textures/images/dot.svg"}}, + {ImageTexture::LocationBriefcase, + {"images/location-briefcase", + ":/res/icons/font-awesome-6/briefcase-solid.svg"}}, + {ImageTexture::LocationBuildingColumns, + {"images/location-building-columns", + ":/res/icons/font-awesome-6/building-columns-solid.svg"}}, + {ImageTexture::LocationBuilding, + {"images/location-building", + ":/res/icons/font-awesome-6/building-solid.svg"}}, + {ImageTexture::LocationCaravan, + {"images/location-caravan", + ":/res/icons/font-awesome-6/caravan-solid.svg"}}, + {ImageTexture::LocationCrosshair, + {"images/location-crosshair", + ":/res/icons/font-awesome-6/location-crosshairs-solid.svg"}}, + {ImageTexture::LocationHouse, + {"images/location-house", + ":/res/icons/font-awesome-6/house-solid-white.svg"}}, + {ImageTexture::LocationMarker, + {"images/location-marker", ":/res/textures/images/location-marker.svg"}}, + {ImageTexture::LocationPin, + {"images/location-pin", ":/res/icons/font-awesome-6/location-pin.svg"}}, + {ImageTexture::LocationStar, + {"images/location-star", + ":/res/icons/font-awesome-6/star-solid-white.svg"}}, + {ImageTexture::LocationTent, + {"images/location-tent", ":/res/icons/font-awesome-6/tent-solid.svg"}}, {ImageTexture::MapboxLogo, {"images/mapbox-logo", ":/res/textures/images/mapbox-logo.svg"}}, {ImageTexture::MapTilerLogo, diff --git a/scwx-qt/source/scwx/qt/types/texture_types.hpp b/scwx-qt/source/scwx/qt/types/texture_types.hpp index 593d574d..d5eabc4a 100644 --- a/scwx-qt/source/scwx/qt/types/texture_types.hpp +++ b/scwx-qt/source/scwx/qt/types/texture_types.hpp @@ -18,6 +18,16 @@ enum class ImageTexture Crosshairs24, Cursor17, Dot3, + LocationBriefcase, + LocationBuildingColumns, + LocationBuilding, + LocationCaravan, + LocationCrosshair, + LocationHouse, + LocationMarker, + LocationPin, + LocationStar, + LocationTent, MapboxLogo, MapTilerLogo }; diff --git a/scwx-qt/source/scwx/qt/ui/about_dialog.cpp b/scwx-qt/source/scwx/qt/ui/about_dialog.cpp index 42ec4e32..9ae6ce18 100644 --- a/scwx-qt/source/scwx/qt/ui/about_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/about_dialog.cpp @@ -24,13 +24,19 @@ AboutDialog::AboutDialog(QWidget* parent) : p {std::make_unique()}, ui(new Ui::AboutDialog) { +#if !defined(__APPLE__) + static constexpr int titleFontSize = 14; +#else + static constexpr int titleFontSize = 18; +#endif + ui->setupUi(this); - int titleFontId = + const int titleFontId = manager::FontManager::Instance().GetFontId(types::Font::din1451alt_g); - QString titleFontFamily = + const QString titleFontFamily = QFontDatabase::applicationFontFamilies(titleFontId).at(0); - QFont titleFont(titleFontFamily, 14); + const QFont titleFont(titleFontFamily, titleFontSize); ui->titleLabel->setFont(titleFont); QString repositoryUrl = @@ -45,7 +51,9 @@ AboutDialog::AboutDialog(QWidget* parent) : } ui->versionLabel->setText( - tr("Version %1").arg(QString::fromStdString(main::kVersionString_))); + tr("Version %1 (Build %2)") + .arg(QString::fromStdString(main::kVersionString_)) + .arg(main::kBuildNumber_)); ui->revisionLabel->setText( tr("Git Revision %2") .arg(repositoryUrl) diff --git a/scwx-qt/source/scwx/qt/ui/about_dialog.ui b/scwx-qt/source/scwx/qt/ui/about_dialog.ui index 64e8ed46..f8ef5270 100644 --- a/scwx-qt/source/scwx/qt/ui/about_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/about_dialog.ui @@ -16,12 +16,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -44,7 +38,7 @@ :/res/icons/scwx-256.png - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -54,7 +48,7 @@ Supercell Wx - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -64,7 +58,7 @@ Version X.Y.Z - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -74,7 +68,7 @@ Git Revision 0000000000 - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -84,7 +78,7 @@ Copyright © 2021-YYYY Dan Paulat - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -94,10 +88,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Ok diff --git a/scwx-qt/source/scwx/qt/ui/alert_dialog.cpp b/scwx-qt/source/scwx/qt/ui/alert_dialog.cpp index 76aa284a..3d9d1af5 100644 --- a/scwx-qt/source/scwx/qt/ui/alert_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/alert_dialog.cpp @@ -54,7 +54,13 @@ AlertDialog::AlertDialog(QWidget* parent) : // Set monospace font for alert view QFont monospaceFont("?"); - monospaceFont.setStyleHint(QFont::TypeWriter); + monospaceFont.setStyleHint(QFont::StyleHint::TypeWriter); + + if (!monospaceFont.fixedPitch()) + { + monospaceFont.setStyleHint(QFont::StyleHint::Monospace); + } + ui->alertText->setFont(monospaceFont); // Add Go button to button box diff --git a/scwx-qt/source/scwx/qt/ui/alert_dialog.ui b/scwx-qt/source/scwx/qt/ui/alert_dialog.ui index 55686925..f180dc71 100644 --- a/scwx-qt/source/scwx/qt/ui/alert_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/alert_dialog.ui @@ -17,18 +17,12 @@ - QTextEdit::NoWrap + QTextEdit::LineWrapMode::NoWrap - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -108,7 +102,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -121,10 +115,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Close + QDialogButtonBox::StandardButton::Close diff --git a/scwx-qt/source/scwx/qt/ui/alert_dock_widget.cpp b/scwx-qt/source/scwx/qt/ui/alert_dock_widget.cpp index 61fd160a..5e22071a 100644 --- a/scwx-qt/source/scwx/qt/ui/alert_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/alert_dock_widget.cpp @@ -131,6 +131,11 @@ void AlertDockWidgetImpl::ConnectSignals() &QAction::toggled, proxyModel_.get(), &model::AlertProxyModel::SetAlertActiveFilter); + connect(textEventManager_.get(), + &manager::TextEventManager::AlertsRemoved, + alertModel_.get(), + &model::AlertModel::HandleAlertsRemoved, + Qt::QueuedConnection); connect(textEventManager_.get(), &manager::TextEventManager::AlertUpdated, alertModel_.get(), diff --git a/scwx-qt/source/scwx/qt/ui/alert_dock_widget.ui b/scwx-qt/source/scwx/qt/ui/alert_dock_widget.ui index 96328278..317fc566 100644 --- a/scwx-qt/source/scwx/qt/ui/alert_dock_widget.ui +++ b/scwx-qt/source/scwx/qt/ui/alert_dock_widget.ui @@ -30,12 +30,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -62,7 +56,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -85,7 +79,7 @@ :/res/icons/font-awesome-6/sliders-solid.svg:/res/icons/font-awesome-6/sliders-solid.svg - QToolButton::InstantPopup + QToolButton::ToolButtonPopupMode::InstantPopup diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp index 12d94d27..eac16c44 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -18,6 +19,14 @@ namespace ui static const std::string logPrefix_ = "scwx::qt::ui::animation_dock_widget"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); +#if (__cpp_lib_chrono >= 201907L) +using local_days = std::chrono::local_days; +using zoned_time_ = std::chrono::zoned_time; +#else +using local_days = date::local_days; +using zoned_time_ = date::zoned_time; +#endif + class AnimationDockWidgetImpl { public: @@ -47,8 +56,14 @@ public: types::MapTime viewType_ {types::MapTime::Live}; bool isLive_ {true}; - std::chrono::sys_days selectedDate_ {}; - std::chrono::seconds selectedTime_ {}; + local_days selectedDate_ {}; + std::chrono::seconds selectedTime_ {}; + + const scwx::util::time_zone* timeZone_ {nullptr}; + + void UpdateTimeZoneLabel(const zoned_time_ zonedTime); + std::chrono::system_clock::time_point GetTimePoint(); + void SetTimePoint(std::chrono::system_clock::time_point time); void ConnectSignals(); void UpdateAutoUpdateLabel(); @@ -61,19 +76,18 @@ AnimationDockWidget::AnimationDockWidget(QWidget* parent) : { ui->setupUi(this); - // Set current date/time - QDateTime currentDateTime = QDateTime::currentDateTimeUtc(); - QDate currentDate = currentDateTime.date(); - QTime currentTime = currentDateTime.time(); - ui->dateEdit->setDate(currentDate); - ui->timeEdit->setTime(currentTime); - ui->dateEdit->setMaximumDate(currentDateTime.date()); - p->selectedDate_ = util::SysDays(currentDate); - p->selectedTime_ = - std::chrono::seconds(currentTime.msecsSinceStartOfDay() / 1000); +#if (__cpp_lib_chrono >= 201907L) + p->timeZone_ = std::chrono::get_tzdb().locate_zone("UTC"); +#else + p->timeZone_ = date::get_tzdb().locate_zone("UTC"); +#endif + const std::chrono::sys_seconds currentTimePoint = + std::chrono::floor(scwx::util::time::now()); + p->SetTimePoint(currentTimePoint); // Update maximum date on a timer - QTimer* maxDateTimer = new QTimer(this); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) Qt Owns this memory + auto* maxDateTimer = new QTimer(this); connect(maxDateTimer, &QTimer::timeout, this, @@ -106,6 +120,76 @@ AnimationDockWidget::~AnimationDockWidget() delete ui; } +void AnimationDockWidgetImpl::UpdateTimeZoneLabel(const zoned_time_ zonedTime) +{ +#if (__cpp_lib_chrono >= 201907L) + namespace df = std; + static constexpr std::string_view kFormatStringTimezone = "{:%Z}"; +#else + namespace df = date; + static const std::string kFormatStringTimezone = "%Z"; +#endif + const std::string timeZoneStr = df::format(kFormatStringTimezone, zonedTime); + self_->ui->timeZoneLabel->setText(timeZoneStr.c_str()); +} + +std::chrono::system_clock::time_point AnimationDockWidgetImpl::GetTimePoint() +{ +#if (__cpp_lib_chrono >= 201907L) + using namespace std::chrono; +#else + using namespace date; +#endif + + // Convert the local time, to a zoned time, to a system time + const local_time localTime = + selectedDate_ + selectedTime_; + const auto zonedTime = + zoned_time(timeZone_, localTime); + const std::chrono::sys_seconds systemTime = zonedTime.get_sys_time(); + + // This is done to update it when the date changes + UpdateTimeZoneLabel(zonedTime); + + return systemTime; +} + +void AnimationDockWidgetImpl::SetTimePoint( + std::chrono::system_clock::time_point systemTime) +{ +#if (__cpp_lib_chrono >= 201907L) + using namespace std::chrono; +#else + using namespace date; +#endif + // Convert the time to a local time + auto systemTimeSeconds = time_point_cast(systemTime); + auto zonedTime = + zoned_time(timeZone_, systemTimeSeconds); + const local_seconds localTime = zonedTime.get_local_time(); + + // Get the date and time as seperate fields + selectedDate_ = floor(localTime); + selectedTime_ = localTime - selectedDate_; + + // Pull out the local date and time as qt times (with c++20 this could be + // simplified) + auto time = QTime::fromMSecsSinceStartOfDay(static_cast( + duration_cast(selectedTime_).count())); + auto yearMonthDay = year_month_day(selectedDate_); + auto date = QDate(int(yearMonthDay.year()), + // These are always in a small range, so cast is safe + static_cast(unsigned(yearMonthDay.month())), + static_cast(unsigned(yearMonthDay.day()))); + + // Update labels + self_->ui->timeEdit->setTime(time); + self_->ui->dateEdit->setDate(date); + + // Time zone almost certainly just changed, so update it + UpdateTimeZoneLabel(zonedTime); +} + void AnimationDockWidgetImpl::ConnectSignals() { // View type @@ -140,8 +224,8 @@ void AnimationDockWidgetImpl::ConnectSignals() { if (date.isValid()) { - selectedDate_ = util::SysDays(date); - Q_EMIT self_->DateTimeChanged(selectedDate_ + selectedTime_); + selectedDate_ = util::LocalDays(date); + Q_EMIT self_->DateTimeChanged(GetTimePoint()); } }); QObject::connect( @@ -152,9 +236,9 @@ void AnimationDockWidgetImpl::ConnectSignals() { if (time.isValid()) { - selectedTime_ = - std::chrono::seconds(time.msecsSinceStartOfDay() / 1000); - Q_EMIT self_->DateTimeChanged(selectedDate_ + selectedTime_); + selectedTime_ = std::chrono::duration_cast( + std::chrono::milliseconds(time.msecsSinceStartOfDay())); + Q_EMIT self_->DateTimeChanged(GetTimePoint()); } }); @@ -300,6 +384,27 @@ void AnimationDockWidgetImpl::UpdateAutoUpdateLabel() } } +void AnimationDockWidget::UpdateTimeZone(const scwx::util::time_zone* timeZone) +{ + // null timezone is really UTC. This simplifies other code. + if (timeZone == nullptr) + { +#if (__cpp_lib_chrono >= 201907L) + timeZone = std::chrono::get_tzdb().locate_zone("UTC"); +#else + timeZone = date::get_tzdb().locate_zone("UTC"); +#endif + } + + // Get the (UTC relative) time that is selected. We want to preserve this + // across timezone changes. + auto currentTime = p->GetTimePoint(); + p->timeZone_ = timeZone; + // Set the (UTC relative) time that was already selected. This ensures that + // the actual time does not change, only the time zone. + p->SetTimePoint(currentTime); +} + } // namespace ui } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp index abc79c88..c22b0849 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -32,6 +33,7 @@ public slots: void UpdateAnimationState(types::AnimationState state); void UpdateLiveState(bool isLive); void UpdateViewType(types::MapTime viewType); + void UpdateTimeZone(const scwx::util::time_zone* timeZone); signals: void ViewTypeChanged(types::MapTime viewType); diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui index 1c79eb48..dbbed2a7 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui @@ -7,15 +7,9 @@ 0 0 189 - 264 + 276 - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -56,7 +50,7 @@ - QAbstractSpinBox::CorrectToNearestValue + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue @@ -78,12 +72,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -100,7 +88,7 @@ - QAbstractSpinBox::CorrectToNearestValue + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue HH:mm @@ -108,7 +96,7 @@ - + UTC @@ -119,12 +107,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -141,7 +123,7 @@ - QAbstractSpinBox::CorrectToNearestValue + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue min @@ -150,7 +132,7 @@ 1 - 1440 + 2880 30 @@ -193,7 +175,7 @@ - QAbstractSpinBox::CorrectToNearestValue + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue x @@ -218,12 +200,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 1 diff --git a/scwx-qt/source/scwx/qt/ui/api_key_edit_widget.cpp b/scwx-qt/source/scwx/qt/ui/api_key_edit_widget.cpp new file mode 100644 index 00000000..73f936b5 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/api_key_edit_widget.cpp @@ -0,0 +1,107 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace scwx::qt::ui; + +static const std::string logPrefix_ = "scwx::qt::ui::QApiKeyEdit"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +QApiKeyEdit::QApiKeyEdit(QWidget* parent) : + QLineEdit(parent), networkAccessManager_(new QNetworkAccessManager(this)) +{ + const QIcon icon = + QApplication::style()->standardIcon(QStyle::SP_BrowserReload); + testAction_ = addAction(icon, QLineEdit::TrailingPosition); + testAction_->setIconText(tr("Test Key")); + testAction_->setToolTip(tr("Test the API key for this provider")); + + connect(testAction_, &QAction::triggered, this, &QApiKeyEdit::apiTest); + connect(networkAccessManager_, + &QNetworkAccessManager::finished, + this, + &QApiKeyEdit::apiTestFinished); + + // Reset test icon when text changes + connect(this, + &QLineEdit::textChanged, + this, + [this, icon]() { testAction_->setIcon(icon); }); +} + +void QApiKeyEdit::apiTest() +{ + QNetworkRequest req; + req.setTransferTimeout(5000); + + switch (provider_) + { + case map::MapProvider::Mapbox: + { + QUrl url("https://api.mapbox.com/v4/mapbox.mapbox-streets-v8/1/0/0.mvt"); + logger_->debug("Testing MapProvider::Mapbox API key at {}", + url.toString().toStdString()); + QUrlQuery query; + query.addQueryItem("access_token", text()); + url.setQuery(query); + req.setUrl(url); + break; + } + case map::MapProvider::MapTiler: + { + QUrl url("https://api.maptiler.com/maps/streets-v2/"); + logger_->debug("Testing MapProvider::MapTiler API key at {}", + url.toString().toStdString()); + QUrlQuery query; + query.addQueryItem("key", text()); + url.setQuery(query); + req.setUrl(url); + break; + } + default: + { + logger_->warn("Cannot test MapProvider::Unknown API key"); + break; + } + } + + networkAccessManager_->get(req); +} + +void QApiKeyEdit::apiTestFinished(QNetworkReply* reply) +{ + switch (reply->error()) + { + case QNetworkReply::NoError: + { + logger_->info("QApiKeyEdit: test success"); + QToolTip::showText(mapToGlobal(QPoint()), tr("Key was valid")); + testAction_->setIcon( + QApplication::style()->standardIcon(QStyle::SP_DialogApplyButton)); + Q_EMIT apiTestSucceeded(); + break; + } + default: + { + const char* errStr = + QMetaEnum::fromType().valueToKey( + reply->error()); + logger_->warn("QApiKeyEdit: test failed, got {} from {}", + errStr, + reply->url().host().toStdString()); + QToolTip::showText(mapToGlobal(QPoint()), tr("Invalid key")); + testAction_->setIcon( + QApplication::style()->standardIcon(QStyle::SP_DialogCancelButton)); + Q_EMIT apiTestFailed(reply->error()); + break; + } + } +} diff --git a/scwx-qt/source/scwx/qt/ui/api_key_edit_widget.hpp b/scwx-qt/source/scwx/qt/ui/api_key_edit_widget.hpp new file mode 100644 index 00000000..074e0b55 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/api_key_edit_widget.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +class QNetworkAccessManager; + +namespace scwx::qt::ui +{ + +class QApiKeyEdit : public QLineEdit +{ + Q_OBJECT + +public: + QApiKeyEdit(QWidget* parent = nullptr); + + map::MapProvider getMapProvider() const { return provider_; } + + void setMapProvider(const map::MapProvider provider) + { + provider_ = provider; + } + +signals: + void apiTestSucceeded(); + void apiTestFailed(QNetworkReply::NetworkError error); + +private slots: + void apiTest(); + void apiTestFinished(QNetworkReply* reply); + +protected: + map::MapProvider provider_ {map::MapProvider::Unknown}; + QNetworkAccessManager* networkAccessManager_ {}; + QAction* testAction_ {}; +}; + +} // namespace scwx::qt::ui diff --git a/scwx-qt/source/scwx/qt/ui/county_dialog.ui b/scwx-qt/source/scwx/qt/ui/county_dialog.ui index 71741c86..6a9a0257 100644 --- a/scwx-qt/source/scwx/qt/ui/county_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/county_dialog.ui @@ -29,12 +29,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -54,10 +48,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/scwx-qt/source/scwx/qt/ui/custom_layer_dialog.cpp b/scwx-qt/source/scwx/qt/ui/custom_layer_dialog.cpp new file mode 100644 index 00000000..d7c963f6 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/custom_layer_dialog.cpp @@ -0,0 +1,108 @@ +#include "custom_layer_dialog.hpp" +#include "ui_custom_layer_dialog.h" + +#include +#include +#include +#include + +#include + +namespace scwx::qt::ui +{ + +static const std::string logPrefix_ = "scwx::qt::ui::custom_layer_dialog"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class CustomLayerDialogImpl +{ +public: + explicit CustomLayerDialogImpl(CustomLayerDialog* self, + QMapLibre::Settings settings) : + self_(self), settings_(std::move(settings)) + { + } + + ~CustomLayerDialogImpl() = default; + CustomLayerDialogImpl(const CustomLayerDialogImpl&) = delete; + CustomLayerDialogImpl(CustomLayerDialogImpl&&) = delete; + CustomLayerDialogImpl& operator=(const CustomLayerDialogImpl&) = delete; + CustomLayerDialogImpl& operator=(CustomLayerDialogImpl&&) = delete; + + void handle_mapChanged(QMapLibre::Map::MapChange change); + + CustomLayerDialog* self_; + + QMapLibre::Settings settings_; + std::shared_ptr map_; +}; + +void CustomLayerDialogImpl::handle_mapChanged(QMapLibre::Map::MapChange change) +{ + if (change == QMapLibre::Map::MapChange::MapChangeDidFinishLoadingStyle) + { + auto& generalSettings = settings::GeneralSettings::Instance(); + const std::string& customStyleDrawLayer = + generalSettings.custom_style_draw_layer().GetStagedOrValue(); + + const QStringList layerIds = map_->layerIds(); + self_->ui->layerListWidget->clear(); + self_->ui->layerListWidget->addItems(layerIds); + + const std::string symbologyLayer = util::maplibre::FindMapSymbologyLayer( + layerIds, {customStyleDrawLayer}); + + const auto& symbologyItems = self_->ui->layerListWidget->findItems( + symbologyLayer.c_str(), Qt::MatchExactly); + if (!symbologyItems.isEmpty()) + { + self_->ui->layerListWidget->setCurrentItem(symbologyItems.first()); + } + } +} + +CustomLayerDialog::CustomLayerDialog(const QMapLibre::Settings& settings, + QWidget* parent) : + QDialog(parent), + p {std::make_unique(this, settings)}, + ui(new Ui::CustomLayerDialog) +{ + ui->setupUi(this); + + auto& generalSettings = settings::GeneralSettings::Instance(); + const auto& customStyleUrl = generalSettings.custom_style_url().GetValue(); + const auto mapProvider = + map::GetMapProvider(generalSettings.map_provider().GetValue()); + + // TODO render the map with a layer to show what they are selecting + p->map_ = std::make_shared( + nullptr, p->settings_, QSize(1, 1), devicePixelRatioF()); + + QString qUrl = QString::fromStdString(customStyleUrl); + + if (mapProvider == map::MapProvider::MapTiler) + { + qUrl.append("?key="); + qUrl.append(map::GetMapProviderApiKey(mapProvider)); + } + + p->map_->setStyleUrl(qUrl); + + QObject::connect(p->map_.get(), + &QMapLibre::Map::mapChanged, + this, + [this](QMapLibre::Map::MapChange change) + { p->handle_mapChanged(change); }); +} + +CustomLayerDialog::~CustomLayerDialog() +{ + delete ui; +} + +std::string CustomLayerDialog::selected_layer() +{ + return ui->layerListWidget->currentItem()->text().toStdString(); +} + +} // namespace scwx::qt::ui diff --git a/scwx-qt/source/scwx/qt/ui/custom_layer_dialog.hpp b/scwx-qt/source/scwx/qt/ui/custom_layer_dialog.hpp new file mode 100644 index 00000000..ad0a2526 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/custom_layer_dialog.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +namespace Ui +{ +class CustomLayerDialog; +} + +namespace scwx::qt::ui +{ + +class CustomLayerDialogImpl; + +class CustomLayerDialog : public QDialog +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(CustomLayerDialog) + +public: + explicit CustomLayerDialog(const QMapLibre::Settings& settings, + QWidget* parent = nullptr); + ~CustomLayerDialog() override; + + std::string selected_layer(); + +private: + friend class CustomLayerDialogImpl; + std::unique_ptr p; + Ui::CustomLayerDialog* ui; +}; + +} // namespace scwx::qt::ui diff --git a/scwx-qt/source/scwx/qt/ui/custom_layer_dialog.ui b/scwx-qt/source/scwx/qt/ui/custom_layer_dialog.ui new file mode 100644 index 00000000..2ce59321 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/custom_layer_dialog.ui @@ -0,0 +1,96 @@ + + + CustomLayerDialog + + + + 0 + 0 + 308 + 300 + + + + + 0 + 0 + + + + Custom Map Style Draw Layer + + + + + + + + + 0 + 0 + + + + QAbstractItemView::EditTrigger::NoEditTriggers + + + true + + + + + + + + + + 0 + 0 + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + + + + + + + buttonBox + accepted() + CustomLayerDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CustomLayerDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/scwx-qt/source/scwx/qt/ui/edit_line_dialog.cpp b/scwx-qt/source/scwx/qt/ui/edit_line_dialog.cpp new file mode 100644 index 00000000..5202e732 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/edit_line_dialog.cpp @@ -0,0 +1,316 @@ +#include "edit_line_dialog.hpp" +#include "ui_edit_line_dialog.h" + +#include +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +static const std::string logPrefix_ = "scwx::qt::ui::edit_line_dialog"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class EditLineDialog::Impl +{ +public: + struct EditComponent + { + void ConnectSignals(EditLineDialog* self) + { + QObject::connect(colorLineEdit_, + &QLineEdit::textEdited, + self, + [=, this](const QString& text) + { + boost::gil::rgba8_pixel_t color = + util::color::ToRgba8PixelT(text.toStdString()); + self->p->set_color(*this, color, false); + }); + + QObject::connect(colorButton_, + &QAbstractButton::clicked, + self, + [=, this]() { self->p->ShowColorDialog(*this); }); + + QObject::connect(widthSpinBox_, + &QSpinBox::valueChanged, + self, + [=, this](int width) + { self->p->set_width(*this, width); }); + } + + boost::gil::rgba8_pixel_t color_; + std::size_t width_; + QFrame* colorFrame_ {nullptr}; + QLineEdit* colorLineEdit_ {nullptr}; + QToolButton* colorButton_ {nullptr}; + QSpinBox* widthSpinBox_ {nullptr}; + }; + + explicit Impl(EditLineDialog* self) : + self_ {self}, lineLabel_ {new LineLabel(self)} + { + } + ~Impl() = default; + + void SetDefaults(); + void ShowColorDialog(EditComponent& component); + void UpdateLineLabel(); + + void set_color(EditComponent& component, + boost::gil::rgba8_pixel_t color, + bool updateLineEdit = true); + void set_width(EditComponent& component, std::size_t width); + + static void SetBackgroundColor(const std::string& value, QFrame* frame); + + EditLineDialog* self_; + + LineLabel* lineLabel_; + + boost::gil::rgba8_pixel_t defaultBorderColor_ {0, 0, 0, 255}; + boost::gil::rgba8_pixel_t defaultHighlightColor_ {0, 0, 0, 0}; + boost::gil::rgba8_pixel_t defaultLineColor_ {255, 255, 255, 255}; + + std::size_t defaultBorderWidth_ {1u}; + std::size_t defaultHighlightWidth_ {0u}; + std::size_t defaultLineWidth_ {3u}; + + EditComponent borderComponent_ {}; + EditComponent highlightComponent_ {}; + EditComponent lineComponent_ {}; +}; + +EditLineDialog::EditLineDialog(QWidget* parent) : + QDialog(parent), + p {std::make_unique(this)}, + ui(new Ui::EditLineDialog) +{ + ui->setupUi(this); + + p->borderComponent_.colorFrame_ = ui->borderColorFrame; + p->borderComponent_.colorLineEdit_ = ui->borderColorLineEdit; + p->borderComponent_.colorButton_ = ui->borderColorButton; + p->borderComponent_.widthSpinBox_ = ui->borderWidthSpinBox; + + p->highlightComponent_.colorFrame_ = ui->highlightColorFrame; + p->highlightComponent_.colorLineEdit_ = ui->highlightColorLineEdit; + p->highlightComponent_.colorButton_ = ui->highlightColorButton; + p->highlightComponent_.widthSpinBox_ = ui->highlightWidthSpinBox; + + p->lineComponent_.colorFrame_ = ui->lineColorFrame; + p->lineComponent_.colorLineEdit_ = ui->lineColorLineEdit; + p->lineComponent_.colorButton_ = ui->lineColorButton; + p->lineComponent_.widthSpinBox_ = ui->lineWidthSpinBox; + + p->SetDefaults(); + + p->lineLabel_->setMinimumWidth(72); + + QHBoxLayout* lineLabelContainerLayout = + static_cast(ui->lineLabelContainer->layout()); + lineLabelContainerLayout->insertWidget(1, p->lineLabel_); + + p->borderComponent_.ConnectSignals(this); + p->highlightComponent_.ConnectSignals(this); + p->lineComponent_.ConnectSignals(this); + + QObject::connect(ui->buttonBox, + &QDialogButtonBox::clicked, + this, + [this](QAbstractButton* button) + { + QDialogButtonBox::ButtonRole role = + ui->buttonBox->buttonRole(button); + + switch (role) + { + case QDialogButtonBox::ButtonRole::ResetRole: // Reset + p->SetDefaults(); + break; + + default: + break; + } + }); +} + +EditLineDialog::~EditLineDialog() +{ + delete ui; +} + +boost::gil::rgba8_pixel_t EditLineDialog::border_color() const +{ + return p->borderComponent_.color_; +} + +boost::gil::rgba8_pixel_t EditLineDialog::highlight_color() const +{ + return p->highlightComponent_.color_; +} + +boost::gil::rgba8_pixel_t EditLineDialog::line_color() const +{ + return p->lineComponent_.color_; +} + +std::size_t EditLineDialog::border_width() const +{ + return p->borderComponent_.width_; +} + +std::size_t EditLineDialog::highlight_width() const +{ + return p->highlightComponent_.width_; +} + +std::size_t EditLineDialog::line_width() const +{ + return p->lineComponent_.width_; +} + +void EditLineDialog::set_border_color(boost::gil::rgba8_pixel_t color) +{ + p->set_color(p->borderComponent_, color); +} + +void EditLineDialog::set_highlight_color(boost::gil::rgba8_pixel_t color) +{ + p->set_color(p->highlightComponent_, color); +} + +void EditLineDialog::set_line_color(boost::gil::rgba8_pixel_t color) +{ + p->set_color(p->lineComponent_, color); +} + +void EditLineDialog::set_border_width(std::size_t width) +{ + p->set_width(p->borderComponent_, width); +} + +void EditLineDialog::set_highlight_width(std::size_t width) +{ + p->set_width(p->highlightComponent_, width); +} + +void EditLineDialog::set_line_width(std::size_t width) +{ + p->set_width(p->lineComponent_, width); +} + +void EditLineDialog::Impl::set_color(EditComponent& component, + boost::gil::rgba8_pixel_t color, + bool updateLineEdit) +{ + const std::string argbString {util::color::ToArgbString(color)}; + + component.color_ = color; + SetBackgroundColor(argbString, component.colorFrame_); + + if (updateLineEdit) + { + component.colorLineEdit_->setText(QString::fromStdString(argbString)); + } + + UpdateLineLabel(); +} + +void EditLineDialog::Impl::set_width(EditComponent& component, + std::size_t width) +{ + component.width_ = width; + component.widthSpinBox_->setValue(static_cast(width)); + + UpdateLineLabel(); +} + +void EditLineDialog::Impl::UpdateLineLabel() +{ + lineLabel_->set_border_color(borderComponent_.color_); + lineLabel_->set_highlight_color(highlightComponent_.color_); + lineLabel_->set_line_color(lineComponent_.color_); + + lineLabel_->set_border_width(borderComponent_.width_); + lineLabel_->set_highlight_width(highlightComponent_.width_); + lineLabel_->set_line_width(lineComponent_.width_); +} + +void EditLineDialog::Initialize(boost::gil::rgba8_pixel_t borderColor, + boost::gil::rgba8_pixel_t highlightColor, + boost::gil::rgba8_pixel_t lineColor, + std::size_t borderWidth, + std::size_t highlightWidth, + std::size_t lineWidth) +{ + p->defaultBorderColor_ = borderColor; + p->defaultHighlightColor_ = highlightColor; + p->defaultLineColor_ = lineColor; + + p->defaultBorderWidth_ = borderWidth; + p->defaultHighlightWidth_ = highlightWidth; + p->defaultLineWidth_ = lineWidth; + + p->SetDefaults(); +} + +void EditLineDialog::Impl::SetDefaults() +{ + self_->set_border_color(defaultBorderColor_); + self_->set_highlight_color(defaultHighlightColor_); + self_->set_line_color(defaultLineColor_); + + self_->set_border_width(defaultBorderWidth_); + self_->set_highlight_width(defaultHighlightWidth_); + self_->set_line_width(defaultLineWidth_); +} + +void EditLineDialog::Impl::ShowColorDialog(EditComponent& component) +{ + QColorDialog* dialog = new QColorDialog(self_); + + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setOption(QColorDialog::ColorDialogOption::ShowAlphaChannel); + + QColor initialColor(component.colorLineEdit_->text()); + if (initialColor.isValid()) + { + dialog->setCurrentColor(initialColor); + } + + QObject::connect( + dialog, + &QColorDialog::colorSelected, + self_, + [this, &component](const QColor& qColor) + { + QString colorName = qColor.name(QColor::NameFormat::HexArgb); + boost::gil::rgba8_pixel_t color = + util::color::ToRgba8PixelT(colorName.toStdString()); + + logger_->info("Selected color: {}", colorName.toStdString()); + set_color(component, color); + }); + + dialog->open(); +} + +void EditLineDialog::Impl::SetBackgroundColor(const std::string& value, + QFrame* frame) +{ + frame->setStyleSheet( + QString::fromStdString(fmt::format("background-color: {}", value))); +} + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/edit_line_dialog.hpp b/scwx-qt/source/scwx/qt/ui/edit_line_dialog.hpp new file mode 100644 index 00000000..2f8ea3ce --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/edit_line_dialog.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include + +#include + +namespace Ui +{ +class EditLineDialog; +} + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class EditLineDialog : public QDialog +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(EditLineDialog) + +public: + explicit EditLineDialog(QWidget* parent = nullptr); + ~EditLineDialog(); + + boost::gil::rgba8_pixel_t border_color() const; + boost::gil::rgba8_pixel_t highlight_color() const; + boost::gil::rgba8_pixel_t line_color() const; + + std::size_t border_width() const; + std::size_t highlight_width() const; + std::size_t line_width() const; + + void set_border_color(boost::gil::rgba8_pixel_t color); + void set_highlight_color(boost::gil::rgba8_pixel_t color); + void set_line_color(boost::gil::rgba8_pixel_t color); + + void set_border_width(std::size_t width); + void set_highlight_width(std::size_t width); + void set_line_width(std::size_t width); + + void Initialize(boost::gil::rgba8_pixel_t borderColor, + boost::gil::rgba8_pixel_t highlightColor, + boost::gil::rgba8_pixel_t lineColor, + std::size_t borderWidth, + std::size_t highlightWidth, + std::size_t lineWidth); + +private: + class Impl; + std::unique_ptr p; + Ui::EditLineDialog* ui; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/edit_line_dialog.ui b/scwx-qt/source/scwx/qt/ui/edit_line_dialog.ui new file mode 100644 index 00000000..bc133bf9 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/edit_line_dialog.ui @@ -0,0 +1,306 @@ + + + EditLineDialog + + + + 0 + 0 + 350 + 225 + + + + Edit Line + + + + + + + true + + + + Component + + + + + + + #ff000000 + + + + + + + 0 + + + 9 + + + + + + + ... + + + + :/res/icons/font-awesome-6/palette-solid.svg:/res/icons/font-awesome-6/palette-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/palette-solid.svg:/res/icons/font-awesome-6/palette-solid.svg + + + + + + + Border + + + + + + + #ff000000 + + + + + + + Line + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::Reset + + + + + + + + 24 + 24 + + + + QFrame::Shape::Box + + + QFrame::Shadow::Plain + + + + + + + 0 + + + 9 + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + 24 + 24 + + + + QFrame::Shape::Box + + + QFrame::Shadow::Plain + + + + + + + + true + + + + Color + + + + + + + #ff000000 + + + + + + + + 24 + 24 + + + + QFrame::Shape::Box + + + QFrame::Shadow::Plain + + + + + + + + true + + + + Width + + + + + + + 1 + + + 9 + + + + + + + Highlight + + + + + + + ... + + + + :/res/icons/font-awesome-6/palette-solid.svg:/res/icons/font-awesome-6/palette-solid.svg + + + + + + + + 0 + 45 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Orientation::Horizontal + + + + + + + Qt::Orientation::Horizontal + + + + + + + + + + + + + + buttonBox + accepted() + EditLineDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + EditLineDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/scwx-qt/source/scwx/qt/ui/edit_marker_dialog.cpp b/scwx-qt/source/scwx/qt/ui/edit_marker_dialog.cpp new file mode 100644 index 00000000..4f310488 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/edit_marker_dialog.cpp @@ -0,0 +1,337 @@ +#include "edit_marker_dialog.hpp" +#include "ui_edit_marker_dialog.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace scwx::qt::ui +{ + +static const std::string logPrefix_ = "scwx::qt::ui::edit_marker_dialog"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static const QString addingTitle_ = QObject::tr("Add Location Marker"); +static const QString editingTitle_ = QObject::tr("Edit Location Marker"); + +class EditMarkerDialog::Impl +{ +public: + explicit Impl(EditMarkerDialog* self) : self_ {self} {} + + void show_color_dialog(); + void show_icon_file_dialog(); + + void set_icon_color(const std::string& color); + void set_adding(bool adding); + + void connect_signals(); + + void handle_accepted(); + void handle_rejected(); + + EditMarkerDialog* self_; + QPushButton* deleteButton_ {nullptr}; + QIcon get_colored_icon(const types::MarkerIconInfo& marker, + const std::string& color); + + std::shared_ptr markerManager_ = + manager::MarkerManager::Instance(); + types::MarkerId editId_ {0}; + bool adding_ {false}; + std::string setIconOnAdded_ {""}; +}; + +QIcon EditMarkerDialog::Impl::get_colored_icon( + const types::MarkerIconInfo& marker, const std::string& color) +{ + return util::modulateColors(marker.qIcon, + self_->ui->iconComboBox->iconSize(), + QColor(QString::fromStdString(color))); +} + +void EditMarkerDialog::Impl::set_adding(bool adding) +{ + if (adding == adding_) + { + return; + } + + if (adding) + { + self_->setWindowTitle(addingTitle_); + } + else + { + self_->setWindowTitle(editingTitle_); + } + adding_ = adding; +} + +EditMarkerDialog::EditMarkerDialog(QWidget* parent) : + QDialog(parent), + p {std::make_unique(this)}, + ui(new Ui::EditMarkerDialog) +{ + ui->setupUi(this); + + for (auto& markerIcon : p->markerManager_->get_icons()) + { + ui->iconComboBox->addItem( + markerIcon.second.qIcon, + QString::fromStdString(markerIcon.second.shortName), + QString::fromStdString(markerIcon.second.name)); + } + p->deleteButton_ = + ui->buttonBox->addButton("Delete", QDialogButtonBox::DestructiveRole); + p->connect_signals(); +} + +EditMarkerDialog::~EditMarkerDialog() +{ + delete ui; +} + +void EditMarkerDialog::setup() +{ + setup(0.0, 0.0); +} + +void EditMarkerDialog::setup(double latitude, double longitude) +{ + // By default use foreground color as marker color, mainly so the icons + // are vissable in the dropdown menu. + const QColor color = QWidget::palette().color(QWidget::foregroundRole()); + p->editId_ = p->markerManager_->add_marker(types::MarkerInfo( + "", + latitude, + longitude, + manager::MarkerManager::getDefaultIconName(), + boost::gil::rgba8_pixel_t {static_cast(color.red()), + static_cast(color.green()), + static_cast(color.blue()), + static_cast(color.alpha())})); + + setup(p->editId_, true); +} + +void EditMarkerDialog::setup(types::MarkerId id, bool adding) +{ + std::optional marker = p->markerManager_->get_marker(id); + if (!marker) + { + return; + } + + p->editId_ = id; + p->set_adding(adding); + + const std::string iconColorStr = + util::color::ToArgbString(marker->iconColor); + p->set_icon_color(iconColorStr); + + int iconIndex = + ui->iconComboBox->findData(QString::fromStdString(marker->iconName)); + if (iconIndex < 0 || marker->iconName == "") + { + iconIndex = 0; + } + + ui->nameLineEdit->setText(QString::fromStdString(marker->name)); + ui->iconComboBox->setCurrentIndex(iconIndex); + ui->latitudeDoubleSpinBox->setValue(marker->latitude); + ui->longitudeDoubleSpinBox->setValue(marker->longitude); + ui->iconColorLineEdit->setText(QString::fromStdString(iconColorStr)); +} + +types::MarkerInfo EditMarkerDialog::get_marker_info() const +{ + const QString colorName = ui->iconColorLineEdit->text(); + const boost::gil::rgba8_pixel_t color = + util::color::ToRgba8PixelT(colorName.toStdString()); + + return types::MarkerInfo( + ui->nameLineEdit->text().toStdString(), + ui->latitudeDoubleSpinBox->value(), + ui->longitudeDoubleSpinBox->value(), + ui->iconComboBox->currentData().toString().toStdString(), + color); +} + +void EditMarkerDialog::Impl::show_color_dialog() +{ + // WA_DeleteOnClose manages memory + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + auto* dialog = new QColorDialog(self_); + + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setOption(QColorDialog::ColorDialogOption::ShowAlphaChannel); + + const QColor initialColor(self_->ui->iconColorLineEdit->text()); + if (initialColor.isValid()) + { + dialog->setCurrentColor(initialColor); + } + + QObject::connect(dialog, + &QColorDialog::colorSelected, + self_, + [this](const QColor& qColor) + { + const QString colorName = + qColor.name(QColor::NameFormat::HexArgb); + self_->ui->iconColorLineEdit->setText(colorName); + set_icon_color(colorName.toStdString()); + }); + dialog->open(); +} + +void EditMarkerDialog::Impl::show_icon_file_dialog() +{ + auto* dialog = new QFileDialog(self_); + + dialog->setFileMode(QFileDialog::ExistingFile); + dialog->setNameFilters({"Icon (*.png *.svg)", "All Files (*)"}); + dialog->setAttribute(Qt::WA_DeleteOnClose); + + QObject::connect(dialog, + &QFileDialog::fileSelected, + self_, + [this](const QString& file) + { + const std::string path = + QDir::toNativeSeparators(file).toStdString(); + setIconOnAdded_ = path; + markerManager_->add_icon(path); + }); + dialog->open(); +} + +void EditMarkerDialog::Impl::connect_signals() +{ + connect(self_, + &EditMarkerDialog::accepted, + self_, + [this]() { handle_accepted(); }); + + connect(self_, + &EditMarkerDialog::rejected, + self_, + [this]() { handle_rejected(); }); + + connect(deleteButton_, + &QPushButton::clicked, + self_, + [this]() + { + markerManager_->remove_marker(editId_); + self_->done(0); + }); + + connect(self_->ui->iconColorLineEdit, + &QLineEdit::textEdited, + self_, + [this](const QString& text) { set_icon_color(text.toStdString()); }); + + connect(self_->ui->iconColorButton, + &QAbstractButton::clicked, + self_, + [this]() { show_color_dialog(); }); + + connect(self_->ui->iconFileOpenButton, + &QPushButton::clicked, + self_, + [this]() { show_icon_file_dialog(); }); + + connect(markerManager_.get(), + &manager::MarkerManager::IconAdded, + self_, + [this]() + { + const std::string color = + self_->ui->iconColorLineEdit->text().toStdString(); + set_icon_color(color); + + if (setIconOnAdded_ != "") + { + const int i = self_->ui->iconComboBox->findData( + QString::fromStdString(setIconOnAdded_)); + if (i >= 0) + { + self_->ui->iconComboBox->setCurrentIndex(i); + setIconOnAdded_ = ""; + } + } + }); + + connect(self_->ui->buttonBox->button(QDialogButtonBox::Apply), + &QAbstractButton::clicked, + self_, + [this]() { handle_accepted(); }); +} + +void EditMarkerDialog::Impl::set_icon_color(const std::string& color) +{ + self_->ui->iconColorFrame->setStyleSheet( + QString::fromStdString(fmt::format("background-color: {}", color))); + + auto* iconComboBox = self_->ui->iconComboBox; + + const QVariant currentIcon = iconComboBox->currentData(); + + self_->ui->iconComboBox->clear(); + for (auto& markerIcon : markerManager_->get_icons()) + { + const int i = + iconComboBox->findData(QString::fromStdString(markerIcon.second.name)); + const QIcon icon = get_colored_icon(markerIcon.second, color); + if (i < 0) + { + iconComboBox->addItem( + icon, + QString::fromStdString(markerIcon.second.shortName), + QString::fromStdString(markerIcon.second.name)); + } + else + { + self_->ui->iconComboBox->setItemIcon(i, icon); + } + } + + const int i = iconComboBox->findData(currentIcon); + if (i < 0) + { + return; + } + + iconComboBox->setCurrentIndex(i); +} + +void EditMarkerDialog::Impl::handle_accepted() +{ + // switch to editing to that canceling after applying does not delete it + set_adding(false); + markerManager_->set_marker(editId_, self_->get_marker_info()); +} + +void EditMarkerDialog::Impl::handle_rejected() +{ + if (adding_) + { + markerManager_->remove_marker(editId_); + } +} + +} // namespace scwx::qt::ui diff --git a/scwx-qt/source/scwx/qt/ui/edit_marker_dialog.hpp b/scwx-qt/source/scwx/qt/ui/edit_marker_dialog.hpp new file mode 100644 index 00000000..3990b6c0 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/edit_marker_dialog.hpp @@ -0,0 +1,34 @@ +#pragma once +#include + +#include + +namespace Ui +{ +class EditMarkerDialog; +} + +namespace scwx::qt::ui +{ +class EditMarkerDialog : public QDialog +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(EditMarkerDialog) + +public: + explicit EditMarkerDialog(QWidget* parent = nullptr); + ~EditMarkerDialog() override; + + void setup(); + void setup(double latitude, double longitude); + void setup(types::MarkerId id, bool adding = false); + + [[nodiscard]] types::MarkerInfo get_marker_info() const; + +private: + class Impl; + std::unique_ptr p; + Ui::EditMarkerDialog* ui; +}; + +} // namespace scwx::qt::ui diff --git a/scwx-qt/source/scwx/qt/ui/edit_marker_dialog.ui b/scwx-qt/source/scwx/qt/ui/edit_marker_dialog.ui new file mode 100644 index 00000000..d3d47500 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/edit_marker_dialog.ui @@ -0,0 +1,210 @@ + + + EditMarkerDialog + + + + 0 + 0 + 400 + 249 + + + + Edit Location Marker + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + + 24 + 24 + + + + QFrame::Shape::Box + + + QFrame::Shadow::Plain + + + + + + + #ffffffff + + + + + + + ... + + + + :/res/icons/font-awesome-6/palette-solid.svg:/res/icons/font-awesome-6/palette-solid.svg + + + + + + + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + 5 + + + -90.000000000000000 + + + 90.000000000000000 + + + + + + + Name + + + + + + + Icon + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + + + + + Longitude + + + + + + + Icon Color + + + + + + + Latitude + + + + + + + Add Custom Icon + + + ... + + + + + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + 5 + + + -180.000000000000000 + + + 180.000000000000000 + + + + + + + + + + + 0 + 0 + + + + true + + + + + + + + + + + buttonBox + accepted() + EditMarkerDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + EditMarkerDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/scwx-qt/source/scwx/qt/ui/imgui_debug_dialog.ui b/scwx-qt/source/scwx/qt/ui/imgui_debug_dialog.ui index 5752a7c1..6b371769 100644 --- a/scwx-qt/source/scwx/qt/ui/imgui_debug_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/imgui_debug_dialog.ui @@ -16,12 +16,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -54,10 +48,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Close + QDialogButtonBox::StandardButton::Close diff --git a/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.cpp b/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.cpp index acb8eda3..541c8e3e 100644 --- a/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.cpp @@ -1,5 +1,4 @@ #include -#include #include #include @@ -10,11 +9,7 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace ui +namespace scwx::qt::ui { static const std::string logPrefix_ = "scwx::qt::ui::imgui_debug_widget"; @@ -52,19 +47,14 @@ public: model::ImGuiContextModel::Instance().DestroyContext(contextName_); } - void ImGuiCheckFonts(); - ImGuiDebugWidget* self_; ImGuiContext* context_; std::string contextName_; ImGuiContext* currentContext_; - gl::OpenGLFunctions gl_; - std::set renderedSet_ {}; bool imGuiRendererInitialized_ {false}; - std::uint64_t imGuiFontsBuildCount_ {}; }; ImGuiDebugWidget::ImGuiDebugWidget(QWidget* parent) : @@ -106,21 +96,16 @@ void ImGuiDebugWidget::initializeGL() { makeCurrent(); - // Initialize OpenGL Functions - p->gl_.initializeOpenGLFunctions(); - // Initialize ImGui OpenGL3 backend ImGui::SetCurrentContext(p->context_); ImGui_ImplOpenGL3_Init(); - p->imGuiFontsBuildCount_ = - manager::FontManager::Instance().imgui_fonts_build_count(); p->imGuiRendererInitialized_ = true; } void ImGuiDebugWidget::paintGL() { - p->gl_.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - p->gl_.glClear(GL_COLOR_BUFFER_BIT); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); ImGui::SetCurrentContext(p->currentContext_); @@ -128,9 +113,9 @@ void ImGuiDebugWidget::paintGL() std::shared_lock imguiFontAtlasLock { manager::FontManager::Instance().imgui_font_atlas_mutex()}; + model::ImGuiContextModel::Instance().NewFrame(); ImGui_ImplQt_NewFrame(this); ImGui_ImplOpenGL3_NewFrame(); - p->ImGuiCheckFonts(); ImGui::NewFrame(); if (!p->renderedSet_.contains(p->currentContext_)) @@ -155,26 +140,4 @@ void ImGuiDebugWidget::paintGL() imguiFontAtlasLock.unlock(); } -void ImGuiDebugWidgetImpl::ImGuiCheckFonts() -{ - // Update ImGui Fonts if required - std::uint64_t currentImGuiFontsBuildCount = - manager::FontManager::Instance().imgui_fonts_build_count(); - - if ((context_ == currentContext_ && - imGuiFontsBuildCount_ != currentImGuiFontsBuildCount) || - !model::ImGuiContextModel::Instance().font_atlas()->IsBuilt()) - { - ImGui_ImplOpenGL3_DestroyFontsTexture(); - ImGui_ImplOpenGL3_CreateFontsTexture(); - } - - if (context_ == currentContext_) - { - imGuiFontsBuildCount_ = currentImGuiFontsBuildCount; - } -} - -} // namespace ui -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::ui diff --git a/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.hpp b/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.hpp index 695a6be9..585d2432 100644 --- a/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.hpp +++ b/scwx-qt/source/scwx/qt/ui/imgui_debug_widget.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include struct ImGuiContext; diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.ui b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui index f9b2a076..45f8413e 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui @@ -16,12 +16,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -41,16 +35,16 @@ true - QAbstractItemView::InternalMove + QAbstractItemView::DragDropMode::InternalMove - Qt::MoveAction + Qt::DropAction::MoveAction true - QAbstractItemView::ExtendedSelection + QAbstractItemView::SelectionMode::ExtendedSelection 0 @@ -59,12 +53,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -81,7 +69,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -138,7 +126,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -156,12 +144,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -188,7 +170,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -207,10 +189,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Close|QDialogButtonBox::Reset + QDialogButtonBox::StandardButton::Close|QDialogButtonBox::StandardButton::Reset diff --git a/scwx-qt/source/scwx/qt/ui/level2_settings_widget.cpp b/scwx-qt/source/scwx/qt/ui/level2_settings_widget.cpp index 85b476c9..3d530733 100644 --- a/scwx-qt/source/scwx/qt/ui/level2_settings_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/level2_settings_widget.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -29,16 +30,15 @@ public: explicit Level2SettingsWidgetImpl(Level2SettingsWidget* self) : self_ {self}, layout_ {new QVBoxLayout(self)}, - elevationGroupBox_ {}, elevationButtons_ {}, - elevationCuts_ {}, - elevationButtonsChanged_ {false}, - resizeElevationButtons_ {false}, - settingsGroupBox_ {}, - declutterCheckBox_ {} + elevationCuts_ {} { + // NOLINTBEGIN(cppcoreguidelines-owning-memory) Qt takes care of this layout_->setContentsMargins(0, 0, 0, 0); + incomingElevationLabel_ = new QLabel("", self); + layout_->addWidget(incomingElevationLabel_); + elevationGroupBox_ = new QGroupBox(tr("Elevation"), self); new ui::FlowLayout(elevationGroupBox_); layout_->addWidget(elevationGroupBox_); @@ -51,6 +51,7 @@ public: settingsLayout->addWidget(declutterCheckBox_); settingsGroupBox_->setVisible(false); + // NOLINTEND(cppcoreguidelines-owning-memory) Qt takes care of this QObject::connect(hotkeyManager_.get(), &manager::HotkeyManager::HotkeyPressed, @@ -66,14 +67,15 @@ public: Level2SettingsWidget* self_; QLayout* layout_; - QGroupBox* elevationGroupBox_; + QGroupBox* elevationGroupBox_ {}; + QLabel* incomingElevationLabel_ {}; std::list elevationButtons_; std::vector elevationCuts_; - bool elevationButtonsChanged_; - bool resizeElevationButtons_; + bool elevationButtonsChanged_ {}; + bool resizeElevationButtons_ {}; - QGroupBox* settingsGroupBox_; - QCheckBox* declutterCheckBox_; + QGroupBox* settingsGroupBox_ {}; + QCheckBox* declutterCheckBox_ {}; float currentElevation_ {}; QToolButton* currentElevationButton_ {nullptr}; @@ -240,10 +242,29 @@ void Level2SettingsWidget::UpdateElevationSelection(float elevation) p->currentElevationButton_ = newElevationButton; } +void Level2SettingsWidget::UpdateIncomingElevation( + std::optional incomingElevation) +{ + if (incomingElevation.has_value()) + { + p->incomingElevationLabel_->setText( + "Incoming Elevation: " + QString::number(*incomingElevation, 'f', 1) + + common::Characters::DEGREE); + } + else + { + p->incomingElevationLabel_->setText("Incoming Elevation: None"); + } +} + void Level2SettingsWidget::UpdateSettings(map::MapWidget* activeMap) { - float currentElevation = activeMap->GetElevation(); - std::vector elevationCuts = activeMap->GetElevationCuts(); + std::optional currentElevationOption = activeMap->GetElevation(); + const float currentElevation = + currentElevationOption.has_value() ? *currentElevationOption : 0.0f; + const std::vector elevationCuts = activeMap->GetElevationCuts(); + const std::optional incomingElevation = + activeMap->GetIncomingLevel2Elevation(); if (p->elevationCuts_ != elevationCuts) { @@ -277,6 +298,7 @@ void Level2SettingsWidget::UpdateSettings(map::MapWidget* activeMap) } UpdateElevationSelection(currentElevation); + UpdateIncomingElevation(incomingElevation); } } // namespace ui diff --git a/scwx-qt/source/scwx/qt/ui/level2_settings_widget.hpp b/scwx-qt/source/scwx/qt/ui/level2_settings_widget.hpp index ce2e443f..32f788bb 100644 --- a/scwx-qt/source/scwx/qt/ui/level2_settings_widget.hpp +++ b/scwx-qt/source/scwx/qt/ui/level2_settings_widget.hpp @@ -2,6 +2,8 @@ #include +#include + namespace scwx { namespace qt @@ -23,6 +25,7 @@ public: void showEvent(QShowEvent* event) override; void UpdateElevationSelection(float elevation); + void UpdateIncomingElevation(std::optional incomingElevation); void UpdateSettings(map::MapWidget* activeMap); signals: diff --git a/scwx-qt/source/scwx/qt/ui/level3_products_widget.cpp b/scwx-qt/source/scwx/qt/ui/level3_products_widget.cpp index ae2c8866..5c993b66 100644 --- a/scwx-qt/source/scwx/qt/ui/level3_products_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/level3_products_widget.cpp @@ -60,7 +60,9 @@ public: categoryButtons_ {}, productTiltMap_ {}, awipsProductMap_ {}, - awipsProductMutex_ {} + awipsProductMutex_ {}, + categoryMap_ {}, + categoryMapMutex_ {} { layout_->setContentsMargins(0, 0, 0, 0); layout_->addWidget(productsWidget_); @@ -183,6 +185,9 @@ public: std::unordered_map awipsProductMap_; std::shared_mutex awipsProductMutex_; + common::Level3ProductCategoryMap categoryMap_; + std::shared_mutex categoryMapMutex_; + std::string currentAwipsId_ {}; QAction* currentProductTiltAction_ {nullptr}; @@ -211,7 +216,9 @@ void Level3ProductsWidgetImpl::HandleHotkeyPressed(types::Hotkey hotkey, if (productCategoryIt == kHotkeyProductCategoryMap_.cend() && hotkey != types::Hotkey::ProductTiltDecrease && - hotkey != types::Hotkey::ProductTiltIncrease) + hotkey != types::Hotkey::ProductTiltIncrease && + hotkey != types::Hotkey::ProductCategoryNext && + hotkey != types::Hotkey::ProductCategoryPrevious) { // Not handling this hotkey return; @@ -246,7 +253,69 @@ void Level3ProductsWidgetImpl::HandleHotkeyPressed(types::Hotkey hotkey, return; } - std::shared_lock lock {awipsProductMutex_}; + if (hotkey == types::Hotkey::ProductCategoryNext || + hotkey == types::Hotkey::ProductCategoryPrevious) + { + const std::shared_lock lock1 {categoryMapMutex_}; + const std::shared_lock lock2 {awipsProductMutex_}; + + const common::Level3ProductCategory category = + common::GetLevel3CategoryByProduct(product); + auto productsIt = categoryMap_.find(category); + if (productsIt == categoryMap_.cend()) + { + logger_->error("Could not find the current category in category map"); + return; + } + auto availableProducts = productsIt->second; + const auto& products = common::GetLevel3ProductsByCategory(category); + + auto productIt = std::find(products.begin(), products.end(), product); + if (productIt == products.end()) + { + logger_->error("Could not find product in category"); + return; + } + + if (hotkey == types::Hotkey::ProductCategoryNext) + { + do + { + productIt = std::next(productIt); + if (productIt == products.cend()) + { + logger_->info("Cannot go past the last product"); + return; + } + } while (!availableProducts.contains(*productIt)); + } + else + { + do + { + if (productIt == products.begin()) + { + logger_->info("Cannot go past the first product"); + return; + } + productIt = std::prev(productIt); + } while (!availableProducts.contains(*productIt)); + } + + auto productTiltsIt = productTiltMap_.find(*productIt); + if (productTiltsIt == productTiltMap_.cend()) + { + logger_->error("Could not find product tilt map: {}", + common::GetLevel3ProductDescription(product)); + return; + } + + // Select the new tilt + productTiltsIt->second.at(0)->trigger(); + return; + } + + const std::shared_lock lock {awipsProductMutex_}; // Find the current product tilt auto productTiltsIt = productTiltMap_.find(product); @@ -322,9 +391,11 @@ void Level3ProductsWidgetImpl::SelectProductCategory( { UpdateCategorySelection(category); + const std::shared_lock lock {categoryMapMutex_}; + Q_EMIT self_->RadarProductSelected( common::RadarProductGroup::Level3, - common::GetLevel3CategoryDefaultProduct(category), + common::GetLevel3CategoryDefaultProduct(category, categoryMap_), 0); } @@ -333,6 +404,12 @@ void Level3ProductsWidget::UpdateAvailableProducts( { logger_->trace("UpdateAvailableProducts()"); + // Save the category map + { + const std::unique_lock lock {p->categoryMapMutex_}; + p->categoryMap_ = updatedCategoryMap; + } + // Iterate through each category tool button std::for_each( p->categoryButtons_.cbegin(), @@ -420,7 +497,7 @@ void Level3ProductsWidgetImpl::UpdateCategorySelection( std::for_each(categoryButtons_.cbegin(), categoryButtons_.cend(), - [&, this](auto& toolButton) + [&](auto& toolButton) { if (toolButton->text().toStdString() == categoryName) { @@ -444,7 +521,7 @@ void Level3ProductsWidgetImpl::UpdateProductSelection( std::for_each(awipsProductMap_.cbegin(), awipsProductMap_.cend(), - [&, this](const auto& pair) + [&](const auto& pair) { if (pair.second == awipsId) { diff --git a/scwx-qt/source/scwx/qt/ui/line_label.cpp b/scwx-qt/source/scwx/qt/ui/line_label.cpp new file mode 100644 index 00000000..03dbcaf6 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/line_label.cpp @@ -0,0 +1,251 @@ +#include +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +static const std::string logPrefix_ = "scwx::qt::ui::line_label"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class LineLabel::Impl +{ +public: + explicit Impl(LineLabel* self) : self_ {self} {}; + ~Impl() = default; + + QImage GenerateImage() const; + void UpdateLineLabel(const settings::LineSettings& lineSettings); + + LineLabel* self_; + + std::size_t borderWidth_ {1}; + std::size_t highlightWidth_ {1}; + std::size_t lineWidth_ {3}; + + boost::gil::rgba8_pixel_t borderColor_ {0, 0, 0, 255}; + boost::gil::rgba8_pixel_t highlightColor_ {255, 255, 0, 255}; + boost::gil::rgba8_pixel_t lineColor_ {0, 0, 255, 255}; + + QPixmap pixmap_ {}; + bool pixmapDirty_ {true}; + + boost::signals2::scoped_connection settingsStaged_ {}; +}; + +LineLabel::LineLabel(QWidget* parent) : + QFrame(parent), p {std::make_unique(this)} +{ +} + +LineLabel::~LineLabel() {} + +boost::gil::rgba8_pixel_t LineLabel::border_color() const +{ + return p->borderColor_; +} + +boost::gil::rgba8_pixel_t LineLabel::highlight_color() const +{ + return p->highlightColor_; +} + +boost::gil::rgba8_pixel_t LineLabel::line_color() const +{ + return p->lineColor_; +} + +std::size_t LineLabel::border_width() const +{ + return p->borderWidth_; +} + +std::size_t LineLabel::highlight_width() const +{ + return p->highlightWidth_; +} + +std::size_t LineLabel::line_width() const +{ + return p->lineWidth_; +} + +void LineLabel::set_border_width(std::size_t width) +{ + p->borderWidth_ = width; + p->pixmapDirty_ = true; + + QMetaObject::invokeMethod(this, &QWidget::updateGeometry); + QMetaObject::invokeMethod( + this, static_cast(&QWidget::update)); +} + +void LineLabel::set_highlight_width(std::size_t width) +{ + p->highlightWidth_ = width; + p->pixmapDirty_ = true; + + QMetaObject::invokeMethod(this, &QWidget::updateGeometry); + QMetaObject::invokeMethod( + this, static_cast(&QWidget::update)); +} + +void LineLabel::set_line_width(std::size_t width) +{ + p->lineWidth_ = width; + p->pixmapDirty_ = true; + + QMetaObject::invokeMethod(this, &QWidget::updateGeometry); + QMetaObject::invokeMethod( + this, static_cast(&QWidget::update)); +} + +void LineLabel::set_border_color(boost::gil::rgba8_pixel_t color) +{ + p->borderColor_ = color; + p->pixmapDirty_ = true; + + QMetaObject::invokeMethod( + this, static_cast(&QWidget::update)); +} + +void LineLabel::set_highlight_color(boost::gil::rgba8_pixel_t color) +{ + p->highlightColor_ = color; + p->pixmapDirty_ = true; + + QMetaObject::invokeMethod( + this, static_cast(&QWidget::update)); +} + +void LineLabel::set_line_color(boost::gil::rgba8_pixel_t color) +{ + p->lineColor_ = color; + p->pixmapDirty_ = true; + + QMetaObject::invokeMethod( + this, static_cast(&QWidget::update)); +} + +void LineLabel::set_line_settings(settings::LineSettings& lineSettings) +{ + p->settingsStaged_ = lineSettings.staged_signal().connect( + [this, &lineSettings]() { p->UpdateLineLabel(lineSettings); }); + + p->UpdateLineLabel(lineSettings); +} + +void LineLabel::Impl::UpdateLineLabel( + const settings::LineSettings& lineSettings) +{ + self_->set_border_color(util::color::ToRgba8PixelT( + lineSettings.border_color().GetStagedOrValue())); + self_->set_highlight_color(util::color::ToRgba8PixelT( + lineSettings.highlight_color().GetStagedOrValue())); + self_->set_line_color( + util::color::ToRgba8PixelT(lineSettings.line_color().GetStagedOrValue())); + + self_->set_border_width(lineSettings.border_width().GetStagedOrValue()); + self_->set_highlight_width( + lineSettings.highlight_width().GetStagedOrValue()); + self_->set_line_width(lineSettings.line_width().GetStagedOrValue()); +} + +QSize LineLabel::minimumSizeHint() const +{ + return sizeHint(); +} + +QSize LineLabel::sizeHint() const +{ + QMargins margins = contentsMargins(); + + const std::size_t width = 1; + const std::size_t height = + (p->borderWidth_ + p->highlightWidth_) * 2 + p->lineWidth_; + + return QSize(static_cast(width) + margins.left() + margins.right(), + static_cast(height) + margins.top() + margins.bottom()); +} + +void LineLabel::paintEvent(QPaintEvent* e) +{ + logger_->trace("paintEvent"); + + QFrame::paintEvent(e); + + if (p->pixmapDirty_) + { + QImage image = p->GenerateImage(); + p->pixmap_ = QPixmap::fromImage(image); + p->pixmapDirty_ = false; + } + + // Don't stretch the line pixmap vertically + QRect rect = contentsRect(); + if (rect.height() > p->pixmap_.height()) + { + int dy = rect.height() - p->pixmap_.height(); + int dy1 = dy / 2; + int dy2 = dy - dy1; + rect.adjust(0, dy1, 0, -dy2); + } + + QPainter painter(this); + painter.drawPixmap(rect, p->pixmap_); +} + +QImage LineLabel::Impl::GenerateImage() const +{ + const QRgb borderRgba = qRgba(static_cast(borderColor_[0]), + static_cast(borderColor_[1]), + static_cast(borderColor_[2]), + static_cast(borderColor_[3])); + const QRgb highlightRgba = qRgba(static_cast(highlightColor_[0]), + static_cast(highlightColor_[1]), + static_cast(highlightColor_[2]), + static_cast(highlightColor_[3])); + const QRgb lineRgba = qRgba(static_cast(lineColor_[0]), + static_cast(lineColor_[1]), + static_cast(lineColor_[2]), + static_cast(lineColor_[3])); + + const std::size_t width = 1; + const std::size_t height = (borderWidth_ + highlightWidth_) * 2 + lineWidth_; + + QImage image(static_cast(width), + static_cast(height), + QImage::Format::Format_ARGB32); + + std::size_t y = 0; + for (std::size_t i = 0; i < borderWidth_; ++i, ++y) + { + image.setPixel(0, static_cast(y), borderRgba); + image.setPixel(0, static_cast(height - 1 - y), borderRgba); + } + + for (std::size_t i = 0; i < highlightWidth_; ++i, ++y) + { + image.setPixel(0, static_cast(y), highlightRgba); + image.setPixel(0, static_cast(height - 1 - y), highlightRgba); + } + + for (std::size_t i = 0; i < lineWidth_; ++i, ++y) + { + image.setPixel(0, static_cast(y), lineRgba); + image.setPixel(0, static_cast(height - 1 - y), lineRgba); + } + + return image; +} + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/line_label.hpp b/scwx-qt/source/scwx/qt/ui/line_label.hpp new file mode 100644 index 00000000..b746a98e --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/line_label.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class LineLabel : public QFrame +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(LineLabel) + +public: + explicit LineLabel(QWidget* parent = nullptr); + ~LineLabel(); + + boost::gil::rgba8_pixel_t border_color() const; + boost::gil::rgba8_pixel_t highlight_color() const; + boost::gil::rgba8_pixel_t line_color() const; + + std::size_t border_width() const; + std::size_t highlight_width() const; + std::size_t line_width() const; + + void set_border_color(boost::gil::rgba8_pixel_t color); + void set_highlight_color(boost::gil::rgba8_pixel_t color); + void set_line_color(boost::gil::rgba8_pixel_t color); + + void set_border_width(std::size_t width); + void set_highlight_width(std::size_t width); + void set_line_width(std::size_t width); + + void set_line_settings(settings::LineSettings& lineSettings); + +protected: + QSize minimumSizeHint() const override; + QSize sizeHint() const override; + void paintEvent(QPaintEvent* e) override; + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/marker_dialog.cpp b/scwx-qt/source/scwx/qt/ui/marker_dialog.cpp new file mode 100644 index 00000000..3db33a06 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/marker_dialog.cpp @@ -0,0 +1,45 @@ +#include "marker_dialog.hpp" +#include "ui_marker_dialog.h" + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +static const std::string logPrefix_ = "scwx::qt::ui::marker_dialog"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class MarkerDialogImpl +{ +public: + explicit MarkerDialogImpl() {} + ~MarkerDialogImpl() = default; + + MarkerSettingsWidget* markerSettingsWidget_ {nullptr}; +}; + +MarkerDialog::MarkerDialog(QWidget* parent) : + QDialog(parent), + p {std::make_unique()}, + ui(new Ui::MarkerDialog) +{ + ui->setupUi(this); + + p->markerSettingsWidget_ = new MarkerSettingsWidget(this); + p->markerSettingsWidget_->layout()->setContentsMargins(0, 0, 0, 0); + ui->contentsFrame->layout()->addWidget(p->markerSettingsWidget_); +} + +MarkerDialog::~MarkerDialog() +{ + delete ui; +} + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/marker_dialog.hpp b/scwx-qt/source/scwx/qt/ui/marker_dialog.hpp new file mode 100644 index 00000000..4a9503e9 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/marker_dialog.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace Ui +{ +class MarkerDialog; +} + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class MarkerDialogImpl; + +class MarkerDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MarkerDialog(QWidget* parent = nullptr); + ~MarkerDialog(); + +private: + friend class MarkerDialogImpl; + std::unique_ptr p; + Ui::MarkerDialog* ui; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/marker_dialog.ui b/scwx-qt/source/scwx/qt/ui/marker_dialog.ui new file mode 100644 index 00000000..86e8fad3 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/marker_dialog.ui @@ -0,0 +1,82 @@ + + + MarkerDialog + + + + 0 + 0 + 700 + 600 + + + + Location Marker Manager + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Close + + + + + + + + + buttonBox + accepted() + MarkerDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MarkerDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/scwx-qt/source/scwx/qt/ui/marker_settings_widget.cpp b/scwx-qt/source/scwx/qt/ui/marker_settings_widget.cpp new file mode 100644 index 00000000..da1d3809 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/marker_settings_widget.cpp @@ -0,0 +1,149 @@ +#include "marker_settings_widget.hpp" +#include "ui_marker_settings_widget.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace scwx::qt::ui +{ + +static const std::string logPrefix_ = "scwx::qt::ui::marker_settings_widget"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class MarkerSettingsWidgetImpl +{ +public: + explicit MarkerSettingsWidgetImpl(MarkerSettingsWidget* self) : + self_ {self}, + markerModel_ {new model::MarkerModel(self_)}, + proxyModel_ {new QSortFilterProxyModel(self_)} + { + proxyModel_->setSourceModel(markerModel_); + proxyModel_->setSortRole(Qt::DisplayRole); // TODO types::SortRole + proxyModel_->setFilterCaseSensitivity(Qt::CaseInsensitive); + proxyModel_->setFilterKeyColumn(-1); + } + + void ConnectSignals(); + void UpdateHotkeyLabel(); + + MarkerSettingsWidget* self_; + model::MarkerModel* markerModel_; + QSortFilterProxyModel* proxyModel_; + std::shared_ptr markerManager_ { + manager::MarkerManager::Instance()}; + std::shared_ptr editMarkerDialog_ {nullptr}; + boost::signals2::scoped_connection hotkeyConnection_; +}; + +MarkerSettingsWidget::MarkerSettingsWidget(QWidget* parent) : + QFrame(parent), + p {std::make_unique(this)}, + ui(new Ui::MarkerSettingsWidget) +{ + ui->setupUi(this); + + ui->removeButton->setEnabled(false); + ui->markerView->setModel(p->proxyModel_); + p->UpdateHotkeyLabel(); + + p->editMarkerDialog_ = std::make_shared(this); + + p->ConnectSignals(); +} + +MarkerSettingsWidget::~MarkerSettingsWidget() +{ + delete ui; +} + +void MarkerSettingsWidgetImpl::ConnectSignals() +{ + QObject::connect(self_->ui->addButton, + &QPushButton::clicked, + self_, + [this]() + { + editMarkerDialog_->setup(); + editMarkerDialog_->show(); + }); + QObject::connect( + self_->ui->removeButton, + &QPushButton::clicked, + self_, + [this]() + { + auto selectionModel = self_->ui->markerView->selectionModel(); + QModelIndex selected = selectionModel + ->selectedRows(static_cast( + model::MarkerModel::Column::Name)) + .first(); + + QVariant const id = + proxyModel_->data(selected, Qt::ItemDataRole::UserRole); + if (!id.isValid()) + { + return; + } + + markerManager_->remove_marker(id.toULongLong()); + }); + QObject::connect( + self_->ui->markerView->selectionModel(), + &QItemSelectionModel::selectionChanged, + self_, + [this](const QItemSelection& selected, const QItemSelection& deselected) + { + if (selected.size() == 0 && deselected.size() == 0) + { + // Items which stay selected but change their index are not + // included in selected and deselected. Thus, this signal might + // be emitted with both selected and deselected empty, if only + // the indices of selected items change. + return; + } + + const bool itemSelected = selected.size() > 0; + self_->ui->removeButton->setEnabled(itemSelected); + }); + QObject::connect(self_->ui->markerView, + &QAbstractItemView::doubleClicked, + self_, + [this](const QModelIndex& index) + { + QVariant const id = + proxyModel_->data(index, Qt::ItemDataRole::UserRole); + if (!id.isValid()) + { + return; + } + + editMarkerDialog_->setup(id.toULongLong()); + editMarkerDialog_->show(); + }); + hotkeyConnection_ = settings::HotkeySettings::Instance() + .hotkey(types::Hotkey::AddLocationMarker) + .changed_signal() + .connect([this]() { UpdateHotkeyLabel(); }); +} + +void MarkerSettingsWidgetImpl::UpdateHotkeyLabel() +{ + self_->ui->hotkeyLabel->setText( + fmt::format( + "A Location Marker can be placed at the location under the cursor by " + "pressing \"{}\" and edited by right clicking it on the map.", + settings::HotkeySettings::Instance() + .hotkey(types::Hotkey::AddLocationMarker) + .GetValue()) + .c_str()); +} + +} // namespace scwx::qt::ui diff --git a/scwx-qt/source/scwx/qt/ui/marker_settings_widget.hpp b/scwx-qt/source/scwx/qt/ui/marker_settings_widget.hpp new file mode 100644 index 00000000..b784c418 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/marker_settings_widget.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace Ui +{ +class MarkerSettingsWidget; +} + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class MarkerSettingsWidgetImpl; + +class MarkerSettingsWidget : public QFrame +{ + Q_OBJECT + +public: + explicit MarkerSettingsWidget(QWidget* parent = nullptr); + ~MarkerSettingsWidget(); + +private: + friend class MarkerSettingsWidgetImpl; + std::unique_ptr p; + Ui::MarkerSettingsWidget* ui; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/marker_settings_widget.ui b/scwx-qt/source/scwx/qt/ui/marker_settings_widget.ui new file mode 100644 index 00000000..49b41c8b --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/marker_settings_widget.ui @@ -0,0 +1,109 @@ + + + MarkerSettingsWidget + + + + 0 + 0 + 400 + 300 + + + + Frame + + + + 0 + + + + + + 0 + 0 + + + + + 9 + + + + QFrame::Shape::NoFrame + + + + + + Qt::TextFormat::PlainText + + + + + + + true + + + 0 + + + true + + + + + + + + 0 + + + 6 + + + 0 + + + 0 + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + &Add + + + + + + + false + + + R&emove + + + + + + + + + + + diff --git a/scwx-qt/source/scwx/qt/ui/open_url_dialog.ui b/scwx-qt/source/scwx/qt/ui/open_url_dialog.ui index a796d20d..117d62c1 100644 --- a/scwx-qt/source/scwx/qt/ui/open_url_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/open_url_dialog.ui @@ -16,12 +16,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -58,7 +52,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -71,10 +65,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/scwx-qt/source/scwx/qt/ui/placefile_dialog.ui b/scwx-qt/source/scwx/qt/ui/placefile_dialog.ui index 8ff045a6..aa3659bf 100644 --- a/scwx-qt/source/scwx/qt/ui/placefile_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/placefile_dialog.ui @@ -16,12 +16,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -41,10 +35,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Close + QDialogButtonBox::StandardButton::Close diff --git a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui index b95ab784..6355fdc0 100644 --- a/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui +++ b/scwx-qt/source/scwx/qt/ui/placefile_settings_widget.ui @@ -29,12 +29,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -61,7 +55,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal diff --git a/scwx-qt/source/scwx/qt/ui/radar_site_dialog.ui b/scwx-qt/source/scwx/qt/ui/radar_site_dialog.ui index 74eae33b..bfaf7a5c 100644 --- a/scwx-qt/source/scwx/qt/ui/radar_site_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/radar_site_dialog.ui @@ -17,7 +17,7 @@ - QAbstractItemView::CurrentChanged|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + QAbstractItemView::EditTrigger::CurrentChanged|QAbstractItemView::EditTrigger::DoubleClicked|QAbstractItemView::EditTrigger::EditKeyPressed|QAbstractItemView::EditTrigger::SelectedClicked true @@ -32,12 +32,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -64,10 +58,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp index d489c1d6..ca2daa42 100644 --- a/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp @@ -168,7 +168,17 @@ SerialPortDialog::~SerialPortDialog() std::string SerialPortDialog::serial_port() { - return p->selectedSerialPort_; + std::string serialPort = p->selectedSerialPort_; + +#if !defined(_WIN32) + auto it = p->portInfoMap_.find(p->selectedSerialPort_); + if (it != p->portInfoMap_.cend()) + { + serialPort = it->second.systemLocation().toStdString(); + } +#endif + + return serialPort; } int SerialPortDialog::baud_rate() @@ -225,7 +235,8 @@ void SerialPortDialog::Impl::UpdateModel() static const QStringList headerLabels { tr("Port"), tr("Description"), tr("Device")}; #else - static const QStringList headerLabels {tr("Port"), tr("Description")}; + static const QStringList headerLabels { + tr("Port"), tr("Location"), tr("Description")}; #endif // Clear existing serial ports @@ -260,8 +271,11 @@ void SerialPortDialog::Impl::UpdateModel() new QStandardItem(description), new QStandardItem(device)}); #else - root->appendRow( - {new QStandardItem(portName), new QStandardItem(description)}); + const QString systemLocation = port.second.systemLocation(); + + root->appendRow({new QStandardItem(portName), + new QStandardItem(systemLocation), + new QStandardItem(description)}); #endif } diff --git a/scwx-qt/source/scwx/qt/ui/settings/alert_palette_settings_widget.cpp b/scwx-qt/source/scwx/qt/ui/settings/alert_palette_settings_widget.cpp new file mode 100644 index 00000000..a3ae4642 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/settings/alert_palette_settings_widget.cpp @@ -0,0 +1,292 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +static const std::string logPrefix_ = + "scwx::qt::ui::settings::alert_palette_settings_widget"; + +class AlertPaletteSettingsWidget::Impl +{ +public: + explicit Impl(AlertPaletteSettingsWidget* self) : + self_ {self}, + phenomenonPagesWidget_ {new QStackedWidget(self)}, + phenomenonListView_ {new QListWidget(self)}, + editLineDialog_ {new EditLineDialog(self)} + { + SetupUi(); + ConnectSignals(); + } + ~Impl() {}; + + void AddPhenomenonLine(const std::string& name, + settings::LineSettings& lineSettings, + QGridLayout* layout, + int row); + QWidget* CreateStackedWidgetPage(awips::Phenomenon phenomenon); + void ConnectSignals(); + void SelectPhenomenon(awips::Phenomenon phenomenon); + void SetupUi(); + + AlertPaletteSettingsWidget* self_; + + QStackedWidget* phenomenonPagesWidget_; + QListWidget* phenomenonListView_; + + EditLineDialog* editLineDialog_; + settings::LineSettings* activeLineSettings_ {nullptr}; + + boost::unordered_flat_map phenomenonPages_ {}; + + std::vector connections_ {}; +}; + +AlertPaletteSettingsWidget::AlertPaletteSettingsWidget(QWidget* parent) : + SettingsPageWidget(parent), p {std::make_shared(this)} +{ +} + +AlertPaletteSettingsWidget::~AlertPaletteSettingsWidget() = default; + +void AlertPaletteSettingsWidget::Impl::SetupUi() +{ + // Setup phenomenon index pane + QLabel* phenomenonLabel = new QLabel(tr("Phenomenon:"), self_); + phenomenonPagesWidget_->setSizePolicy(QSizePolicy::Policy::MinimumExpanding, + QSizePolicy::Policy::Preferred); + + // Setup stacked widget + for (auto& phenomenon : settings::PaletteSettings::alert_phenomena()) + { + QWidget* phenomenonWidget = CreateStackedWidgetPage(phenomenon); + phenomenonPagesWidget_->addWidget(phenomenonWidget); + + phenomenonPages_.insert_or_assign(phenomenon, phenomenonWidget); + + phenomenonListView_->addItem( + QString::fromStdString(awips::GetPhenomenonText(phenomenon))); + } + + phenomenonListView_->setCurrentRow(0); + + // Create phenomenon index pane layout + QVBoxLayout* phenomenonIndexLayout = new QVBoxLayout(self_); + phenomenonIndexLayout->addWidget(phenomenonLabel); + phenomenonIndexLayout->addWidget(phenomenonListView_); + + QWidget* phenomenonIndexPane = new QWidget(self_); + phenomenonIndexPane->setLayout(phenomenonIndexLayout); + + // Create primary widget layout + QGridLayout* gridLayout = new QGridLayout(self_); + gridLayout->setContentsMargins(0, 0, 0, 0); + gridLayout->addWidget(phenomenonIndexPane, 0, 0); + gridLayout->addWidget(phenomenonPagesWidget_, 0, 1); + + QSpacerItem* spacer = + new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding); + gridLayout->addItem(spacer, 1, 0); + + self_->setLayout(gridLayout); +} + +void AlertPaletteSettingsWidget::Impl::ConnectSignals() +{ + connect( + phenomenonListView_->selectionModel(), + &QItemSelectionModel::selectionChanged, + self_, + [this](const QItemSelection& selected, const QItemSelection& deselected) + { + if (selected.size() == 0 && deselected.size() == 0) + { + // Items which stay selected but change their index are not + // included in selected and deselected. Thus, this signal might + // be emitted with both selected and deselected empty, if only + // the indices of selected items change. + return; + } + + if (selected.size() > 0) + { + QModelIndex selectedIndex = selected[0].indexes()[0]; + QVariant variantData = + phenomenonListView_->model()->data(selectedIndex); + if (variantData.typeId() == QMetaType::QString) + { + awips::Phenomenon phenomenon = awips::GetPhenomenonFromText( + variantData.toString().toStdString()); + SelectPhenomenon(phenomenon); + } + } + }); + + connect(editLineDialog_, + &EditLineDialog::accepted, + self_, + [this]() + { + // If the active line label was set + if (activeLineSettings_ != nullptr) + { + // Update the active line settings with selected line settings + activeLineSettings_->StageValues( + editLineDialog_->border_color(), + editLineDialog_->highlight_color(), + editLineDialog_->line_color(), + editLineDialog_->border_width(), + editLineDialog_->highlight_width(), + editLineDialog_->line_width()); + + // Reset the active line settings + activeLineSettings_ = nullptr; + } + }); +} + +void AlertPaletteSettingsWidget::Impl::SelectPhenomenon( + awips::Phenomenon phenomenon) +{ + auto it = phenomenonPages_.find(phenomenon); + if (it != phenomenonPages_.cend()) + { + phenomenonPagesWidget_->setCurrentWidget(it->second); + } +} + +QWidget* AlertPaletteSettingsWidget::Impl::CreateStackedWidgetPage( + awips::Phenomenon phenomenon) +{ + QWidget* page = new QWidget(self_); + QGridLayout* gridLayout = new QGridLayout(self_); + page->setLayout(gridLayout); + + const auto& impactBasedWarningInfo = + awips::ibw::GetImpactBasedWarningInfo(phenomenon); + + auto& alertPalette = + settings::PaletteSettings::Instance().alert_palette(phenomenon); + + int row = 0; + + // Add a blank label to align left and right widgets + gridLayout->addWidget(new QLabel(self_), row++, 0); + + AddPhenomenonLine( + "Active", + alertPalette.threat_category(awips::ibw::ThreatCategory::Base), + gridLayout, + row++); + + if (impactBasedWarningInfo.hasObservedTag_) + { + AddPhenomenonLine("Observed", alertPalette.observed(), gridLayout, row++); + } + + if (impactBasedWarningInfo.hasTornadoPossibleTag_) + { + AddPhenomenonLine("Tornado Possible", + alertPalette.tornado_possible(), + gridLayout, + row++); + } + + for (auto& category : impactBasedWarningInfo.threatCategories_) + { + if (category == awips::ibw::ThreatCategory::Base) + { + continue; + } + + AddPhenomenonLine(awips::ibw::GetThreatCategoryName(category), + alertPalette.threat_category(category), + gridLayout, + row++); + } + + AddPhenomenonLine("Inactive", alertPalette.inactive(), gridLayout, row++); + + QSpacerItem* spacer = + new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding); + gridLayout->addItem(spacer, row, 0); + + return page; +} + +void AlertPaletteSettingsWidget::Impl::AddPhenomenonLine( + const std::string& name, + settings::LineSettings& lineSettings, + QGridLayout* layout, + int row) +{ + QToolButton* toolButton = new QToolButton(self_); + toolButton->setText(tr("...")); + + LineLabel* lineLabel = new LineLabel(self_); + lineLabel->set_line_settings(lineSettings); + + QToolButton* resetButton = new QToolButton(self_); + resetButton->setIcon( + QIcon {":/res/icons/font-awesome-6/rotate-left-solid.svg"}); + resetButton->setVisible(!lineSettings.IsDefaultStaged()); + + layout->addWidget(new QLabel(tr(name.c_str()), self_), row, 0); + layout->addWidget(lineLabel, row, 1); + layout->addWidget(toolButton, row, 2); + layout->addWidget(resetButton, row, 3); + + self_->AddSettingsCategory(&lineSettings); + + connect(toolButton, + &QAbstractButton::clicked, + self_, + [this, lineLabel, &lineSettings]() + { + // Set the active line label for when the dialog is finished + activeLineSettings_ = &lineSettings; + + // Initialize dialog with current line settings + editLineDialog_->Initialize(lineLabel->border_color(), + lineLabel->highlight_color(), + lineLabel->line_color(), + lineLabel->border_width(), + lineLabel->highlight_width(), + lineLabel->line_width()); + + // Show the dialog + editLineDialog_->show(); + }); + + connect(resetButton, + &QAbstractButton::clicked, + self_, + [&lineSettings]() { lineSettings.StageDefaults(); }); + + connections_.emplace_back(lineSettings.staged_signal().connect( + [resetButton, &lineSettings]() + { resetButton->setVisible(!lineSettings.IsDefaultStaged()); })); +} + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/settings/alert_palette_settings_widget.hpp b/scwx-qt/source/scwx/qt/ui/settings/alert_palette_settings_widget.hpp new file mode 100644 index 00000000..45f03e36 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/settings/alert_palette_settings_widget.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class AlertPaletteSettingsWidget : public SettingsPageWidget +{ + Q_OBJECT + +public: + explicit AlertPaletteSettingsWidget(QWidget* parent = nullptr); + ~AlertPaletteSettingsWidget(); + +private: + class Impl; + std::shared_ptr p; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.cpp b/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.cpp index d174fbbd..41c43817 100644 --- a/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.cpp @@ -19,6 +19,7 @@ public: explicit Impl() {} ~Impl() = default; + std::vector categories_; std::vector settings_; }; @@ -29,6 +30,12 @@ SettingsPageWidget::SettingsPageWidget(QWidget* parent) : SettingsPageWidget::~SettingsPageWidget() = default; +void SettingsPageWidget::AddSettingsCategory( + settings::SettingsCategory* category) +{ + p->categories_.push_back(category); +} + void SettingsPageWidget::AddSettingsInterface( settings::SettingsInterfaceBase* setting) { @@ -39,6 +46,11 @@ bool SettingsPageWidget::CommitChanges() { bool committed = false; + for (auto& category : p->categories_) + { + committed |= category->Commit(); + } + for (auto& setting : p->settings_) { committed |= setting->Commit(); @@ -49,6 +61,11 @@ bool SettingsPageWidget::CommitChanges() void SettingsPageWidget::DiscardChanges() { + for (auto& category : p->categories_) + { + category->Reset(); + } + for (auto& setting : p->settings_) { setting->Reset(); diff --git a/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.hpp b/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.hpp index 228badd6..39d8647a 100644 --- a/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.hpp +++ b/scwx-qt/source/scwx/qt/ui/settings/settings_page_widget.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -61,6 +62,15 @@ public: protected: void AddSettingsInterface(settings::SettingsInterfaceBase* setting); + /** + * Commits and resets all settings within a category upon page commit or + * reset. The use of SettingsInterface is preferred, as it allows the binding + * of widgets to these actions. + * + * @param [in] category Settings category + */ + void AddSettingsCategory(settings::SettingsCategory* category); + private: class Impl; std::shared_ptr p; diff --git a/scwx-qt/source/scwx/qt/ui/settings/unit_settings_widget.cpp b/scwx-qt/source/scwx/qt/ui/settings/unit_settings_widget.cpp index aff11583..ea678e65 100644 --- a/scwx-qt/source/scwx/qt/ui/settings/unit_settings_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings/unit_settings_widget.cpp @@ -2,21 +2,17 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include -namespace scwx -{ -namespace qt -{ -namespace ui +namespace scwx::qt::ui { static const std::string logPrefix_ = @@ -51,7 +47,7 @@ public: [&row, &self, this]( settings::SettingsInterface& settingsInterface, const std::string& labelName, - QComboBox* comboBox) + QFocusedComboBox* comboBox) { QLabel* label = new QLabel(QObject::tr(labelName.c_str()), self); QToolButton* resetButton = new QToolButton(self); @@ -72,9 +68,12 @@ public: ++row; }; - QComboBox* accumulationComboBox = new QComboBox(self); + // Qt manages the memory for these widgets + // NOLINTBEGIN(cppcoreguidelines-owning-memory) + auto* accumulationComboBox = new QFocusedComboBox(self); accumulationComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + accumulationComboBox->setFocusPolicy(Qt::StrongFocus); accumulationUnits_.SetSettingsVariable(unitSettings.accumulation_units()); SCWX_SETTINGS_COMBO_BOX(accumulationUnits_, accumulationComboBox, @@ -82,9 +81,10 @@ public: types::GetAccumulationUnitsName); AddRow(accumulationUnits_, "Accumulation", accumulationComboBox); - QComboBox* echoTopsComboBox = new QComboBox(self); + auto* echoTopsComboBox = new QFocusedComboBox(self); echoTopsComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + echoTopsComboBox->setFocusPolicy(Qt::StrongFocus); echoTopsUnits_.SetSettingsVariable(unitSettings.echo_tops_units()); SCWX_SETTINGS_COMBO_BOX(echoTopsUnits_, echoTopsComboBox, @@ -92,9 +92,10 @@ public: types::GetEchoTopsUnitsName); AddRow(echoTopsUnits_, "Echo Tops", echoTopsComboBox); - QComboBox* speedComboBox = new QComboBox(self); + auto* speedComboBox = new QFocusedComboBox(self); speedComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + speedComboBox->setFocusPolicy(Qt::StrongFocus); speedUnits_.SetSettingsVariable(unitSettings.speed_units()); SCWX_SETTINGS_COMBO_BOX(speedUnits_, speedComboBox, @@ -102,9 +103,10 @@ public: types::GetSpeedUnitsName); AddRow(speedUnits_, "Speed", speedComboBox); - QComboBox* distanceComboBox = new QComboBox(self); + auto* distanceComboBox = new QFocusedComboBox(self); distanceComboBox->setSizePolicy(QSizePolicy::Expanding, - QSizePolicy::Preferred); + QSizePolicy::Preferred); + distanceComboBox->setFocusPolicy(Qt::StrongFocus); distanceUnits_.SetSettingsVariable(unitSettings.distance_units()); SCWX_SETTINGS_COMBO_BOX(distanceUnits_, distanceComboBox, @@ -112,9 +114,10 @@ public: types::GetDistanceUnitsName); AddRow(distanceUnits_, "Distance", distanceComboBox); - QComboBox* otherComboBox = new QComboBox(self); + auto* otherComboBox = new QFocusedComboBox(self); otherComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + otherComboBox->setFocusPolicy(Qt::StrongFocus); otherUnits_.SetSettingsVariable(unitSettings.other_units()); SCWX_SETTINGS_COMBO_BOX(otherUnits_, otherComboBox, @@ -122,12 +125,19 @@ public: types::GetOtherUnitsName); AddRow(otherUnits_, "Other", otherComboBox); - QSpacerItem* spacer = + auto* spacer = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout_->addItem(spacer, row, 0); + + // NOLINTEND(cppcoreguidelines-owning-memory) } ~Impl() = default; + Impl(const Impl&) = delete; + Impl(Impl&&) = delete; + Impl& operator=(const Impl&) = delete; + Impl& operator=(Impl&&) = delete; + QWidget* contents_; QLayout* layout_; QScrollArea* scrollArea_ {}; @@ -147,6 +157,4 @@ UnitSettingsWidget::UnitSettingsWidget(QWidget* parent) : UnitSettingsWidget::~UnitSettingsWidget() = default; -} // namespace ui -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::ui diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index 8159fb69..81e3a96d 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -23,11 +24,13 @@ #include #include #include -#include +#include #include #include +#include #include #include +#include #include #include #include @@ -40,8 +43,16 @@ #include #include #include +#include #include +#include #include +#include + +#define QT6CT_LIBRARY +#include +#include +#undef QT6CT_LIBRARY namespace scwx { @@ -103,7 +114,8 @@ static const std::unordered_map class SettingsDialogImpl { public: - explicit SettingsDialogImpl(SettingsDialog* self) : + explicit SettingsDialogImpl(SettingsDialog* self, + QMapLibre::Settings& mapSettings) : self_ {self}, radarSiteDialog_ {new RadarSiteDialog(self)}, alertAudioRadarSiteDialog_ {new RadarSiteDialog(self)}, @@ -111,6 +123,7 @@ public: countyDialog_ {new CountyDialog(self)}, wfoDialog_ {new WFODialog(self)}, fontDialog_ {new QFontDialog(self)}, + mapSettings_ {mapSettings}, fontCategoryModel_ {new QStandardItemModel(self)}, settings_ {std::initializer_list { &defaultRadarSite_, @@ -120,6 +133,7 @@ public: &mapboxApiKey_, &mapTilerApiKey_, &theme_, + &themeFile_, &defaultAlertAction_, &clockFormat_, &customStyleDrawLayer_, @@ -129,11 +143,16 @@ public: &nmeaBaudRate_, &nmeaSource_, &warningsProvider_, + &radarSiteThreshold_, &antiAliasingEnabled_, + ¢erOnRadarSelection_, &showMapAttribution_, &showMapCenter_, &showMapLogo_, + &showSmoothedRangeFolding_, &updateNotificationsEnabled_, + &cursorIconAlwaysOn_, + &cursorIconScale_, &debugEnabled_, &alertAudioSoundFile_, &alertAudioLocationMethod_, @@ -181,7 +200,6 @@ public: void SetupTextTab(); void SetupHotkeysTab(); - void ShowColorDialog(QLineEdit* lineEdit, QFrame* frame = nullptr); void UpdateRadarDialogLocation(const std::string& id); void UpdateAlertRadarDialogLocation(const std::string& id); @@ -203,8 +221,7 @@ public: const std::string& value, QLabel* imageLabel); static std::string - RadarSiteLabel(std::shared_ptr& radarSite); - static void SetBackgroundColor(const std::string& value, QFrame* frame); + RadarSiteLabel(std::shared_ptr& radarSite); SettingsDialog* self_; RadarSiteDialog* radarSiteDialog_; @@ -214,6 +231,8 @@ public: WFODialog* wfoDialog_; QFontDialog* fontDialog_; + QMapLibre::Settings& mapSettings_; + QStandardItemModel* fontCategoryModel_; types::FontCategory selectedFontCategory_ {types::FontCategory::Unknown}; @@ -224,6 +243,7 @@ public: manager::PositionManager::Instance()}; std::vector settingsPages_ {}; + AlertPaletteSettingsWidget* alertPaletteSettingsWidget_ {}; HotkeySettingsWidget* hotkeySettingsWidget_ {}; UnitSettingsWidget* unitSettingsWidget_ {}; @@ -242,22 +262,22 @@ public: settings::SettingsInterface nmeaBaudRate_ {}; settings::SettingsInterface nmeaSource_ {}; settings::SettingsInterface theme_ {}; + settings::SettingsInterface themeFile_ {}; settings::SettingsInterface warningsProvider_ {}; + settings::SettingsInterface radarSiteThreshold_ {}; settings::SettingsInterface antiAliasingEnabled_ {}; + settings::SettingsInterface centerOnRadarSelection_ {}; settings::SettingsInterface showMapAttribution_ {}; settings::SettingsInterface showMapCenter_ {}; settings::SettingsInterface showMapLogo_ {}; + settings::SettingsInterface showSmoothedRangeFolding_ {}; settings::SettingsInterface updateNotificationsEnabled_ {}; + settings::SettingsInterface cursorIconAlwaysOn_ {}; + settings::SettingsInterface cursorIconScale_ {}; settings::SettingsInterface debugEnabled_ {}; std::unordered_map> colorTables_ {}; - std::unordered_map> - activeAlertColors_ {}; - std::unordered_map> - inactiveAlertColors_ {}; settings::SettingsInterface alertAudioSoundFile_ {}; settings::SettingsInterface alertAudioLocationMethod_ {}; @@ -288,13 +308,18 @@ public: std::vector settings_; }; -SettingsDialog::SettingsDialog(QWidget* parent) : +SettingsDialog::SettingsDialog(QMapLibre::Settings& mapSettings, + QWidget* parent) : QDialog(parent), - p {std::make_unique(this)}, + p {std::make_unique(this, mapSettings)}, ui(new Ui::SettingsDialog) { ui->setupUi(this); + // Set OK as default + ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok) + ->setDefault(true); + // General p->SetupGeneralTab(); @@ -358,24 +383,23 @@ void SettingsDialogImpl::ConnectSignals() self_, [this]() { alertAudioRadarSiteDialog_->show(); }); - QObject::connect(alertAudioRadarSiteDialog_, - &RadarSiteDialog::accepted, - self_, - [this]() - { - std::string id = - alertAudioRadarSiteDialog_->radar_site(); + QObject::connect( + alertAudioRadarSiteDialog_, + &RadarSiteDialog::accepted, + self_, + [this]() + { + std::string id = alertAudioRadarSiteDialog_->radar_site(); - std::shared_ptr radarSite = - config::RadarSite::Get(id); + std::shared_ptr radarSite = + config::RadarSite::Get(id); - if (radarSite != nullptr) - { - self_->ui->alertAudioRadarSiteComboBox - ->setCurrentText(QString::fromStdString( - RadarSiteLabel(radarSite))); - } - }); + if (radarSite != nullptr) + { + self_->ui->alertAudioRadarSiteComboBox->setCurrentText( + QString::fromStdString(RadarSiteLabel(radarSite))); + } + }); QObject::connect(self_->ui->gpsSourceSelectButton, &QAbstractButton::clicked, @@ -525,6 +549,22 @@ void SettingsDialogImpl::SetupGeneralTab() { settings::GeneralSettings& generalSettings = settings::GeneralSettings::Instance(); + settings::ProductSettings& productSettings = + settings::ProductSettings::Instance(); + + QObject::connect( + self_->ui->themeComboBox, + &QComboBox::currentTextChanged, + self_, + [this](const QString& text) + { + const types::UiStyle style = types::GetUiStyle(text.toStdString()); + const bool themeFileEnabled = style == types::UiStyle::FusionCustom; + + self_->ui->themeFileLineEdit->setEnabled(themeFileEnabled); + self_->ui->themeFileSelectButton->setEnabled(themeFileEnabled); + self_->ui->resetThemeFileButton->setEnabled(themeFileEnabled); + }); theme_.SetSettingsVariable(generalSettings.theme()); SCWX_SETTINGS_COMBO_BOX(theme_, @@ -533,6 +573,63 @@ void SettingsDialogImpl::SetupGeneralTab() types::GetUiStyleName); theme_.SetResetButton(self_->ui->resetThemeButton); + themeFile_.SetSettingsVariable(generalSettings.theme_file()); + themeFile_.SetEditWidget(self_->ui->themeFileLineEdit); + themeFile_.SetResetButton(self_->ui->resetThemeFileButton); + themeFile_.EnableTrimming(); + + QObject::connect( + self_->ui->themeFileSelectButton, + &QAbstractButton::clicked, + self_, + [this]() + { + const settings::GeneralSettings& generalSettings = + settings::GeneralSettings::Instance(); + QString file = generalSettings.theme_file().GetStagedOrValue().c_str(); + + if (file.isEmpty()) + { + const QString appDataPath {QStandardPaths::writableLocation( + QStandardPaths::AppLocalDataLocation)}; + file = appDataPath + "/theme.conf"; + self_->ui->themeFileLineEdit->setText(file); + // setText does not emit the textEdited signal + Q_EMIT self_->ui->themeFileLineEdit->textEdited(file); + } + + const QPalette palette = + Qt6CT::loadColorScheme(file, QApplication::palette()); + QStyle* style = QApplication::style(); + + // WA_DeleteOnClose manages memory + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + auto* dialog = new PaletteEditDialog(palette, style, self_); + dialog->setAttribute(Qt::WA_DeleteOnClose); + + QObject::connect( + dialog, + &QDialog::accepted, + self_, + [dialog]() + { + const QPalette palette = dialog->selectedPalette(); + const settings::GeneralSettings& generalSettings = + settings::GeneralSettings::Instance(); + const QString file = + generalSettings.theme_file().GetStagedOrValue().c_str(); + Qt6CT::createColorScheme(file, palette); + + auto uiStyle = scwx::qt::types::GetUiStyle( + generalSettings.theme().GetValue()); + if (uiStyle == scwx::qt::types::UiStyle::FusionCustom) + { + QApplication::setPalette(palette); + } + }); + dialog->open(); + }); + auto radarSites = config::RadarSite::GetAll(); // Sort radar sites by ID @@ -577,7 +674,7 @@ void SettingsDialogImpl::SetupGeneralTab() [](const std::string& text) -> std::string { // Find the position of location details - size_t pos = text.rfind(" ("); + size_t pos = text.find(" ("); if (pos == std::string::npos) { @@ -607,22 +704,55 @@ void SettingsDialogImpl::SetupGeneralTab() map::GetMapProviderName); mapProvider_.SetResetButton(self_->ui->resetMapProviderButton); + self_->ui->mapboxApiKeyLineEdit->setMapProvider(map::MapProvider::Mapbox); mapboxApiKey_.SetSettingsVariable(generalSettings.mapbox_api_key()); mapboxApiKey_.SetEditWidget(self_->ui->mapboxApiKeyLineEdit); mapboxApiKey_.SetResetButton(self_->ui->resetMapboxApiKeyButton); + mapboxApiKey_.EnableTrimming(); + self_->ui->mapTilerApiKeyLineEdit->setMapProvider( + map::MapProvider::MapTiler); mapTilerApiKey_.SetSettingsVariable(generalSettings.maptiler_api_key()); mapTilerApiKey_.SetEditWidget(self_->ui->mapTilerApiKeyLineEdit); mapTilerApiKey_.SetResetButton(self_->ui->resetMapTilerApiKeyButton); + mapTilerApiKey_.EnableTrimming(); customStyleUrl_.SetSettingsVariable(generalSettings.custom_style_url()); customStyleUrl_.SetEditWidget(self_->ui->customMapUrlLineEdit); customStyleUrl_.SetResetButton(self_->ui->resetCustomMapUrlButton); + customStyleUrl_.SetInvalidTooltip( + "Remove anything following \"?key=\" in the URL"); + customStyleUrl_.EnableTrimming(); customStyleDrawLayer_.SetSettingsVariable( generalSettings.custom_style_draw_layer()); customStyleDrawLayer_.SetEditWidget(self_->ui->customMapLayerLineEdit); customStyleDrawLayer_.SetResetButton(self_->ui->resetCustomMapLayerButton); + QObject::connect( + self_->ui->customMapLayerToolButton, + &QAbstractButton::clicked, + self_, + [this]() + { + // WA_DeleteOnClose manages memory + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + auto* customLayerDialog = new ui::CustomLayerDialog(mapSettings_); + customLayerDialog->setAttribute(Qt::WA_DeleteOnClose); + QObject::connect( + customLayerDialog, + &QDialog::accepted, + self_, + [this, customLayerDialog]() + { + auto newLayer = customLayerDialog->selected_layer(); + self_->ui->customMapLayerLineEdit->setText(newLayer.c_str()); + // setText does not emit the textEdited signal + Q_EMIT + self_->ui->customMapLayerLineEdit->textEdited(newLayer.c_str()); + }); + + customLayerDialog->open(); + }); defaultAlertAction_.SetSettingsVariable( generalSettings.default_alert_action()); @@ -679,15 +809,47 @@ void SettingsDialogImpl::SetupGeneralTab() nmeaSource_.SetSettingsVariable(generalSettings.nmea_source()); nmeaSource_.SetEditWidget(self_->ui->nmeaSourceLineEdit); nmeaSource_.SetResetButton(self_->ui->resetNmeaSourceButton); + nmeaSource_.EnableTrimming(); warningsProvider_.SetSettingsVariable(generalSettings.warnings_provider()); warningsProvider_.SetEditWidget(self_->ui->warningsProviderLineEdit); warningsProvider_.SetResetButton(self_->ui->resetWarningsProviderButton); + warningsProvider_.EnableTrimming(); + + radarSiteThreshold_.SetSettingsVariable( + generalSettings.radar_site_threshold()); + radarSiteThreshold_.SetEditWidget(self_->ui->radarSiteThresholdSpinBox); + radarSiteThreshold_.SetResetButton(self_->ui->resetRadarSiteThresholdButton); + radarSiteThreshold_.SetUnitLabel(self_->ui->radarSiteThresholdUnitLabel); + auto radarSiteThresholdUpdateUnits = [this](const std::string& newValue) + { + const types::DistanceUnits radiusUnits = + types::GetDistanceUnitsFromName(newValue); + const double radiusScale = types::GetDistanceUnitsScale(radiusUnits); + const std::string abbreviation = + types::GetDistanceUnitsAbbreviation(radiusUnits); + + radarSiteThreshold_.SetUnit(radiusScale, abbreviation); + }; + settings::UnitSettings::Instance() + .distance_units() + .RegisterValueStagedCallback(radarSiteThresholdUpdateUnits); + radarSiteThresholdUpdateUnits( + settings::UnitSettings::Instance().distance_units().GetValue()); + + cursorIconScale_.SetSettingsVariable(generalSettings.cursor_icon_scale()); + cursorIconScale_.SetEditWidget(self_->ui->cursorIconScaleSpinBox); + cursorIconScale_.SetResetButton(self_->ui->resetCursorIconScaleButton); antiAliasingEnabled_.SetSettingsVariable( generalSettings.anti_aliasing_enabled()); antiAliasingEnabled_.SetEditWidget(self_->ui->antiAliasingEnabledCheckBox); + centerOnRadarSelection_.SetSettingsVariable( + generalSettings.center_on_radar_selection()); + centerOnRadarSelection_.SetEditWidget( + self_->ui->centerOnRadarSelectionCheckBox); + showMapAttribution_.SetSettingsVariable( generalSettings.show_map_attribution()); showMapAttribution_.SetEditWidget(self_->ui->showMapAttributionCheckBox); @@ -698,11 +860,20 @@ void SettingsDialogImpl::SetupGeneralTab() showMapLogo_.SetSettingsVariable(generalSettings.show_map_logo()); showMapLogo_.SetEditWidget(self_->ui->showMapLogoCheckBox); + showSmoothedRangeFolding_.SetSettingsVariable( + productSettings.show_smoothed_range_folding()); + showSmoothedRangeFolding_.SetEditWidget( + self_->ui->showSmoothedRangeFoldingCheckBox); + updateNotificationsEnabled_.SetSettingsVariable( generalSettings.update_notifications_enabled()); updateNotificationsEnabled_.SetEditWidget( self_->ui->enableUpdateNotificationsCheckBox); + cursorIconAlwaysOn_.SetSettingsVariable( + generalSettings.cursor_icon_always_on()); + cursorIconAlwaysOn_.SetEditWidget(self_->ui->cursorIconAlwaysOnCheckBox); + debugEnabled_.SetSettingsVariable(generalSettings.debug_enabled()); debugEnabled_.SetEditWidget(self_->ui->debugEnabledCheckBox); } @@ -757,6 +928,7 @@ void SettingsDialogImpl::SetupPalettesColorTablesTab() colorTable.SetSettingsVariable(colorTableVariable); colorTable.SetEditWidget(lineEdit); colorTable.SetResetButton(resetButton); + colorTable.EnableTrimming(); colorTableVariable.RegisterValueStagedCallback( [colorTableType, imageLabel](const std::string& value) @@ -784,7 +956,7 @@ void SettingsDialogImpl::SetupPalettesColorTablesTab() QObject::connect(dialog, &QFileDialog::fileSelected, self_, - [this, lineEdit](const QString& file) + [lineEdit](const QString& file) { QString path = QDir::toNativeSeparators(file); @@ -803,123 +975,14 @@ void SettingsDialogImpl::SetupPalettesColorTablesTab() void SettingsDialogImpl::SetupPalettesAlertsTab() { - settings::PaletteSettings& paletteSettings = - settings::PaletteSettings::Instance(); - // Palettes > Alerts - QGridLayout* alertsLayout = - reinterpret_cast(self_->ui->alertsFrame->layout()); + QVBoxLayout* layout = new QVBoxLayout(self_->ui->alertsPalette); - QLabel* phenomenonLabel = new QLabel(QObject::tr("Phenomenon"), self_); - QLabel* activeLabel = new QLabel(QObject::tr("Active"), self_); - QLabel* inactiveLabel = new QLabel(QObject::tr("Inactive"), self_); + alertPaletteSettingsWidget_ = + new AlertPaletteSettingsWidget(self_->ui->hotkeys); + layout->addWidget(alertPaletteSettingsWidget_); - QFont boldFont; - boldFont.setBold(true); - phenomenonLabel->setFont(boldFont); - activeLabel->setFont(boldFont); - inactiveLabel->setFont(boldFont); - - alertsLayout->addWidget(phenomenonLabel, 0, 0); - alertsLayout->addWidget(activeLabel, 0, 1, 1, 4); - alertsLayout->addWidget(inactiveLabel, 0, 5, 1, 4); - - auto& alertPhenomena = settings::PaletteSettings::alert_phenomena(); - - activeAlertColors_.reserve(alertPhenomena.size()); - inactiveAlertColors_.reserve(alertPhenomena.size()); - - int alertsRow = 1; - for (auto& phenomenon : alertPhenomena) - { - QFrame* activeFrame = new QFrame(self_); - QFrame* inactiveFrame = new QFrame(self_); - - QLineEdit* activeEdit = new QLineEdit(self_); - QLineEdit* inactiveEdit = new QLineEdit(self_); - - QToolButton* activeButton = new QToolButton(self_); - QToolButton* inactiveButton = new QToolButton(self_); - QToolButton* activeResetButton = new QToolButton(self_); - QToolButton* inactiveResetButton = new QToolButton(self_); - - activeFrame->setMinimumHeight(24); - activeFrame->setMinimumWidth(24); - activeFrame->setFrameShape(QFrame::Shape::Box); - activeFrame->setFrameShadow(QFrame::Shadow::Plain); - inactiveFrame->setMinimumHeight(24); - inactiveFrame->setMinimumWidth(24); - inactiveFrame->setFrameShape(QFrame::Shape::Box); - inactiveFrame->setFrameShadow(QFrame::Shadow::Plain); - - activeButton->setIcon( - QIcon {":/res/icons/font-awesome-6/palette-solid.svg"}); - inactiveButton->setIcon( - QIcon {":/res/icons/font-awesome-6/palette-solid.svg"}); - activeResetButton->setIcon( - QIcon {":/res/icons/font-awesome-6/rotate-left-solid.svg"}); - inactiveResetButton->setIcon( - QIcon {":/res/icons/font-awesome-6/rotate-left-solid.svg"}); - - alertsLayout->addWidget( - new QLabel(QObject::tr(awips::GetPhenomenonText(phenomenon).c_str()), - self_), - alertsRow, - 0); - alertsLayout->addWidget(activeFrame, alertsRow, 1); - alertsLayout->addWidget(activeEdit, alertsRow, 2); - alertsLayout->addWidget(activeButton, alertsRow, 3); - alertsLayout->addWidget(activeResetButton, alertsRow, 4); - alertsLayout->addWidget(inactiveFrame, alertsRow, 5); - alertsLayout->addWidget(inactiveEdit, alertsRow, 6); - alertsLayout->addWidget(inactiveButton, alertsRow, 7); - alertsLayout->addWidget(inactiveResetButton, alertsRow, 8); - ++alertsRow; - - // Create settings interface - auto activeResult = activeAlertColors_.emplace( - phenomenon, settings::SettingsInterface {}); - auto inactiveResult = inactiveAlertColors_.emplace( - phenomenon, settings::SettingsInterface {}); - auto& activeColor = activeResult.first->second; - auto& inactiveColor = inactiveResult.first->second; - - // Add to settings list - settings_.push_back(&activeColor); - settings_.push_back(&inactiveColor); - - auto& activeSetting = paletteSettings.alert_color(phenomenon, true); - auto& inactiveSetting = paletteSettings.alert_color(phenomenon, false); - - activeColor.SetSettingsVariable(activeSetting); - activeColor.SetEditWidget(activeEdit); - activeColor.SetResetButton(activeResetButton); - - inactiveColor.SetSettingsVariable(inactiveSetting); - inactiveColor.SetEditWidget(inactiveEdit); - inactiveColor.SetResetButton(inactiveResetButton); - - SetBackgroundColor(activeSetting.GetValue(), activeFrame); - SetBackgroundColor(inactiveSetting.GetValue(), inactiveFrame); - - activeSetting.RegisterValueStagedCallback( - [activeFrame](const std::string& value) - { SetBackgroundColor(value, activeFrame); }); - inactiveSetting.RegisterValueStagedCallback( - [inactiveFrame](const std::string& value) - { SetBackgroundColor(value, inactiveFrame); }); - - QObject::connect(activeButton, - &QAbstractButton::clicked, - self_, - [=, this]() - { ShowColorDialog(activeEdit, activeFrame); }); - QObject::connect(inactiveButton, - &QAbstractButton::clicked, - self_, - [=, this]() - { ShowColorDialog(inactiveEdit, inactiveFrame); }); - } + settingsPages_.push_back(alertPaletteSettingsWidget_); } void SettingsDialogImpl::SetupUnitsTab() @@ -953,8 +1016,7 @@ void SettingsDialogImpl::SetupAudioTab() locationMethod == types::LocationMethod::RadarSite; bool countyEntryEnabled = locationMethod == types::LocationMethod::County; - bool wfoEntryEnabled = - locationMethod == types::LocationMethod::WFO; + bool wfoEntryEnabled = locationMethod == types::LocationMethod::WFO; self_->ui->alertAudioLatitudeSpinBox->setEnabled( coordinateEntryEnabled); @@ -972,10 +1034,8 @@ void SettingsDialogImpl::SetupAudioTab() self_->ui->resetAlertAudioRadarSiteButton->setEnabled( radarSiteEntryEnable); - self_->ui->alertAudioRadiusSpinBox->setEnabled( - radiusEntryEnable); - self_->ui->resetAlertAudioRadiusButton->setEnabled( - radiusEntryEnable); + self_->ui->alertAudioRadiusSpinBox->setEnabled(radiusEntryEnable); + self_->ui->resetAlertAudioRadiusButton->setEnabled(radiusEntryEnable); self_->ui->alertAudioCountyLineEdit->setEnabled(countyEntryEnabled); self_->ui->alertAudioCountySelectButton->setEnabled( @@ -992,6 +1052,7 @@ void SettingsDialogImpl::SetupAudioTab() alertAudioSoundFile_.SetSettingsVariable(audioSettings.alert_sound_file()); alertAudioSoundFile_.SetEditWidget(self_->ui->alertAudioSoundLineEdit); alertAudioSoundFile_.SetResetButton(self_->ui->resetAlertAudioSoundButton); + alertAudioSoundFile_.EnableTrimming(); QObject::connect( self_->ui->alertAudioSoundSelectButton, @@ -1073,7 +1134,7 @@ void SettingsDialogImpl::SetupAudioTab() [](const std::string& text) -> std::string { // Find the position of location details - size_t pos = text.rfind(" ("); + size_t pos = text.find(" ("); if (pos == std::string::npos) { @@ -1091,15 +1152,14 @@ void SettingsDialogImpl::SetupAudioTab() alertAudioRadius_.SetSettingsVariable(audioSettings.alert_radius()); alertAudioRadius_.SetEditWidget(self_->ui->alertAudioRadiusSpinBox); - alertAudioRadius_.SetResetButton( - self_->ui->resetAlertAudioRadiusButton); + alertAudioRadius_.SetResetButton(self_->ui->resetAlertAudioRadiusButton); alertAudioRadius_.SetUnitLabel(self_->ui->alertAudioRadiusUnitsLabel); auto alertAudioRadiusUpdateUnits = [this](const std::string& newValue) { - types::DistanceUnits radiusUnits = + const types::DistanceUnits radiusUnits = types::GetDistanceUnitsFromName(newValue); - double radiusScale = types::GetDistanceUnitsScale(radiusUnits); - std::string abbreviation = + const double radiusScale = types::GetDistanceUnitsScale(radiusUnits); + const std::string abbreviation = types::GetDistanceUnitsAbbreviation(radiusUnits); alertAudioRadius_.SetUnit(radiusScale, abbreviation); @@ -1205,15 +1265,12 @@ void SettingsDialogImpl::SetupAudioTab() alertAudioCounty_.SetSettingsVariable(audioSettings.alert_county()); alertAudioCounty_.SetEditWidget(self_->ui->alertAudioCountyLineEdit); alertAudioCounty_.SetResetButton(self_->ui->resetAlertAudioCountyButton); + alertAudioCounty_.EnableTrimming(); - QObject::connect( - self_->ui->alertAudioWFOSelectButton, - &QAbstractButton::clicked, - self_, - [this]() - { - wfoDialog_->show(); - }); + QObject::connect(self_->ui->alertAudioWFOSelectButton, + &QAbstractButton::clicked, + self_, + [this]() { wfoDialog_->show(); }); QObject::connect(wfoDialog_, &WFODialog::accepted, self_, @@ -1232,9 +1289,8 @@ void SettingsDialogImpl::SetupAudioTab() self_, [this](const QString& text) { - std::string wfoName = - config::CountyDatabase::GetWFOName( - text.toStdString()); + std::string wfoName = config::CountyDatabase::GetWFOName( + text.toStdString()); self_->ui->alertAudioWFOLabel->setText( QString::fromStdString(wfoName)); }); @@ -1242,7 +1298,7 @@ void SettingsDialogImpl::SetupAudioTab() alertAudioWFO_.SetSettingsVariable(audioSettings.alert_wfo()); alertAudioWFO_.SetEditWidget(self_->ui->alertAudioWFOLineEdit); alertAudioWFO_.SetResetButton(self_->ui->resetAlertAudioWFOButton); - + alertAudioWFO_.EnableTrimming(); } void SettingsDialogImpl::SetupTextTab() @@ -1384,44 +1440,6 @@ void SettingsDialogImpl::LoadColorTablePreview(const std::string& key, }); } -void SettingsDialogImpl::ShowColorDialog(QLineEdit* lineEdit, QFrame* frame) -{ - QColorDialog* dialog = new QColorDialog(self_); - - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->setOption(QColorDialog::ColorDialogOption::ShowAlphaChannel); - - QColor initialColor(lineEdit->text()); - if (initialColor.isValid()) - { - dialog->setCurrentColor(initialColor); - } - - QObject::connect( - dialog, - &QColorDialog::colorSelected, - self_, - [this, lineEdit, frame](const QColor& color) - { - QString colorName = color.name(QColor::NameFormat::HexArgb); - - logger_->info("Selected color: {}", colorName.toStdString()); - lineEdit->setText(colorName); - - // setText does not emit the textEdited signal - Q_EMIT lineEdit->textEdited(colorName); - }); - - dialog->open(); -} - -void SettingsDialogImpl::SetBackgroundColor(const std::string& value, - QFrame* frame) -{ - frame->setStyleSheet( - QString::fromStdString(fmt::format("background-color: {}", value))); -} - void SettingsDialogImpl::UpdateRadarDialogLocation(const std::string& id) { std::shared_ptr radarSite = config::RadarSite::Get(id); @@ -1444,8 +1462,6 @@ void SettingsDialogImpl::UpdateAlertRadarDialogLocation(const std::string& id) } } - - QFont SettingsDialogImpl::GetSelectedFont() { std::string fontFamily = fontFamilies_.at(selectedFontCategory_) @@ -1480,6 +1496,12 @@ void SettingsDialogImpl::UpdateFontDisplayData() self_->ui->fontStyleLabel->setText(font.styleName()); self_->ui->fontSizeLabel->setText(QString::number(font.pointSizeF())); +#if defined(__APPLE__) + const units::font_size::points fontSize {font.pointSizeF()}; + const units::font_size::pixels fontPixels {fontSize}; + font.setPixelSize(static_cast(fontPixels.value())); +#endif + self_->ui->fontPreviewLabel->setFont(font); if (selectedFontCategory_ != types::FontCategory::Unknown) diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.hpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.hpp index 82f00905..866e1ea8 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.hpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include namespace Ui { @@ -24,7 +25,8 @@ private: Q_DISABLE_COPY(SettingsDialog) public: - explicit SettingsDialog(QWidget* parent = nullptr); + explicit SettingsDialog(QMapLibre::Settings& mapSettings, + QWidget* parent = nullptr); ~SettingsDialog(); private: diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index 88dacbbc..aa12d3b8 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -16,12 +16,6 @@ - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - 0 @@ -122,7 +116,7 @@ - 3 + 0 @@ -136,8 +130,8 @@ 0 0 - 274 - 691 + 513 + 845 @@ -159,318 +153,48 @@ 0 - - - - MapTiler API Key + + + + Qt::FocusPolicy::StrongFocus - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - - - - Default Time Zone - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + Qt::FocusPolicy::StrongFocus - + + + Qt::FocusPolicy::StrongFocus + + - - + + - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + Radar Site Threshold - - + + - Map Provider + Custom Map URL - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - - - - - - - ... - - - - - - - QLineEdit::EchoMode::Password - - - - - - - GPS Plugin - - - - - - - GPS Baud Rate - - - - - - - - - - ... - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - Grid Width - - - - - - - - - - - - - - - - GPS Source - - - - - - - - - - Default Radar Site - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - Grid Height - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - Theme - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - Mapbox API Key - - - - - - - Clock Format - - - - - - - QLineEdit::EchoMode::Password - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - + Warnings Provider - - - - 1 - - - 999999999 - - - - - - - Default Alert Action - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - @@ -482,16 +206,60 @@ + + + + GPS Source + + + - - - - - + + - Custom Map URL + MapTiler API Key + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + Default Radar Site @@ -502,6 +270,20 @@ + + + + Qt::FocusPolicy::StrongFocus + + + + + + + Mapbox API Key + + + @@ -513,6 +295,38 @@ + + + + Clock Format + + + + + + + Qt::FocusPolicy::StrongFocus + + + + + + + Qt::FocusPolicy::StrongFocus + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + @@ -524,6 +338,359 @@ + + + + Qt::FocusPolicy::StrongFocus + + + 1 + + + 999999999 + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + Theme File + + + + + + + Grid Width + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + Grid Height + + + + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + ... + + + + + + + Default Time Zone + + + + + + + Qt::FocusPolicy::StrongFocus + + + + + + + + + + Qt::FocusPolicy::StrongFocus + + + + + + + ... + + + + + + + Map Provider + + + + + + + QLineEdit::EchoMode::Password + + + + + + + Qt::FocusPolicy::StrongFocus + + + + + + + Theme + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::FocusPolicy::StrongFocus + + + Set to 0 to disable + + + + + + 0 + + + -10000.000000000000000 + + + 10000.000000000000000 + + + QAbstractSpinBox::StepType::DefaultStepType + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + QLineEdit::EchoMode::Password + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + GPS Baud Rate + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + Default Alert Action + + + + + + + + + + ... + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + GPS Plugin + + + + + + + ... + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + Multi-Pane Cursor Size + + + + + + + Qt::FocusPolicy::StrongFocus + + + 1 + + + 1.000000000000000 + + + 5.000000000000000 + + + 0.100000000000000 + + + QAbstractSpinBox::StepType::DefaultStepType + + + + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + @@ -534,6 +701,26 @@ + + + + Center Map on Radar Selection + + + + + + + false + + + + + + Multi-Pane Cursor Marker Always On + + + @@ -555,6 +742,13 @@ + + + + Show Range Folding when Smoothing Radar Data + + + @@ -610,8 +804,8 @@ 0 0 - 98 - 28 + 505 + 384 @@ -634,49 +828,10 @@ - + Alerts - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - Qt::Orientation::Vertical - - - - 20 - 239 - - - - - @@ -759,7 +914,10 @@ - + + + Qt::FocusPolicy::StrongFocus + 4 @@ -775,7 +933,13 @@ - + + + Qt::FocusPolicy::StrongFocus + + + Set to 0 to disable + 2 @@ -824,7 +988,10 @@ - + + + Qt::FocusPolicy::StrongFocus + 4 @@ -921,16 +1088,23 @@ - + + + Qt::FocusPolicy::StrongFocus + + - + 0 0 + + Qt::FocusPolicy::StrongFocus + @@ -1017,12 +1191,6 @@ - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - 0 @@ -1051,12 +1219,6 @@ - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - 0 @@ -1196,12 +1358,6 @@ - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - 0 @@ -1241,7 +1397,10 @@ - + + + Qt::FocusPolicy::StrongFocus + 999 @@ -1266,7 +1425,11 @@ - + + + Qt::FocusPolicy::StrongFocus + + @@ -1321,6 +1484,28 @@ + + + scwx::qt::ui::QApiKeyEdit + QLineEdit +
scwx/qt/ui/api_key_edit_widget.hpp
+
+ + QFocusedDoubleSpinBox + QDoubleSpinBox +
scwx/qt/ui/widgets/focused_double_spin_box.hpp
+
+ + QFocusedSpinBox + QSpinBox +
scwx/qt/ui/widgets/focused_spin_box.hpp
+
+ + QFocusedComboBox + QComboBox +
scwx/qt/ui/widgets/focused_combo_box.hpp
+
+
diff --git a/scwx-qt/source/scwx/qt/ui/setup/audio_codec_page.cpp b/scwx-qt/source/scwx/qt/ui/setup/audio_codec_page.cpp index dd86e1f6..364c9959 100644 --- a/scwx-qt/source/scwx/qt/ui/setup/audio_codec_page.cpp +++ b/scwx-qt/source/scwx/qt/ui/setup/audio_codec_page.cpp @@ -124,6 +124,9 @@ void AudioCodecPage::Impl::SetInstructionsLabelText() self_, [](const QString& link) { QDesktopServices::openUrl(QUrl {link}); }); +#elif defined(__APPLE__) + instructionsLabel_->setText(tr( + "Please see the instructions for your Mac for installing media codecs.")); #else instructionsLabel_->setText( tr("Please see the instructions for your Linux distribution for " diff --git a/scwx-qt/source/scwx/qt/ui/setup/map_provider_page.cpp b/scwx-qt/source/scwx/qt/ui/setup/map_provider_page.cpp index fc0c9ef1..b4d7038a 100644 --- a/scwx-qt/source/scwx/qt/ui/setup/map_provider_page.cpp +++ b/scwx-qt/source/scwx/qt/ui/setup/map_provider_page.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -18,13 +19,7 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace ui -{ -namespace setup +namespace scwx::qt::ui::setup { static const std::unordered_map kUrl_ { @@ -33,8 +28,8 @@ static const std::unordered_map kUrl_ { struct MapProviderGroup { - QLabel* apiKeyLabel_ {}; - QLineEdit* apiKeyEdit_ {}; + QLabel* apiKeyLabel_ {}; + QApiKeyEdit* apiKeyEdit_ {}; }; class MapProviderPage::Impl @@ -45,7 +40,9 @@ public: void SelectMapProvider(const QString& text); void SelectMapProvider(map::MapProvider mapProvider); - void SetupMapProviderGroup(MapProviderGroup& group, int row); + void SetupMapProviderGroup(const map::MapProvider mapProvider, + MapProviderGroup& group, + int row); void SetupSettingsInterface(); static void SetGroupVisible(MapProviderGroup& group, bool visible); @@ -122,8 +119,8 @@ MapProviderPage::MapProviderPage(QWidget* parent) : p->buttonLayout_->addItem(p->buttonSpacer_); p->buttonFrame_->setLayout(p->buttonLayout_); - p->SetupMapProviderGroup(p->mapboxGroup_, 1); - p->SetupMapProviderGroup(p->maptilerGroup_, 2); + p->SetupMapProviderGroup(map::MapProvider::Mapbox, p->mapboxGroup_, 1); + p->SetupMapProviderGroup(map::MapProvider::MapTiler, p->maptilerGroup_, 2); // Overall layout p->layout_ = new QVBoxLayout(this); @@ -159,11 +156,12 @@ MapProviderPage::MapProviderPage(QWidget* parent) : MapProviderPage::~MapProviderPage() = default; -void MapProviderPage::Impl::SetupMapProviderGroup(MapProviderGroup& group, - int row) +void MapProviderPage::Impl::SetupMapProviderGroup( + const map::MapProvider provider, MapProviderGroup& group, int row) { group.apiKeyLabel_ = new QLabel(self_); - group.apiKeyEdit_ = new QLineEdit(self_); + group.apiKeyEdit_ = new QApiKeyEdit(self_); + group.apiKeyEdit_->setMapProvider(provider); group.apiKeyLabel_->setText(tr("API Key")); @@ -171,7 +169,7 @@ void MapProviderPage::Impl::SetupMapProviderGroup(MapProviderGroup& group, mapProviderLayout_->addWidget(group.apiKeyEdit_, row, 1, 1, 1); QObject::connect(group.apiKeyEdit_, - &QLineEdit::textChanged, + &QApiKeyEdit::textChanged, self_, &QWizardPage::completeChanged); } @@ -209,9 +207,11 @@ void MapProviderPage::Impl::SetupSettingsInterface() mapProvider_.SetEditWidget(mapProviderComboBox_); mapboxApiKey_.SetSettingsVariable(generalSettings.mapbox_api_key()); + mapboxApiKey_.EnableTrimming(); mapboxApiKey_.SetEditWidget(mapboxGroup_.apiKeyEdit_); mapTilerApiKey_.SetSettingsVariable(generalSettings.maptiler_api_key()); + mapTilerApiKey_.EnableTrimming(); mapTilerApiKey_.SetEditWidget(maptilerGroup_.apiKeyEdit_); } @@ -290,7 +290,4 @@ bool MapProviderPage::IsRequired() return (mapboxApiKey.size() <= 1 && maptilerApiKey.size() <= 1); } -} // namespace setup -} // namespace ui -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::ui::setup diff --git a/scwx-qt/source/scwx/qt/ui/setup/setup_wizard.cpp b/scwx-qt/source/scwx/qt/ui/setup/setup_wizard.cpp index 92be250f..9b265bd4 100644 --- a/scwx-qt/source/scwx/qt/ui/setup/setup_wizard.cpp +++ b/scwx-qt/source/scwx/qt/ui/setup/setup_wizard.cpp @@ -22,6 +22,9 @@ class SetupWizard::Impl public: explicit Impl() = default; ~Impl() = default; + + bool mapProviderPageIsRequired_ {MapProviderPage::IsRequired()}; + bool audioCodecPageIsRequired_ {AudioCodecPage::IsRequired()}; }; SetupWizard::SetupWizard(QWidget* parent) : @@ -66,14 +69,14 @@ int SetupWizard::nextId() const { case static_cast(Page::MapProvider): case static_cast(Page::MapLayout): - if (MapProviderPage::IsRequired()) + if (p->mapProviderPageIsRequired_) { return nextId; } break; case static_cast(Page::AudioCodec): - if (AudioCodecPage::IsRequired()) + if (p->audioCodecPageIsRequired_) { return nextId; } diff --git a/scwx-qt/source/scwx/qt/ui/update_dialog.cpp b/scwx-qt/source/scwx/qt/ui/update_dialog.cpp index 0ca61a18..edb0396e 100644 --- a/scwx-qt/source/scwx/qt/ui/update_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/update_dialog.cpp @@ -42,13 +42,19 @@ public: UpdateDialog::UpdateDialog(QWidget* parent) : QDialog(parent), p {std::make_unique(this)}, ui(new Ui::UpdateDialog) { +#if !defined(__APPLE__) + static constexpr int titleFontSize = 12; +#else + static constexpr int titleFontSize = 16; +#endif + ui->setupUi(this); - int titleFontId = + const int titleFontId = manager::FontManager::Instance().GetFontId(types::Font::din1451alt_g); - QString titleFontFamily = + const QString titleFontFamily = QFontDatabase::applicationFontFamilies(titleFontId).at(0); - QFont titleFont(titleFontFamily, 12); + const QFont titleFont(titleFontFamily, titleFontSize); ui->bannerLabel->setFont(titleFont); ui->releaseNotesText->setOpenExternalLinks(true); @@ -85,7 +91,14 @@ void UpdateDialog::UpdateReleaseInfo(const std::string& latestVersion, void UpdateDialog::Impl::HandleAsset(const types::gh::ReleaseAsset& asset) { #if defined(_WIN32) - if (asset.name_.ends_with(".msi")) + +# if defined(_M_AMD64) + static constexpr std::string assetSuffix = "-x64.msi"; +# else + static constexpr std::string assetSuffix = "-arm64.msi"; +# endif + + if (asset.name_.ends_with(assetSuffix)) { self_->ui->installUpdateButton->setVisible(true); installUrl_ = asset.browserDownloadUrl_; diff --git a/scwx-qt/source/scwx/qt/ui/update_dialog.ui b/scwx-qt/source/scwx/qt/ui/update_dialog.ui index 5aa8e054..84540fe9 100644 --- a/scwx-qt/source/scwx/qt/ui/update_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/update_dialog.ui @@ -16,12 +16,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -56,12 +50,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -113,12 +101,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - 0 @@ -149,7 +131,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -162,10 +144,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Ok diff --git a/scwx-qt/source/scwx/qt/ui/wfo_dialog.ui b/scwx-qt/source/scwx/qt/ui/wfo_dialog.ui index 26623489..cfb59664 100644 --- a/scwx-qt/source/scwx/qt/ui/wfo_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/wfo_dialog.ui @@ -29,12 +29,6 @@ - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - 0 diff --git a/scwx-qt/source/scwx/qt/ui/widgets/focused_combo_box.hpp b/scwx-qt/source/scwx/qt/ui/widgets/focused_combo_box.hpp new file mode 100644 index 00000000..619bcd89 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/widgets/focused_combo_box.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +class QFocusedComboBox : public QComboBox +{ + Q_OBJECT + +public: + using QComboBox::QComboBox; + +protected: + void wheelEvent(QWheelEvent* event) override + { + if (hasFocus()) + { + QComboBox::wheelEvent(event); + } + else + { + event->ignore(); + } + } +}; diff --git a/scwx-qt/source/scwx/qt/ui/widgets/focused_double_spin_box.hpp b/scwx-qt/source/scwx/qt/ui/widgets/focused_double_spin_box.hpp new file mode 100644 index 00000000..f4fe4f9f --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/widgets/focused_double_spin_box.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +class QFocusedDoubleSpinBox : public QDoubleSpinBox +{ + Q_OBJECT + +public: + using QDoubleSpinBox::QDoubleSpinBox; + +protected: + void wheelEvent(QWheelEvent* event) override + { + if (hasFocus()) + { + QDoubleSpinBox::wheelEvent(event); + } + else + { + event->ignore(); + } + } +}; diff --git a/scwx-qt/source/scwx/qt/ui/widgets/focused_spin_box.hpp b/scwx-qt/source/scwx/qt/ui/widgets/focused_spin_box.hpp new file mode 100644 index 00000000..5d120386 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/widgets/focused_spin_box.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +class QFocusedSpinBox : public QSpinBox +{ + Q_OBJECT + +public: + using QSpinBox::QSpinBox; + +protected: + void wheelEvent(QWheelEvent* event) override + { + if (hasFocus()) + { + QSpinBox::wheelEvent(event); + } + else + { + event->ignore(); + } + } +}; diff --git a/scwx-qt/source/scwx/qt/util/color.cpp b/scwx-qt/source/scwx/qt/util/color.cpp index 6e193dc9..bdd3f6c2 100644 --- a/scwx-qt/source/scwx/qt/util/color.cpp +++ b/scwx-qt/source/scwx/qt/util/color.cpp @@ -1,6 +1,7 @@ #include #include +#include #include namespace scwx @@ -12,8 +13,6 @@ namespace util namespace color { -static const std::string logPrefix_ = "scwx::qt::util::color"; - std::string ToArgbString(const boost::gil::rgba8_pixel_t& color) { return fmt::format( @@ -38,6 +37,12 @@ boost::gil::rgba32f_pixel_t ToRgba32fPixelT(const std::string& argbString) rgba8Pixel[3] / 255.0f}; } +bool ValidateArgbString(const std::string& argbString) +{ + static constexpr LazyRE2 re = {"#[0-9A-Fa-f]{8}"}; + return RE2::FullMatch(argbString, *re); +} + } // namespace color } // namespace util } // namespace qt diff --git a/scwx-qt/source/scwx/qt/util/color.hpp b/scwx-qt/source/scwx/qt/util/color.hpp index 73ca07f1..6d90fe56 100644 --- a/scwx-qt/source/scwx/qt/util/color.hpp +++ b/scwx-qt/source/scwx/qt/util/color.hpp @@ -39,6 +39,15 @@ boost::gil::rgba8_pixel_t ToRgba8PixelT(const std::string& argbString); */ boost::gil::rgba32f_pixel_t ToRgba32fPixelT(const std::string& argbString); +/** + * Validates an ARGB string used by Qt libraries. + * + * @param argbString + * + * @return Validity of ARGB string + */ +bool ValidateArgbString(const std::string& argbString); + } // namespace color } // namespace util } // namespace qt diff --git a/scwx-qt/source/scwx/qt/util/file.cpp b/scwx-qt/source/scwx/qt/util/file.cpp index b129e6ce..7e126345 100644 --- a/scwx-qt/source/scwx/qt/util/file.cpp +++ b/scwx-qt/source/scwx/qt/util/file.cpp @@ -12,8 +12,6 @@ namespace qt namespace util { -static const std::string logPrefix_ = "scwx::qt::util::file"; - std::unique_ptr OpenFile(const std::string& filename, std::ios_base::openmode mode) { diff --git a/scwx-qt/source/scwx/qt/util/geographic_lib.cpp b/scwx-qt/source/scwx/qt/util/geographic_lib.cpp index bfaf408f..1e5fa5e7 100644 --- a/scwx-qt/source/scwx/qt/util/geographic_lib.cpp +++ b/scwx-qt/source/scwx/qt/util/geographic_lib.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -289,6 +290,25 @@ bool AreaInRangeOfPoint(const std::vector& area, return GetDistanceAreaPoint(area, point) <= distance; } +units::length::meters +GetRadarBeamAltititude(units::length::meters range, + units::angle::degrees elevation, + units::length::meters height) +{ + static const units::length::meters earthRadius {6367444 * 4 / 3}; + + height += earthRadius; + + const double elevationRadians = + units::angle::radians(elevation).value(); + const auto altitudeSquared = + (range * range + height * height + + 2 * range * height * std::sin(elevationRadians)); + + return units::length::meters(std::sqrt(altitudeSquared.value())) - + earthRadius; +} + } // namespace GeographicLib } // namespace util } // namespace qt diff --git a/scwx-qt/source/scwx/qt/util/geographic_lib.hpp b/scwx-qt/source/scwx/qt/util/geographic_lib.hpp index 5038d9a9..d34f2aeb 100644 --- a/scwx-qt/source/scwx/qt/util/geographic_lib.hpp +++ b/scwx-qt/source/scwx/qt/util/geographic_lib.hpp @@ -121,6 +121,20 @@ bool AreaInRangeOfPoint(const std::vector& area, const common::Coordinate& point, const units::length::meters distance); +/** + * Get the altitude of the radar beam at a given distance, elevation and height + * + * @param [in] range The range to the radar site + * @param [in] elevation The elevation of the radar site + * @param [in] height The height of the radar site + * + * @return The altitude of the radar at that range + */ +units::length::meters +GetRadarBeamAltititude(units::length::meters range, + units::angle::degrees elevation, + units::length::meters height); + } // namespace GeographicLib } // namespace util } // namespace qt diff --git a/scwx-qt/source/scwx/qt/util/imgui.cpp b/scwx-qt/source/scwx/qt/util/imgui.cpp index 64076dab..1bb8af3c 100644 --- a/scwx-qt/source/scwx/qt/util/imgui.cpp +++ b/scwx-qt/source/scwx/qt/util/imgui.cpp @@ -34,7 +34,7 @@ void ImGui::DrawTooltip(const std::string& hoverText) if (::ImGui::BeginTooltip()) { - ::ImGui::PushFont(tooltipFont->font()); + ::ImGui::PushFont(tooltipFont.first->font(), tooltipFont.second.value()); ::ImGui::TextUnformatted(hoverText.c_str()); ::ImGui::PopFont(); ::ImGui::EndTooltip(); diff --git a/scwx-qt/source/scwx/qt/util/json.cpp b/scwx-qt/source/scwx/qt/util/json.cpp index 7bf0d23a..d508a224 100644 --- a/scwx-qt/source/scwx/qt/util/json.cpp +++ b/scwx-qt/source/scwx/qt/util/json.cpp @@ -1,41 +1,19 @@ #include +#include #include -#include - -#include -#include #include #include -namespace scwx -{ -namespace qt -{ -namespace util -{ -namespace json +namespace scwx::qt::util::json { static const std::string logPrefix_ = "scwx::qt::util::json"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -/* Adapted from: - * https://www.boost.org/doc/libs/1_77_0/libs/json/doc/html/json/examples.html#json.examples.pretty - * - * Copyright (c) 2019, 2020 Vinnie Falco - * Copyright (c) 2020 Krystian Stasiowski - * Distributed under the Boost Software License, Version 1.0. (See - * http://www.boost.org/LICENSE_1_0.txt) - */ -static void PrettyPrintJson(std::ostream& os, - boost::json::value const& jv, - std::string* indent = nullptr); - static boost::json::value ReadJsonFile(QFile& file); -static boost::json::value ReadJsonStream(std::istream& is); -boost::json::value ReadJsonFile(const std::string& path) +boost::json::value ReadJsonQFile(const std::string& path) { boost::json::value json; @@ -46,8 +24,7 @@ boost::json::value ReadJsonFile(const std::string& path) } else { - std::ifstream ifs {path}; - json = ReadJsonStream(ifs); + json = ::scwx::util::json::ReadJsonFile(path); } return json; @@ -65,7 +42,7 @@ static boost::json::value ReadJsonFile(QFile& file) std::string jsonSource = jsonStream.readAll().toStdString(); std::istringstream is {jsonSource}; - json = ReadJsonStream(is); + json = ::scwx::util::json::ReadJsonStream(is); file.close(); } @@ -78,147 +55,4 @@ static boost::json::value ReadJsonFile(QFile& file) return json; } -static boost::json::value ReadJsonStream(std::istream& is) -{ - std::string line; - - boost::json::stream_parser p; - boost::system::error_code ec; - - while (std::getline(is, line)) - { - p.write(line, ec); - if (ec) - { - logger_->warn("{}", ec.message()); - return nullptr; - } - } - - p.finish(ec); - if (ec) - { - logger_->warn("{}", ec.message()); - return nullptr; - } - - return p.release(); -} - -void WriteJsonFile(const std::string& path, - const boost::json::value& json, - bool prettyPrint) -{ - std::ofstream ofs {path}; - - if (!ofs.is_open()) - { - logger_->warn("Cannot write JSON file: \"{}\"", path); - } - else - { - if (prettyPrint) - { - PrettyPrintJson(ofs, json); - } - else - { - ofs << json; - } - ofs.close(); - } -} - -static void PrettyPrintJson(std::ostream& os, - boost::json::value const& jv, - std::string* indent) -{ - std::string indent_; - if (!indent) - indent = &indent_; - switch (jv.kind()) - { - case boost::json::kind::object: - { - os << "{\n"; - indent->append(4, ' '); - auto const& obj = jv.get_object(); - if (!obj.empty()) - { - auto it = obj.begin(); - for (;;) - { - os << *indent << boost::json::serialize(it->key()) << " : "; - PrettyPrintJson(os, it->value(), indent); - if (++it == obj.end()) - break; - os << ",\n"; - } - } - os << "\n"; - indent->resize(indent->size() - 4); - os << *indent << "}"; - break; - } - - case boost::json::kind::array: - { - os << "[\n"; - indent->append(4, ' '); - auto const& arr = jv.get_array(); - if (!arr.empty()) - { - auto it = arr.begin(); - for (;;) - { - os << *indent; - PrettyPrintJson(os, *it, indent); - if (++it == arr.end()) - break; - os << ",\n"; - } - } - os << "\n"; - indent->resize(indent->size() - 4); - os << *indent << "]"; - break; - } - - case boost::json::kind::string: - { - os << boost::json::serialize(jv.get_string()); - break; - } - - case boost::json::kind::uint64: - os << jv.get_uint64(); - break; - - case boost::json::kind::int64: - os << jv.get_int64(); - break; - - case boost::json::kind::double_: - os << jv.get_double(); - break; - - case boost::json::kind::bool_: - if (jv.get_bool()) - os << "true"; - else - os << "false"; - break; - - case boost::json::kind::null: - os << "null"; - break; - } - - if (indent->empty()) - os << "\n"; -} - -} // namespace json -} // namespace util -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::util::json diff --git a/scwx-qt/source/scwx/qt/util/json.hpp b/scwx-qt/source/scwx/qt/util/json.hpp index bbf497f4..9dd09810 100644 --- a/scwx-qt/source/scwx/qt/util/json.hpp +++ b/scwx-qt/source/scwx/qt/util/json.hpp @@ -1,7 +1,5 @@ #pragma once -#include - #include namespace scwx @@ -13,10 +11,7 @@ namespace util namespace json { -boost::json::value ReadJsonFile(const std::string& path); -void WriteJsonFile(const std::string& path, - const boost::json::value& json, - bool prettyPrint = true); +boost::json::value ReadJsonQFile(const std::string& path); } // namespace json } // namespace util diff --git a/scwx-qt/source/scwx/qt/util/maplibre.cpp b/scwx-qt/source/scwx/qt/util/maplibre.cpp index 3414af41..6a4c7d55 100644 --- a/scwx-qt/source/scwx/qt/util/maplibre.cpp +++ b/scwx-qt/source/scwx/qt/util/maplibre.cpp @@ -1,7 +1,9 @@ #include #include +#include #include +#include namespace scwx { @@ -46,7 +48,10 @@ glm::vec2 GetMapScale(const QMapLibre::CustomLayerRenderParameters& params) bool IsPointInPolygon(const std::vector& vertices, const glm::vec2& point) { + // All members of these unions are floats, so no type safety violation + // NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) bool inPolygon = true; + bool allSame = true; // For each vertex, assume counterclockwise order for (std::size_t i = 0; i < vertices.size(); ++i) @@ -55,6 +60,8 @@ bool IsPointInPolygon(const std::vector& vertices, const auto& p2 = (i == vertices.size() - 1) ? vertices[0] : vertices[i + 1]; + allSame = allSame && p1.x == p2.x && p1.y == p2.y; + // Test which side of edge point lies on const float a = -(p2.y - p1.y); const float b = p2.x - p1.x; @@ -70,7 +77,14 @@ bool IsPointInPolygon(const std::vector& vertices, } } + if (allSame) + { + inPolygon = vertices.size() > 0 && vertices[0].x == point.x && + vertices[0].y == point.y; + } + return inPolygon; + // NOLINTEND(cppcoreguidelines-pro-type-union-access) } glm::vec2 LatLongToScreenCoordinate(const QMapLibre::Coordinate& coordinate) @@ -108,6 +122,44 @@ void SetMapStyleUrl(const std::shared_ptr& mapContext, } } +std::string FindMapSymbologyLayer(const QStringList& styleLayers, + const std::vector& drawBelow) +{ + std::string before = "ferry"; + + for (const QString& qlayer : styleLayers) + { + const std::string layer = qlayer.toStdString(); + + // Draw below layers defined in map style + auto it = + std::ranges::find_if(drawBelow, + [&layer](const std::string& styleLayer) -> bool + { + // Perform case-insensitive matching + const RE2 re {"(?i)" + styleLayer}; + if (re.ok()) + { + return RE2::FullMatch(layer, re); + } + else + { + // Fall back to basic comparison if RE + // doesn't compile + return layer == styleLayer; + } + }); + + if (it != drawBelow.cend()) + { + before = layer; + break; + } + } + + return before; +} + } // namespace maplibre } // namespace util } // namespace qt diff --git a/scwx-qt/source/scwx/qt/util/maplibre.hpp b/scwx-qt/source/scwx/qt/util/maplibre.hpp index 7c2eb58b..c03be113 100644 --- a/scwx-qt/source/scwx/qt/util/maplibre.hpp +++ b/scwx-qt/source/scwx/qt/util/maplibre.hpp @@ -37,6 +37,18 @@ glm::vec2 LatLongToScreenCoordinate(const QMapLibre::Coordinate& coordinate); void SetMapStyleUrl(const std::shared_ptr& mapContext, const std::string& url); +/** + * @brief Find the first layer which should be drawn above the radar products + * + * @param [in] styleLayers The layers of the style + * @param [in] drawBelow A list of RE2 compatible regex's describing the layers + * to draw below + * + * @return The first layer to be drawn above the radar products + */ +std::string FindMapSymbologyLayer(const QStringList& styleLayers, + const std::vector& drawBelow); + } // namespace maplibre } // namespace util } // namespace qt diff --git a/scwx-qt/source/scwx/qt/util/network.cpp b/scwx-qt/source/scwx/qt/util/network.cpp index 2e8d442f..a0a782f8 100644 --- a/scwx-qt/source/scwx/qt/util/network.cpp +++ b/scwx-qt/source/scwx/qt/util/network.cpp @@ -17,14 +17,15 @@ std::string NormalizeUrl(const std::string& urlString) std::string normalizedUrl; // Normalize URL string - QUrl url = QUrl::fromUserInput(QString::fromStdString(urlString)); + QString trimmedUrlString = QString::fromStdString(urlString).trimmed(); + QUrl url = QUrl::fromUserInput(trimmedUrlString); if (url.isLocalFile()) { normalizedUrl = QDir::toNativeSeparators(url.toLocalFile()).toStdString(); } else { - normalizedUrl = urlString; + normalizedUrl = trimmedUrlString.toStdString(); } return normalizedUrl; diff --git a/scwx-qt/source/scwx/qt/util/q_color_modulate.cpp b/scwx-qt/source/scwx/qt/util/q_color_modulate.cpp new file mode 100644 index 00000000..c205ce88 --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/q_color_modulate.cpp @@ -0,0 +1,64 @@ +#include + +#include +#include +#include +#include +#include + +namespace scwx::qt::util +{ + +void modulateColors_(QImage& image, const QColor& color) +{ + for (int y = 0; y < image.height(); ++y) + { + QRgb* line = reinterpret_cast(image.scanLine(y)); + for (int x = 0; x < image.width(); ++x) + { + // This is pulled from Qt Documentation + // https://doc.qt.io/qt-6/qimage.html#scanLine + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + QRgb& rgb = line[x]; + /* clang-format off + * NOLINTBEGIN(cppcoreguidelines-narrowing-conversions, bugprone-narrowing-conversions) + * qRed/qGreen/qBlue/qAlpha return values 0-255, handlable by float + * redF/greenF/blueF/alphaF are all 0-1, so output is 0-255 + * Rounding is fine for this. + * clang-format on + */ + const int red = qRed(rgb) * color.redF(); + const int green = qGreen(rgb) * color.greenF(); + const int blue = qBlue(rgb) * color.blueF(); + const int alpha = qAlpha(rgb) * color.alphaF(); + /* clang-format off + * NOLINTEND(cppcoreguidelines-narrowing-conversions, bugprone-narrowing-conversions) + * clang-format on + */ + + rgb = qRgba(red, green, blue, alpha); + } + } +} + +QImage modulateColors(const QImage& image, const QColor& color) +{ + QImage copy = image.copy(); + modulateColors_(copy, color); + return copy; +} + +QPixmap modulateColors(const QPixmap& pixmap, const QColor& color) +{ + QImage image = pixmap.toImage(); + modulateColors_(image, color); + return QPixmap::fromImage(image); +} + +QIcon modulateColors(const QIcon& icon, const QSize& size, const QColor& color) +{ + const QPixmap pixmap = modulateColors(icon.pixmap(size), color); + return QIcon(pixmap); +} + +} // namespace scwx::qt::util diff --git a/scwx-qt/source/scwx/qt/util/q_color_modulate.hpp b/scwx-qt/source/scwx/qt/util/q_color_modulate.hpp new file mode 100644 index 00000000..35326293 --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/q_color_modulate.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace scwx::qt::util +{ + +QImage modulateColors(const QImage& image, const QColor& color); +QPixmap modulateColors(const QPixmap& pixmap, const QColor& color); +QIcon modulateColors(const QIcon& icon, const QSize& size, const QColor& color); + +} // namespace scwx::qt::util diff --git a/scwx-qt/source/scwx/qt/util/q_file_buffer.cpp b/scwx-qt/source/scwx/qt/util/q_file_buffer.cpp index 5d55361c..1808d283 100644 --- a/scwx-qt/source/scwx/qt/util/q_file_buffer.cpp +++ b/scwx-qt/source/scwx/qt/util/q_file_buffer.cpp @@ -224,6 +224,9 @@ QFileBuffer::pos_type QFileBuffer::seekoff(off_type off, break; } + default: + logger_->error("Got invalid seekdir value"); + break; } if (newPos != static_cast(-1)) diff --git a/scwx-qt/source/scwx/qt/util/queue_counter.cpp b/scwx-qt/source/scwx/qt/util/queue_counter.cpp new file mode 100644 index 00000000..39b9fb9d --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/queue_counter.cpp @@ -0,0 +1,49 @@ +#include + +#include + +namespace scwx::qt::util +{ + +class QueueCounter::Impl +{ +public: + explicit Impl(size_t maxCount) : maxCount_ {maxCount} {} + + const size_t maxCount_; + boost::atomic count_ {0}; +}; + +QueueCounter::QueueCounter(size_t maxCount) : + p {std::make_unique(maxCount)} +{ +} + +QueueCounter::~QueueCounter() = default; + +bool QueueCounter::add() +{ + const size_t count = p->count_.fetch_add(1); + // Must be >= (not ==) to avoid race conditions + if (count >= p->maxCount_) + { + p->count_.fetch_sub(1); + return false; + } + else + { + return true; + } +} + +void QueueCounter::remove() +{ + p->count_.fetch_sub(1); +} + +bool QueueCounter::is_lock_free() +{ + return p->count_.is_lock_free(); +} + +} // namespace scwx::qt::util diff --git a/scwx-qt/source/scwx/qt/util/queue_counter.hpp b/scwx-qt/source/scwx/qt/util/queue_counter.hpp new file mode 100644 index 00000000..471bd645 --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/queue_counter.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +namespace scwx::qt::util +{ + +class QueueCounter +{ +public: + /** + * Counts the number of items in a queue, and prevents it from exceeding a + * count in a thread safe manor. This is lock free, assuming + * std::atomic supports lock free fetch_add and fetch_sub. + */ + + /** + * Construct a QueueCounter with a given maximum count + * + * @param maxCount The maximum number of items in the queue + */ + explicit QueueCounter(size_t maxCount); + + ~QueueCounter(); + QueueCounter(const QueueCounter&) = delete; + QueueCounter(QueueCounter&&) = delete; + QueueCounter& operator=(const QueueCounter&) = delete; + QueueCounter& operator=(QueueCounter&&) = delete; + + /** + * Called before adding an item. If it returns true, it is ok to add. If it + * returns false, it should not be added + * + * @return true if it is ok to add, false if the queue is full + */ + bool add(); + + /** + * Called when item is removed from the queue. Should only be called after a + * corresponding and successful call to add. + */ + void remove(); + + /** + * Tells if this instance is lock free + * + * @return true if it is lock free, false otherwise + */ + bool is_lock_free(); + + /** + * Tells if this class is always lock free. True if it is lock free, false + * otherwise + */ + static constexpr bool is_always_lock_free = + boost::atomic::is_always_lock_free; + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace scwx::qt::util diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp index fe2b2a9a..8ed20533 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -50,12 +50,12 @@ public: ~Impl() {} static std::shared_ptr - LoadImage(const std::string& imagePath); + LoadImage(const std::string& imagePath, double scale = 1); static std::shared_ptr ReadPngFile(const QString& imagePath); static std::shared_ptr - ReadSvgFile(const QString& imagePath); + ReadSvgFile(const QString& imagePath, double scale = 1); std::vector> registeredTextures_ {}; @@ -92,12 +92,12 @@ void TextureAtlas::RegisterTexture(const std::string& name, p->registeredTextures_.emplace_back(std::move(image)); } -std::shared_ptr -TextureAtlas::CacheTexture(const std::string& name, const std::string& path) +std::shared_ptr TextureAtlas::CacheTexture( + const std::string& name, const std::string& path, double scale) { // Attempt to load the image std::shared_ptr image = - TextureAtlas::Impl::LoadImage(path); + TextureAtlas::Impl::LoadImage(path, scale); // If the image is valid if (image != nullptr && image->width() > 0 && image->height() > 0) @@ -314,7 +314,7 @@ void TextureAtlas::BuildAtlas(std::size_t width, std::size_t height) logger_->debug("Texture atlas built in {}", timer.format(6, "%ws")); } -void TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl, GLuint texture) +void TextureAtlas::BufferAtlas(GLuint texture) { std::shared_lock lock(p->atlasMutex_); @@ -343,25 +343,23 @@ void TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl, GLuint texture) lock.unlock(); - gl.glBindTexture(GL_TEXTURE_2D_ARRAY, texture); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture); - gl.glTexParameteri( - GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - gl.glTexParameteri( - GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - gl.glTexImage3D(GL_TEXTURE_2D_ARRAY, - 0, - GL_RGBA, - static_cast(width), - static_cast(height), - static_cast(numLayers), - 0, - GL_RGBA, - GL_UNSIGNED_BYTE, - pixelData.data()); + glTexImage3D(GL_TEXTURE_2D_ARRAY, + 0, + GL_RGBA, + static_cast(width), + static_cast(height), + static_cast(numLayers), + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + pixelData.data()); } } @@ -380,7 +378,7 @@ TextureAttributes TextureAtlas::GetTextureAttributes(const std::string& name) } std::shared_ptr -TextureAtlas::Impl::LoadImage(const std::string& imagePath) +TextureAtlas::Impl::LoadImage(const std::string& imagePath, double scale) { logger_->debug("Loading image: {}", imagePath); @@ -390,15 +388,14 @@ TextureAtlas::Impl::LoadImage(const std::string& imagePath) QUrl url = QUrl::fromUserInput(qImagePath); - if (url.isLocalFile()) { - QString suffix = QFileInfo(qImagePath).suffix().toLower(); - QString qLocalImagePath = url.toString(QUrl::PreferLocalFile); + const QString suffix = QFileInfo(qImagePath).suffix().toLower(); + const QString qLocalImagePath = url.toString(QUrl::PreferLocalFile); if (suffix == "svg") { - image = ReadSvgFile(qLocalImagePath); + image = ReadSvgFile(qLocalImagePath, scale); } else { @@ -448,18 +445,18 @@ TextureAtlas::Impl::LoadImage(const std::string& imagePath) // If no alpha channel, replace black with transparent if (numChannels == 3) { - std::for_each( - std::execution::par_unseq, - view.begin(), - view.end(), - [](boost::gil::rgba8_pixel_t& pixel) - { - static const boost::gil::rgba8_pixel_t kBlack {0, 0, 0, 255}; - if (pixel == kBlack) - { - pixel[3] = 0; - } - }); + std::for_each(std::execution::par, + view.begin(), + view.end(), + [](boost::gil::rgba8_pixel_t& pixel) + { + static const boost::gil::rgba8_pixel_t kBlack { + 0, 0, 0, 255}; + if (pixel == kBlack) + { + pixel[3] = 0; + } + }); } stbi_image_free(pixelData); @@ -509,10 +506,10 @@ TextureAtlas::Impl::ReadPngFile(const QString& imagePath) } std::shared_ptr -TextureAtlas::Impl::ReadSvgFile(const QString& imagePath) +TextureAtlas::Impl::ReadSvgFile(const QString& imagePath, double scale) { QSvgRenderer renderer {imagePath}; - QPixmap pixmap {renderer.defaultSize()}; + QPixmap pixmap {renderer.defaultSize() * scale}; pixmap.fill(Qt::GlobalColor::transparent); QPainter painter {&pixmap}; diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp index 64c5a2d7..b0214a66 100644 --- a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp @@ -74,10 +74,10 @@ public: std::uint64_t BuildCount() const; void RegisterTexture(const std::string& name, const std::string& path); - std::shared_ptr - CacheTexture(const std::string& name, const std::string& path); + std::shared_ptr CacheTexture( + const std::string& name, const std::string& path, double scale = 1); void BuildAtlas(std::size_t width, std::size_t height); - void BufferAtlas(gl::OpenGLFunctions& gl, GLuint texture); + void BufferAtlas(GLuint texture); TextureAttributes GetTextureAttributes(const std::string& name); diff --git a/scwx-qt/source/scwx/qt/util/time.cpp b/scwx-qt/source/scwx/qt/util/time.cpp index f34c6ea5..73d7820b 100644 --- a/scwx-qt/source/scwx/qt/util/time.cpp +++ b/scwx-qt/source/scwx/qt/util/time.cpp @@ -12,13 +12,23 @@ std::chrono::sys_days SysDays(const QDate& date) using namespace std::chrono; using sys_days = time_point; constexpr auto julianEpoch = sys_days {-4713y / November / 24d}; - constexpr auto unixEpoch = sys_days {1970y / January / 1d}; - constexpr auto offset = std::chrono::days(julianEpoch - unixEpoch); return std::chrono::sys_days(std::chrono::days(date.toJulianDay()) + julianEpoch); } +local_days LocalDays(const QDate& date) +{ +#if (__cpp_lib_chrono >= 201907L) + using namespace std::chrono; +#else + using namespace date; +#endif + auto yearMonthDay = + year_month_day(year(date.year()), month(date.month()), day(date.day())); + return local_days(yearMonthDay); +} + } // namespace util } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/time.hpp b/scwx-qt/source/scwx/qt/util/time.hpp index 8f3e7a53..34af655e 100644 --- a/scwx-qt/source/scwx/qt/util/time.hpp +++ b/scwx-qt/source/scwx/qt/util/time.hpp @@ -2,6 +2,10 @@ #include +#if (__cpp_lib_chrono < 201907L) +# include +#endif + #include namespace scwx @@ -11,6 +15,12 @@ namespace qt namespace util { +#if (__cpp_lib_chrono >= 201907L) +using local_days = std::chrono::local_days; +#else +using local_days = date::local_days; +#endif + /** * @brief Convert QDate to std::chrono::sys_days. * @@ -20,6 +30,15 @@ namespace util */ std::chrono::sys_days SysDays(const QDate& date); +/** + * @brief Convert QDate to std::chrono::local_days. + * + * @param [in] date Date to convert + * + * @return Days + */ +local_days LocalDays(const QDate& date); + } // namespace util } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp index 47558d81..9ead358b 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp @@ -25,6 +25,11 @@ static constexpr std::uint32_t kMaxRadialGates_ = common::MAX_0_5_DEGREE_RADIALS * common::MAX_DATA_MOMENT_GATES; static constexpr std::uint32_t kMaxCoordinates_ = kMaxRadialGates_ * 2u; +static constexpr std::uint8_t kDataWordSize8_ = 8u; + +static constexpr std::size_t kVerticesPerGate_ = 6u; +static constexpr std::size_t kVerticesPerOriginGate_ = 3u; + static constexpr uint16_t RANGE_FOLDED = 1u; static constexpr uint32_t VERTICES_PER_BIN = 6u; static constexpr uint32_t VALUES_PER_VERTEX = 2u; @@ -46,6 +51,9 @@ static const std::unordered_map productScale_ { + {common::Level2Product::CorrelationCoefficient, 100.0f}}; + static const std::unordered_map productUnits_ {{common::Level2Product::Reflectivity, "dBZ"}, {common::Level2Product::DifferentialReflectivity, "dB"}, @@ -53,11 +61,10 @@ static const std::unordered_map {common::Level2Product::CorrelationCoefficient, "%"}, {common::Level2Product::ClutterFilterPowerRemoved, "dB"}}; -class Level2ProductViewImpl +class Level2ProductView::Impl { public: - explicit Level2ProductViewImpl(Level2ProductView* self, - common::Level2Product product) : + explicit Impl(Level2ProductView* self, common::Level2Product product) : self_ {self}, product_ {product}, selectedElevation_ {0.0f}, @@ -94,7 +101,7 @@ public: UpdateOtherUnits(unitSettings.other_units().GetValue()); UpdateSpeedUnits(unitSettings.speed_units().GetValue()); } - ~Level2ProductViewImpl() + ~Impl() { auto& unitSettings = settings::UnitSettings::Instance(); @@ -106,20 +113,36 @@ public: threadPool_.join(); }; - void - ComputeCoordinates(std::shared_ptr radarData); + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + + Impl(Impl&&) noexcept = delete; + Impl& operator=(Impl&&) noexcept = delete; + + void ComputeCoordinates( + const std::shared_ptr& radarData, + bool smoothingEnabled); void SetProduct(const std::string& productName); void SetProduct(common::Level2Product product); void UpdateOtherUnits(const std::string& name); void UpdateSpeedUnits(const std::string& name); + void ComputeEdgeValue(); + template + [[nodiscard]] inline T RemapDataMoment(T dataMoment) const; + + static bool IsRadarDataIncomplete( + const std::shared_ptr& radarData); + static units::degrees NormalizeAngle(units::degrees angle); + Level2ProductView* self_; boost::asio::thread_pool threadPool_ {1u}; common::Level2Product product_; - wsr88d::rda::DataBlockType dataBlockType_; + wsr88d::rda::DataBlockType dataBlockType_ { + wsr88d::rda::DataBlockType::Unknown}; float selectedElevation_; @@ -127,11 +150,17 @@ public: std::shared_ptr momentDataBlock0_; + bool lastShowSmoothedRangeFolding_ {false}; + bool lastSmoothingEnabled_ {false}; + std::vector coordinates_ {}; std::vector vertices_ {}; std::vector dataMoments8_ {}; std::vector dataMoments16_ {}; std::vector cfpMoments_ {}; + std::uint16_t edgeValue_ {}; + + bool showSmoothedRangeFolding_ {false}; float latitude_; float longitude_; @@ -161,7 +190,7 @@ Level2ProductView::Level2ProductView( common::Level2Product product, std::shared_ptr radarProductManager) : RadarProductView(radarProductManager), - p(std::make_unique(this, product)) + p(std::make_unique(this, product)) { ConnectRadarProductManager(); } @@ -179,12 +208,9 @@ void Level2ProductView::ConnectRadarProductManager() [this](std::shared_ptr record) { if (record->radar_product_group() == - common::RadarProductGroup::Level2 && - std::chrono::floor(record->time()) == - selected_time()) + common::RadarProductGroup::Level2) { - // If the data associated with the currently selected time is - // reloaded, update the view + // If level 2 data associated was reloaded, update the view Update(); } }); @@ -245,7 +271,7 @@ uint16_t Level2ProductView::color_table_max() const } } -float Level2ProductView::elevation() const +std::optional Level2ProductView::elevation() const { return p->elevationCut_; } @@ -272,6 +298,15 @@ float Level2ProductView::unit_scale() const break; } + if (p->otherUnits_ == types::OtherUnits::Default) + { + auto it = productScale_.find(p->product_); + if (it != productScale_.cend()) + { + return it->second; + } + } + return 1.0f; } @@ -379,12 +414,12 @@ void Level2ProductView::SelectProduct(const std::string& productName) p->SetProduct(productName); } -void Level2ProductViewImpl::SetProduct(const std::string& productName) +void Level2ProductView::Impl::SetProduct(const std::string& productName) { SetProduct(common::GetLevel2Product(productName)); } -void Level2ProductViewImpl::SetProduct(common::Level2Product product) +void Level2ProductView::Impl::SetProduct(common::Level2Product product) { product_ = product; @@ -401,12 +436,12 @@ void Level2ProductViewImpl::SetProduct(common::Level2Product product) } } -void Level2ProductViewImpl::UpdateOtherUnits(const std::string& name) +void Level2ProductView::Impl::UpdateOtherUnits(const std::string& name) { otherUnits_ = types::GetOtherUnitsFromName(name); } -void Level2ProductViewImpl::UpdateSpeedUnits(const std::string& name) +void Level2ProductView::Impl::UpdateSpeedUnits(const std::string& name) { speedUnits_ = types::GetSpeedUnitsFromName(name); } @@ -497,7 +532,7 @@ void Level2ProductView::UpdateColorTableLut() void Level2ProductView::ComputeSweep() { - logger_->debug("ComputeSweep()"); + logger_->trace("ComputeSweep()"); boost::timer::cpu_timer timer; @@ -511,34 +546,52 @@ void Level2ProductView::ComputeSweep() std::shared_ptr radarProductManager = radar_product_manager(); + const bool smoothingEnabled = smoothing_enabled(); + p->showSmoothedRangeFolding_ = show_smoothed_range_folding(); + const bool& showSmoothedRangeFolding = p->showSmoothedRangeFolding_; std::shared_ptr radarData; std::chrono::system_clock::time_point requestedTime {selected_time()}; - std::chrono::system_clock::time_point foundTime; - std::tie(radarData, p->elevationCut_, p->elevationCuts_, foundTime) = + std::tie(radarData, p->elevationCut_, p->elevationCuts_, std::ignore) = radarProductManager->GetLevel2Data( p->dataBlockType_, p->selectedElevation_, requestedTime); - // If a different time was found than what was requested, update it - if (requestedTime != foundTime) - { - SelectTime(foundTime); - } - if (radarData == nullptr) { Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded); return; } - if (radarData == p->elevationScan_) + if ((radarData == p->elevationScan_) && + smoothingEnabled == p->lastSmoothingEnabled_ && + (showSmoothedRangeFolding == p->lastShowSmoothedRangeFolding_ || + !smoothingEnabled)) { Q_EMIT SweepNotComputed(types::NoUpdateReason::NoChange); return; } - const size_t radials = radarData->size(); + p->lastShowSmoothedRangeFolding_ = showSmoothedRangeFolding; + p->lastSmoothingEnabled_ = smoothingEnabled; - p->ComputeCoordinates(radarData); + logger_->debug("Computing Sweep"); + + std::size_t radials = radarData->crbegin()->first + 1; + std::size_t vertexRadials = radials; + + // When there is missing data, insert another empty vertex radial at the end + // to avoid stretching + const bool isRadarDataIncomplete = Impl::IsRadarDataIncomplete(radarData); + if (isRadarDataIncomplete) + { + ++vertexRadials; + } + + // Limit radials + radials = std::min(radials, common::MAX_0_5_DEGREE_RADIALS); + vertexRadials = + std::min(vertexRadials, common::MAX_0_5_DEGREE_RADIALS); + + p->ComputeCoordinates(radarData, smoothingEnabled); const std::vector& coordinates = p->coordinates_; @@ -574,7 +627,8 @@ void Level2ProductView::ComputeSweep() std::vector& vertices = p->vertices_; size_t vIndex = 0; vertices.clear(); - vertices.resize(radials * gates * VERTICES_PER_BIN * VALUES_PER_VERTEX); + vertices.resize(vertexRadials * gates * VERTICES_PER_BIN * + VALUES_PER_VERTEX); // Setup data moment vector std::vector& dataMoments8 = p->dataMoments8_; @@ -616,11 +670,20 @@ void Level2ProductView::ComputeSweep() // Start radial is always 0, as coordinates are calculated for each sweep constexpr std::uint16_t startRadial = 0u; - for (auto& radialPair : *radarData) + // For most products other than reflectivity, the edge should not go to the + // bottom of the color table + if (smoothingEnabled) { + p->ComputeEdgeValue(); + } + + for (auto it = radarData->cbegin(); it != radarData->cend(); ++it) + { + const auto& radialPair = *it; std::uint16_t radial = radialPair.first; - auto& radialData = radialPair.second; - auto momentData = radialData->moment_data_block(p->dataBlockType_); + const auto& radialData = radialPair.second; + const std::shared_ptr + momentData = radialData->moment_data_block(p->dataBlockType_); if (momentData0->data_word_size() != momentData->data_word_size()) { @@ -642,7 +705,7 @@ void Level2ProductView::ComputeSweep() std::max(1, dataMomentInterval / gateSizeMeters); // Compute gate range [startGate, endGate) - const std::int32_t startGate = + std::int32_t startGate = (dataMomentRange - dataMomentIntervalH) / gateSizeMeters; const std::int32_t numberOfDataMomentGates = std::min(momentData->number_of_data_moment_gates(), @@ -651,9 +714,19 @@ void Level2ProductView::ComputeSweep() startGate + numberOfDataMomentGates * gateSize, static_cast(common::MAX_DATA_MOMENT_GATES)); - const std::uint8_t* dataMomentsArray8 = nullptr; - const std::uint16_t* dataMomentsArray16 = nullptr; - const std::uint8_t* cfpMomentsArray = nullptr; + if (smoothingEnabled) + { + // If smoothing is enabled, the start gate is incremented by one, as we + // are skipping the radar site origin. The end gate is unaffected, as + // we need to draw one less data point. + ++startGate; + } + + const std::uint8_t* dataMomentsArray8 = nullptr; + const std::uint16_t* dataMomentsArray16 = nullptr; + const std::uint8_t* nextDataMomentsArray8 = nullptr; + const std::uint16_t* nextDataMomentsArray16 = nullptr; + const std::uint8_t* cfpMomentsArray = nullptr; if (momentData->data_word_size() == 8) { @@ -673,6 +746,45 @@ void Level2ProductView::ComputeSweep() ->data_moments()); } + std::shared_ptr + nextMomentData = nullptr; + std::int32_t numberOfNextDataMomentGates = 0; + if (smoothingEnabled) + { + // Smoothing requires the next radial pair as well + auto nextIt = std::next(it); + if (nextIt == radarData->cend()) + { + nextIt = radarData->cbegin(); + } + + const auto& nextRadialPair = *(nextIt); + const auto& nextRadialData = nextRadialPair.second; + nextMomentData = nextRadialData->moment_data_block(p->dataBlockType_); + + if (momentData->data_word_size() != nextMomentData->data_word_size()) + { + // Data should be consistent between radials + logger_->warn("Invalid data moment size"); + continue; + } + + if (nextMomentData->data_word_size() == kDataWordSize8_) + { + nextDataMomentsArray8 = reinterpret_cast( + nextMomentData->data_moments()); + } + else + { + nextDataMomentsArray16 = reinterpret_cast( + nextMomentData->data_moments()); + } + + numberOfNextDataMomentGates = std::min( + nextMomentData->number_of_data_moment_gates(), + static_cast(gates)); + } + for (std::int32_t gate = startGate, i = 0; gate + gateSize <= endGate; gate += gateSize, ++i) { @@ -681,56 +793,172 @@ void Level2ProductView::ComputeSweep() continue; } - std::size_t vertexCount = (gate > 0) ? 6 : 3; + const std::size_t vertexCount = + (gate > 0) ? kVerticesPerGate_ : kVerticesPerOriginGate_; + + // Allow pointer arithmetic here, as bounds have already been checked + // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) // Store data moment value if (dataMomentsArray8 != nullptr) { - std::uint8_t dataValue = dataMomentsArray8[i]; - if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) + if (!smoothingEnabled) { - continue; - } - - for (std::size_t m = 0; m < vertexCount; m++) - { - dataMoments8[mIndex++] = dataMomentsArray8[i]; - - if (cfpMomentsArray != nullptr) + const std::uint8_t& dataValue = dataMomentsArray8[i]; + if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) { - cfpMoments[mIndex - 1] = cfpMomentsArray[i]; + continue; } + + for (std::size_t m = 0; m < vertexCount; m++) + { + dataMoments8[mIndex++] = dataValue; + + if (cfpMomentsArray != nullptr) + { + cfpMoments[mIndex - 1] = cfpMomentsArray[i]; + } + } + } + else if (gate > 0) + { + // Validate indices are all in range + if (i + 1 >= numberOfDataMomentGates || + i + 1 >= numberOfNextDataMomentGates) + { + continue; + } + + const std::uint8_t& dm1 = dataMomentsArray8[i]; + const std::uint8_t& dm2 = dataMomentsArray8[i + 1]; + const std::uint8_t& dm3 = nextDataMomentsArray8[i]; + const std::uint8_t& dm4 = nextDataMomentsArray8[i + 1]; + + if ((!showSmoothedRangeFolding && // + (dm1 < snrThreshold || dm1 == RANGE_FOLDED) && + (dm2 < snrThreshold || dm2 == RANGE_FOLDED) && + (dm3 < snrThreshold || dm3 == RANGE_FOLDED) && + (dm4 < snrThreshold || dm4 == RANGE_FOLDED)) || + (showSmoothedRangeFolding && // + dm1 < snrThreshold && dm1 != RANGE_FOLDED && + dm2 < snrThreshold && dm2 != RANGE_FOLDED && + dm3 < snrThreshold && dm3 != RANGE_FOLDED && + dm4 < snrThreshold && dm4 != RANGE_FOLDED)) + { + // Skip only if all data moments are hidden + continue; + } + + // The order must match the store vertices section below + dataMoments8[mIndex++] = p->RemapDataMoment(dm1); + dataMoments8[mIndex++] = p->RemapDataMoment(dm2); + dataMoments8[mIndex++] = p->RemapDataMoment(dm4); + dataMoments8[mIndex++] = p->RemapDataMoment(dm1); + dataMoments8[mIndex++] = p->RemapDataMoment(dm3); + dataMoments8[mIndex++] = p->RemapDataMoment(dm4); + + // cfpMoments is unused, so not populated here + } + else + { + // If smoothing is enabled, gate should never start at zero + // (radar site origin) + logger_->error( + "Smoothing enabled, gate should not start at zero"); + continue; } } else { - std::uint16_t dataValue = dataMomentsArray16[i]; - if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) + if (!smoothingEnabled) { + const std::uint16_t& dataValue = dataMomentsArray16[i]; + if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) + { + continue; + } + + for (std::size_t m = 0; m < vertexCount; m++) + { + dataMoments16[mIndex++] = dataValue; + } + } + else if (gate > 0) + { + // Validate indices are all in range + if (i + 1 >= numberOfDataMomentGates || + i + 1 >= numberOfNextDataMomentGates) + { + continue; + } + + const std::uint16_t& dm1 = dataMomentsArray16[i]; + const std::uint16_t& dm2 = dataMomentsArray16[i + 1]; + const std::uint16_t& dm3 = nextDataMomentsArray16[i]; + const std::uint16_t& dm4 = nextDataMomentsArray16[i + 1]; + + if ((!showSmoothedRangeFolding && // + (dm1 < snrThreshold || dm1 == RANGE_FOLDED) && + (dm2 < snrThreshold || dm2 == RANGE_FOLDED) && + (dm3 < snrThreshold || dm3 == RANGE_FOLDED) && + (dm4 < snrThreshold || dm4 == RANGE_FOLDED)) || + (showSmoothedRangeFolding && // + dm1 < snrThreshold && dm1 != RANGE_FOLDED && + dm2 < snrThreshold && dm2 != RANGE_FOLDED && + dm3 < snrThreshold && dm3 != RANGE_FOLDED && + dm4 < snrThreshold && dm4 != RANGE_FOLDED)) + { + // Skip only if all data moments are hidden + continue; + } + + // The order must match the store vertices section below + dataMoments16[mIndex++] = p->RemapDataMoment(dm1); + dataMoments16[mIndex++] = p->RemapDataMoment(dm2); + dataMoments16[mIndex++] = p->RemapDataMoment(dm4); + dataMoments16[mIndex++] = p->RemapDataMoment(dm1); + dataMoments16[mIndex++] = p->RemapDataMoment(dm3); + dataMoments16[mIndex++] = p->RemapDataMoment(dm4); + + // cfpMoments is unused, so not populated here + } + else + { + // If smoothing is enabled, gate should never start at zero + // (radar site origin) continue; } - - for (std::size_t m = 0; m < vertexCount; m++) - { - dataMoments16[mIndex++] = dataMomentsArray16[i]; - } } + // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) + // Store vertices if (gate > 0) { + // Draw two triangles per gate + // + // 2 +---+ 4 + // | /| + // | / | + // |/ | + // 1 +---+ 3 + const std::uint16_t baseCoord = gate - 1; - std::size_t offset1 = ((startRadial + radial) % radials * - common::MAX_DATA_MOMENT_GATES + - baseCoord) * - 2; - std::size_t offset2 = offset1 + gateSize * 2; - std::size_t offset3 = (((startRadial + radial + 1) % radials) * - common::MAX_DATA_MOMENT_GATES + - baseCoord) * - 2; - std::size_t offset4 = offset3 + gateSize * 2; + const std::size_t offset1 = + ((startRadial + radial) % vertexRadials * + common::MAX_DATA_MOMENT_GATES + + baseCoord) * + 2; + const std::size_t offset2 = + offset1 + static_cast(gateSize) * 2; + const std::size_t offset3 = + (((startRadial + radial + 1) % vertexRadials) * + common::MAX_DATA_MOMENT_GATES + + baseCoord) * + 2; + const std::size_t offset4 = + offset3 + static_cast(gateSize) * 2; vertices[vIndex++] = coordinates[offset1]; vertices[vIndex++] = coordinates[offset1 + 1]; @@ -738,32 +966,31 @@ void Level2ProductView::ComputeSweep() vertices[vIndex++] = coordinates[offset2]; vertices[vIndex++] = coordinates[offset2 + 1]; - vertices[vIndex++] = coordinates[offset3]; - vertices[vIndex++] = coordinates[offset3 + 1]; + vertices[vIndex++] = coordinates[offset4]; + vertices[vIndex++] = coordinates[offset4 + 1]; + + vertices[vIndex++] = coordinates[offset1]; + vertices[vIndex++] = coordinates[offset1 + 1]; vertices[vIndex++] = coordinates[offset3]; vertices[vIndex++] = coordinates[offset3 + 1]; vertices[vIndex++] = coordinates[offset4]; vertices[vIndex++] = coordinates[offset4 + 1]; - - vertices[vIndex++] = coordinates[offset2]; - vertices[vIndex++] = coordinates[offset2 + 1]; - - vertexCount = 6; } else { const std::uint16_t baseCoord = gate; - std::size_t offset1 = ((startRadial + radial) % radials * - common::MAX_DATA_MOMENT_GATES + - baseCoord) * - 2; - std::size_t offset2 = (((startRadial + radial + 1) % radials) * + std::size_t offset1 = ((startRadial + radial) % vertexRadials * common::MAX_DATA_MOMENT_GATES + baseCoord) * 2; + std::size_t offset2 = + (((startRadial + radial + 1) % vertexRadials) * + common::MAX_DATA_MOMENT_GATES + + baseCoord) * + 2; vertices[vIndex++] = p->latitude_; vertices[vIndex++] = p->longitude_; @@ -773,8 +1000,6 @@ void Level2ProductView::ComputeSweep() vertices[vIndex++] = coordinates[offset2]; vertices[vIndex++] = coordinates[offset2 + 1]; - - vertexCount = 3; } } } @@ -806,8 +1031,50 @@ void Level2ProductView::ComputeSweep() Q_EMIT SweepComputed(); } -void Level2ProductViewImpl::ComputeCoordinates( - std::shared_ptr radarData) +void Level2ProductView::Impl::ComputeEdgeValue() +{ + const float offset = momentDataBlock0_->offset(); + + switch (dataBlockType_) + { + case wsr88d::rda::DataBlockType::MomentVel: + case wsr88d::rda::DataBlockType::MomentZdr: + edgeValue_ = static_cast(offset); + break; + + case wsr88d::rda::DataBlockType::MomentSw: + case wsr88d::rda::DataBlockType::MomentPhi: + edgeValue_ = 2; + break; + + case wsr88d::rda::DataBlockType::MomentRho: + edgeValue_ = std::numeric_limits::max(); + break; + + case wsr88d::rda::DataBlockType::MomentRef: + default: + edgeValue_ = 0; + break; + } +} + +template +T Level2ProductView::Impl::RemapDataMoment(T dataMoment) const +{ + if (dataMoment != 0 && + (dataMoment != RANGE_FOLDED || showSmoothedRangeFolding_)) + { + return dataMoment; + } + else + { + return edgeValue_; + } +} + +void Level2ProductView::Impl::ComputeCoordinates( + const std::shared_ptr& radarData, + bool smoothingEnabled) { logger_->debug("ComputeCoordinates()"); @@ -828,52 +1095,203 @@ void Level2ProductViewImpl::ComputeCoordinates( auto& radarData0 = (*radarData)[0]; auto momentData0 = radarData0->moment_data_block(dataBlockType_); - const std::uint16_t numRadials = - static_cast(radarData->size()); + std::uint16_t numRadials = + static_cast(radarData->crbegin()->first + 1); const std::uint16_t numRangeBins = std::max(momentData0->number_of_data_moment_gates() + 1u, common::MAX_DATA_MOMENT_GATES); + // Add an extra radial when incomplete data exists + if (IsRadarDataIncomplete(radarData)) + { + ++numRadials; + } + + // Limit radials + numRadials = + std::min(numRadials, common::MAX_0_5_DEGREE_RADIALS); + auto radials = boost::irange(0u, numRadials); auto gates = boost::irange(0u, numRangeBins); - std::for_each(std::execution::par_unseq, - radials.begin(), - radials.end(), - [&](std::uint32_t radial) - { - const units::degrees angle = - (*radarData)[radial]->azimuth_angle(); + const float gateRangeOffset = (smoothingEnabled) ? + // Center of the first gate is half the gate + // size distance from the radar site + 0.5f : + // Far end of the first gate is the gate + // size distance from the radar site + 1.0f; - std::for_each(std::execution::par_unseq, - gates.begin(), - gates.end(), - [&](std::uint32_t gate) - { - const std::uint32_t radialGate = - radial * common::MAX_DATA_MOMENT_GATES + - gate; - const float range = (gate + 1) * gateSize; - const std::size_t offset = radialGate * 2; + std::for_each( + std::execution::par_unseq, + radials.begin(), + radials.end(), + [&](std::uint32_t radial) + { + units::degrees angle {}; - double latitude; - double longitude; + auto radialData = radarData->find(radial); + if (radialData != radarData->cend() && smoothingEnabled) + { + angle = radialData->second->azimuth_angle(); + } + else + { + auto prevRadial1 = radarData->find( + (radial >= 1) ? radial - 1 : numRadials - (1 - radial)); + auto prevRadial2 = radarData->find( + (radial >= 2) ? radial - 2 : numRadials - (2 - radial)); - geodesic.Direct(radarLatitude, - radarLongitude, - angle.value(), - range, - latitude, - longitude); + if (radialData != radarData->cend() && + prevRadial1 != radarData->cend() && !smoothingEnabled) + { + const units::degrees currentAngle = + radialData->second->azimuth_angle(); + const units::degrees prevAngle = + prevRadial1->second->azimuth_angle(); - coordinates_[offset] = latitude; - coordinates_[offset + 1] = longitude; - }); - }); + // Calculate delta angle + const units::degrees deltaAngle = + NormalizeAngle(currentAngle - prevAngle); + + // Delta scale is half the delta angle to reach the end of the + // bin, because smoothing is not enabled + constexpr float deltaScale = 0.5f; + + angle = currentAngle - deltaAngle * deltaScale; + } + else if (radialData != radarData->cend() && !smoothingEnabled) + { + const units::degrees currentAngle = + radialData->second->azimuth_angle(); + + // Assume a half degree delta if there aren't enough angles + // to determine a delta angle + constexpr units::degrees deltaAngle {0.5f}; + + // Delta scale is half the delta angle to reach the edge of the + // bin, because smoothing is enabled + constexpr float deltaScale = 0.5f; + + angle = currentAngle - deltaAngle * deltaScale; + } + else if (prevRadial1 != radarData->cend() && + prevRadial2 != radarData->cend()) + { + const units::degrees prevAngle1 = + prevRadial1->second->azimuth_angle(); + const units::degrees prevAngle2 = + prevRadial2->second->azimuth_angle(); + + // Calculate delta angle + const units::degrees deltaAngle = + NormalizeAngle(prevAngle1 - prevAngle2); + + const float deltaScale = + (smoothingEnabled) ? + // Delta scale is 1.0x the delta angle to reach the center + // of the next bin, because smoothing is enabled + 1.0f : + // Delta scale is 0.5x the delta angle to reach the edge of + // the next bin + 0.5f; + + angle = prevAngle1 + deltaAngle * deltaScale; + } + else if (prevRadial1 != radarData->cend()) + { + const units::degrees prevAngle1 = + prevRadial1->second->azimuth_angle(); + + // Assume a half degree delta if there aren't enough angles + // to determine a delta angle + constexpr units::degrees deltaAngle {0.5f}; + + const float deltaScale = + (smoothingEnabled) ? + // Delta scale is 1.0x the delta angle to reach the center + // of the next bin, because smoothing is enabled + 1.0f : + // Delta scale is 0.5x the delta angle to reach the edge of + // the next bin + 0.5f; + + angle = prevAngle1 + deltaAngle * deltaScale; + } + else + { + // Not enough angles present to determine an angle + return; + } + } + + std::for_each( + std::execution::par_unseq, + gates.begin(), + gates.end(), + [&](std::uint32_t gate) + { + const std::uint32_t radialGate = + radial * common::MAX_DATA_MOMENT_GATES + gate; + const float range = + (static_cast(gate) + gateRangeOffset) * gateSize; + const std::size_t offset = + static_cast(radialGate) * 2; + + double latitude = 0.0; + double longitude = 0.0; + + geodesic.Direct(radarLatitude, + radarLongitude, + angle.value(), + range, + latitude, + longitude); + + coordinates_[offset] = static_cast(latitude); + coordinates_[offset + 1] = static_cast(longitude); + }); + }); timer.stop(); logger_->debug("Coordinates calculated in {}", timer.format(6, "%ws")); } +bool Level2ProductView::Impl::IsRadarDataIncomplete( + const std::shared_ptr& radarData) +{ + // Assume the data is incomplete when the delta between the first and last + // angles is greater than 2.5 degrees. + constexpr units::degrees kIncompleteDataAngleThreshold_ {2.5}; + + const units::degrees firstAngle = + radarData->cbegin()->second->azimuth_angle(); + const units::degrees lastAngle = + radarData->crbegin()->second->azimuth_angle(); + const units::degrees angleDelta = + common::GetAngleDelta(firstAngle, lastAngle); + + return angleDelta > kIncompleteDataAngleThreshold_; +} + +units::degrees +Level2ProductView::Impl::NormalizeAngle(units::degrees angle) +{ + constexpr auto angleLimit = units::degrees {180.0f}; + constexpr auto fullAngle = units::degrees {360.0f}; + + // Normalize angle to [-180, 180) + while (angle < -angleLimit) + { + angle += fullAngle; + } + while (angle >= angleLimit) + { + angle -= fullAngle; + } + + return angle; +} + std::optional Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const { @@ -916,8 +1334,19 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const } // Find Radial - const std::uint16_t numRadials = - static_cast(radarData->size()); + std::uint16_t numRadials = + static_cast(radarData->crbegin()->first + 1); + + // Add an extra radial when incomplete data exists + if (Impl::IsRadarDataIncomplete(radarData)) + { + ++numRadials; + } + + // Limit radials + numRadials = + std::min(numRadials, common::MAX_0_5_DEGREE_RADIALS); + auto radials = boost::irange(0u, numRadials); auto radial = std::find_if( // @@ -926,25 +1355,68 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const radials.end(), [&](std::uint32_t i) { - bool found = false; - const units::degrees startAngle = - (*radarData)[i]->azimuth_angle(); - const units::degrees nextAngle = - (*radarData)[(i + 1) % numRadials]->azimuth_angle(); + bool hasNextAngle = false; + bool found = false; - if (startAngle < nextAngle) + units::degrees startAngle {}; + units::degrees nextAngle {}; + + auto radialData = radarData->find(i); + if (radialData != radarData->cend()) { - if (startAngle.value() <= azi1 && azi1 < nextAngle.value()) + startAngle = radialData->second->azimuth_angle(); + + auto nextRadial = radarData->find((i + 1) % numRadials); + if (nextRadial != radarData->cend()) { - found = true; + nextAngle = nextRadial->second->azimuth_angle(); + + // Level 2 angles are the center of the bins. + const units::degrees deltaAngle = + common::GetAngleDelta(startAngle, nextAngle); + startAngle -= deltaAngle / 2; + nextAngle -= deltaAngle / 2; + + hasNextAngle = true; + } + else + { + // Next angle is not available, interpolate + auto prevRadial = + radarData->find((i >= 1) ? i - 1 : numRadials - (1 - i)); + + if (prevRadial != radarData->cend()) + { + const units::degrees prevAngle = + prevRadial->second->azimuth_angle(); + + const units::degrees deltaAngle = + common::GetAngleDelta(startAngle, prevAngle); + + // Level 2 angles are the center of the bins. + nextAngle = startAngle + deltaAngle / 2; + startAngle -= deltaAngle / 2; + hasNextAngle = true; + } } } - else + + if (hasNextAngle) { - // If the bin crosses 0/360 degrees, special handling is needed - if (startAngle.value() <= azi1 || azi1 < nextAngle.value()) + if (startAngle < nextAngle) { - found = true; + if (startAngle.value() <= azi1 && azi1 < nextAngle.value()) + { + found = true; + } + } + else + { + // If the bin crosses 0/360 degrees, special handling is needed + if (startAngle.value() <= azi1 || azi1 < nextAngle.value()) + { + found = true; + } } } diff --git a/scwx-qt/source/scwx/qt/view/level2_product_view.hpp b/scwx-qt/source/scwx/qt/view/level2_product_view.hpp index 9e25a254..64651bfa 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.hpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.hpp @@ -8,14 +8,8 @@ #include #include -namespace scwx +namespace scwx::qt::view { -namespace qt -{ -namespace view -{ - -class Level2ProductViewImpl; class Level2ProductView : public RadarProductView { @@ -25,38 +19,47 @@ public: explicit Level2ProductView( common::Level2Product product, std::shared_ptr radarProductManager); - ~Level2ProductView(); + ~Level2ProductView() override; - std::shared_ptr color_table() const override; - const std::vector& - color_table_lut() const override; - std::uint16_t color_table_min() const override; - std::uint16_t color_table_max() const override; - float elevation() const override; - float range() const override; - std::chrono::system_clock::time_point sweep_time() const override; - float unit_scale() const override; - std::string units() const override; - std::uint16_t vcp() const override; - const std::vector& vertices() const override; + Level2ProductView(const Level2ProductView&) = delete; + Level2ProductView(Level2ProductView&&) = delete; + Level2ProductView& operator=(const Level2ProductView&) = delete; + Level2ProductView& operator=(Level2ProductView&&) = delete; + + [[nodiscard]] std::shared_ptr + color_table() const override; + [[nodiscard]] const std::vector& + color_table_lut() const override; + [[nodiscard]] std::uint16_t color_table_min() const override; + [[nodiscard]] std::uint16_t color_table_max() const override; + [[nodiscard]] std::optional elevation() const override; + [[nodiscard]] float range() const override; + [[nodiscard]] std::chrono::system_clock::time_point + sweep_time() const override; + [[nodiscard]] float unit_scale() const override; + [[nodiscard]] std::string units() const override; + [[nodiscard]] std::uint16_t vcp() const override; + [[nodiscard]] const std::vector& vertices() const override; void LoadColorTable(std::shared_ptr colorTable) override; void SelectElevation(float elevation) override; void SelectProduct(const std::string& productName) override; - common::RadarProductGroup GetRadarProductGroup() const override; - std::string GetRadarProductName() const override; - std::vector GetElevationCuts() const override; - std::tuple + [[nodiscard]] common::RadarProductGroup + GetRadarProductGroup() const override; + [[nodiscard]] std::string GetRadarProductName() const override; + [[nodiscard]] std::vector GetElevationCuts() const override; + [[nodiscard]] std::tuple GetMomentData() const override; - std::tuple + [[nodiscard]] std::tuple GetCfpMomentData() const override; - std::optional + [[nodiscard]] std::optional GetBinLevel(const common::Coordinate& coordinate) const override; - std::optional - GetDataLevelCode(std::uint16_t level) const override; - std::optional GetDataValue(std::uint16_t level) const override; + [[nodiscard]] std::optional + GetDataLevelCode(std::uint16_t level) const override; + [[nodiscard]] std::optional + GetDataValue(std::uint16_t level) const override; static std::shared_ptr Create(common::Level2Product product, @@ -73,9 +76,8 @@ protected slots: void ComputeSweep() override; private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; -} // namespace view -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::view diff --git a/scwx-qt/source/scwx/qt/view/level3_product_view.cpp b/scwx-qt/source/scwx/qt/view/level3_product_view.cpp index 551d04e4..97985a39 100644 --- a/scwx-qt/source/scwx/qt/view/level3_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level3_product_view.cpp @@ -17,10 +17,6 @@ #include #include -#if !defined(_MSC_VER) -# include -#endif - namespace scwx { namespace qt @@ -33,6 +29,10 @@ static const auto logger_ = util::Logger::Create(logPrefix_); static constexpr uint16_t RANGE_FOLDED = 1u; +static const std::unordered_map + categoryScale_ { + {common::Level3ProductCategory::CorrelationCoefficient, 100.0f}}; + static const std::unordered_map categoryUnits_ { {common::Level3ProductCategory::Reflectivity, "dBZ"}, @@ -221,6 +221,15 @@ float Level3ProductView::unit_scale() const break; } + if (p->otherUnits_ == types::OtherUnits::Default) + { + auto it = categoryScale_.find(p->category_); + if (it != categoryScale_.cend()) + { + return it->second; + } + } + return 1.0f; } @@ -489,6 +498,52 @@ void Level3ProductView::UpdateColorTableLut() Q_EMIT ColorTableLutUpdated(); } +std::uint8_t Level3ProductView::ComputeEdgeValue() const +{ + std::uint8_t edgeValue = 0; + + const std::shared_ptr + descriptionBlock = p->graphicMessage_->description_block(); + + const float offset = descriptionBlock->offset(); + const float scale = descriptionBlock->scale(); + + switch (p->category_) + { + case common::Level3ProductCategory::Velocity: + edgeValue = static_cast((scale > 0.0f) ? (-offset / scale) : + -offset); + break; + + case common::Level3ProductCategory::DifferentialReflectivity: + edgeValue = static_cast(-offset); + break; + + case common::Level3ProductCategory::SpectrumWidth: + case common::Level3ProductCategory::SpecificDifferentialPhase: + edgeValue = 2; + break; + + case common::Level3ProductCategory::CorrelationCoefficient: + edgeValue = static_cast( + std::max(std::numeric_limits::max(), + descriptionBlock->number_of_levels())); + break; + + case common::Level3ProductCategory::Reflectivity: + case common::Level3ProductCategory::StormRelativeVelocity: + case common::Level3ProductCategory::VerticallyIntegratedLiquid: + case common::Level3ProductCategory::EchoTops: + case common::Level3ProductCategory::HydrometeorClassification: + case common::Level3ProductCategory::PrecipitationAccumulation: + default: + edgeValue = 0; + break; + } + + return edgeValue; +} + std::optional Level3ProductView::GetDataLevelCode(std::uint16_t level) const { diff --git a/scwx-qt/source/scwx/qt/view/level3_product_view.hpp b/scwx-qt/source/scwx/qt/view/level3_product_view.hpp index e836c6e0..b5e043b3 100644 --- a/scwx-qt/source/scwx/qt/view/level3_product_view.hpp +++ b/scwx-qt/source/scwx/qt/view/level3_product_view.hpp @@ -58,6 +58,8 @@ protected: void DisconnectRadarProductManager() override; void UpdateColorTableLut() override; + [[nodiscard]] std::uint8_t ComputeEdgeValue() const; + private: class Impl; std::unique_ptr p; diff --git a/scwx-qt/source/scwx/qt/view/level3_radial_view.cpp b/scwx-qt/source/scwx/qt/view/level3_radial_view.cpp index 5611fdf5..c01e0cd4 100644 --- a/scwx-qt/source/scwx/qt/view/level3_radial_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level3_radial_view.cpp @@ -39,12 +39,16 @@ public: vcp_ {}, sweepTime_ {} { - coordinates_.resize(kMaxCoordinates_); } ~Impl() { threadPool_.join(); }; void ComputeCoordinates( - const std::shared_ptr& radialData); + const std::shared_ptr& radialData, + bool smoothingEnabled, + float gateSize); + + [[nodiscard]] inline std::uint8_t + RemapDataMoment(std::uint8_t dataMoment) const; Level3RadialView* self_; @@ -53,11 +57,17 @@ public: std::vector coordinates_ {}; std::vector vertices_ {}; std::vector dataMoments8_ {}; + std::uint8_t edgeValue_ {}; + + bool showSmoothedRangeFolding_ {false}; std::shared_ptr lastRadialData_ {}; + bool lastShowSmoothedRangeFolding_ {false}; + bool lastSmoothingEnabled_ {false}; float latitude_; float longitude_; + std::optional elevation_ {}; float range_; std::uint16_t vcp_; @@ -82,6 +92,11 @@ boost::asio::thread_pool& Level3RadialView::thread_pool() return p->threadPool_; } +std::optional Level3RadialView::elevation() const +{ + return p->elevation_; +} + float Level3RadialView::range() const { return p->range_; @@ -117,7 +132,7 @@ std::tuple Level3RadialView::GetMomentData() const void Level3RadialView::ComputeSweep() { - logger_->debug("ComputeSweep()"); + logger_->trace("ComputeSweep()"); boost::timer::cpu_timer timer; @@ -125,6 +140,9 @@ void Level3RadialView::ComputeSweep() std::shared_ptr radarProductManager = radar_product_manager(); + const bool smoothingEnabled = smoothing_enabled(); + p->showSmoothedRangeFolding_ = show_smoothed_range_folding(); + const bool& showSmoothedRangeFolding = p->showSmoothedRangeFolding_; // Retrieve message from Radar Product Manager std::shared_ptr message; @@ -155,7 +173,10 @@ void Level3RadialView::ComputeSweep() Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData); return; } - else if (gpm == graphic_product_message()) + else if (gpm == graphic_product_message() && + smoothingEnabled == p->lastSmoothingEnabled_ && + (showSmoothedRangeFolding == p->lastShowSmoothedRangeFolding_ || + !smoothingEnabled)) { // Skip if this is the message we previously processed Q_EMIT SweepNotComputed(types::NoUpdateReason::NoChange); @@ -163,6 +184,9 @@ void Level3RadialView::ComputeSweep() } set_graphic_product_message(gpm); + p->lastShowSmoothedRangeFolding_ = showSmoothedRangeFolding; + p->lastSmoothingEnabled_ = smoothingEnabled; + // A message with radial data should have a Product Description Block and // Product Symbology Block std::shared_ptr descriptionBlock = @@ -185,6 +209,8 @@ void Level3RadialView::ComputeSweep() return; } + logger_->debug("Computing Sweep"); + // A message with radial data should either have a Digital Radial Data // Array Packet, or a Radial Data Array Packet std::shared_ptr @@ -249,27 +275,34 @@ void Level3RadialView::ComputeSweep() } common::RadialSize radialSize; - if (radials == common::MAX_0_5_DEGREE_RADIALS) + if (radarProductManager->is_tdwr()) { - radialSize = common::RadialSize::_0_5Degree; - } - else if (radials == common::MAX_1_DEGREE_RADIALS) - { - radialSize = common::RadialSize::_1Degree; + radialSize = common::RadialSize::NonStandard; } else { - radialSize = common::RadialSize::NonStandard; + if (radials == common::MAX_0_5_DEGREE_RADIALS) + { + radialSize = common::RadialSize::_0_5Degree; + } + else if (radials == common::MAX_1_DEGREE_RADIALS) + { + radialSize = common::RadialSize::_1Degree; + } + else + { + radialSize = common::RadialSize::NonStandard; + } } const std::vector& coordinates = (radialSize == common::RadialSize::NonStandard) ? p->coordinates_ : - radarProductManager->coordinates(radialSize); + radarProductManager->coordinates(radialSize, smoothingEnabled); // There should be a positive number of range bins in radial data - const uint16_t gates = radialData->number_of_range_bins(); - if (gates < 1) + const uint16_t numberOfDataMomentGates = radialData->number_of_range_bins(); + if (numberOfDataMomentGates < 1) { logger_->warn("No range bins in radial data"); Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData); @@ -279,6 +312,10 @@ void Level3RadialView::ComputeSweep() p->latitude_ = descriptionBlock->latitude_of_radar(); p->longitude_ = descriptionBlock->longitude_of_radar(); p->range_ = descriptionBlock->range(); + p->elevation_ = + descriptionBlock->has_elevation() ? + static_cast(descriptionBlock->elevation().value()) : + std::optional {}; p->sweepTime_ = scwx::util::TimePoint(descriptionBlock->volume_scan_date(), descriptionBlock->volume_scan_start_time() * 1000); @@ -291,22 +328,33 @@ void Level3RadialView::ComputeSweep() std::vector& vertices = p->vertices_; size_t vIndex = 0; vertices.clear(); - vertices.resize(radials * gates * VERTICES_PER_BIN * VALUES_PER_VERTEX); + vertices.resize(radials * numberOfDataMomentGates * VERTICES_PER_BIN * + VALUES_PER_VERTEX); // Setup data moment vector std::vector& dataMoments8 = p->dataMoments8_; size_t mIndex = 0; - dataMoments8.resize(radials * gates * VERTICES_PER_BIN); + dataMoments8.resize(radials * numberOfDataMomentGates * VERTICES_PER_BIN); // Compute threshold at which to display an individual bin const uint16_t snrThreshold = descriptionBlock->threshold(); + // Compute gate interval + const std::uint16_t dataMomentInterval = + descriptionBlock->x_resolution_raw(); + + // Get the gate length in meters. Use dataMomentInterval for NonStandard to + // avoid generating >1 base gates per bin. + const float gateLength = radialSize == common::RadialSize::NonStandard ? + static_cast(dataMomentInterval) : + radarProductManager->gate_size(); + // Determine which radial to start at std::uint16_t startRadial; if (radialSize == common::RadialSize::NonStandard) { - p->ComputeCoordinates(radialData); + p->ComputeCoordinates(radialData, smoothingEnabled, gateLength); startRadial = 0; } else @@ -316,40 +364,99 @@ void Level3RadialView::ComputeSweep() startRadial = std::lroundf(startAngle * radialMultiplier); } - for (uint16_t radial = 0; radial < radialData->number_of_radials(); radial++) + // Compute gate size (number of base gates per bin) + const std::uint16_t gateSize = std::max( + 1, dataMomentInterval / static_cast(gateLength)); + + // Compute gate range [startGate, endGate) + std::uint16_t startGate = 0; + const std::uint16_t endGate = + std::min(startGate + numberOfDataMomentGates * gateSize, + common::MAX_DATA_MOMENT_GATES); + + if (smoothingEnabled) { - const auto dataMomentsArray8 = radialData->level(radial); + // If smoothing is enabled, the start gate is incremented by one, as we + // are skipping the radar site origin. The end gate is unaffected, as + // we need to draw one less data point. + ++startGate; - // Compute gate interval - const uint16_t dataMomentInterval = descriptionBlock->x_resolution_raw(); + // For most products other than reflectivity, the edge should not go to + // the bottom of the color table + p->edgeValue_ = ComputeEdgeValue(); + } - // Compute gate size (number of base gates per bin) - const uint16_t gateSize = std::max( - 1, - dataMomentInterval / - static_cast(radarProductManager->gate_size())); + for (std::uint16_t radial = 0; radial < radialData->number_of_radials(); + ++radial) + { + const auto& dataMomentsArray8 = radialData->level(radial); - // Compute gate range [startGate, endGate) - const uint16_t startGate = 0; - const uint16_t endGate = std::min( - startGate + gates * gateSize, common::MAX_DATA_MOMENT_GATES); + const std::uint16_t nextRadial = + (radial == radialData->number_of_radials() - 1) ? 0 : radial + 1; + const auto& nextDataMomentsArray8 = radialData->level(nextRadial); - for (uint16_t gate = startGate, i = 0; gate + gateSize <= endGate; + for (std::uint16_t gate = startGate, i = 0; gate + gateSize <= endGate; gate += gateSize, ++i) { size_t vertexCount = (gate > 0) ? 6 : 3; - // Store data moment value - uint8_t dataValue = - (i < dataMomentsArray8.size()) ? dataMomentsArray8[i] : 0; - if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) + if (!smoothingEnabled) { - continue; - } + // Store data moment value + const uint8_t dataValue = + (i < dataMomentsArray8.size()) ? dataMomentsArray8[i] : 0; + if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) + { + continue; + } - for (size_t m = 0; m < vertexCount; m++) + for (size_t m = 0; m < vertexCount; m++) + { + dataMoments8[mIndex++] = dataValue; + } + } + else if (gate > 0) { - dataMoments8[mIndex++] = dataValue; + // Validate indices are all in range + if (i + 1 >= numberOfDataMomentGates) + { + continue; + } + + const std::uint8_t& dm1 = dataMomentsArray8[i]; + const std::uint8_t& dm2 = dataMomentsArray8[i + 1]; + const std::uint8_t& dm3 = nextDataMomentsArray8[i]; + const std::uint8_t& dm4 = nextDataMomentsArray8[i + 1]; + + if ((!showSmoothedRangeFolding && // + (dm1 < snrThreshold || dm1 == RANGE_FOLDED) && + (dm2 < snrThreshold || dm2 == RANGE_FOLDED) && + (dm3 < snrThreshold || dm3 == RANGE_FOLDED) && + (dm4 < snrThreshold || dm4 == RANGE_FOLDED)) || + (showSmoothedRangeFolding && // + dm1 < snrThreshold && dm1 != RANGE_FOLDED && + dm2 < snrThreshold && dm2 != RANGE_FOLDED && + dm3 < snrThreshold && dm3 != RANGE_FOLDED && + dm4 < snrThreshold && dm4 != RANGE_FOLDED)) + { + // Skip only if all data moments are hidden + continue; + } + + // The order must match the store vertices section below + dataMoments8[mIndex++] = p->RemapDataMoment(dm1); + dataMoments8[mIndex++] = p->RemapDataMoment(dm2); + dataMoments8[mIndex++] = p->RemapDataMoment(dm4); + dataMoments8[mIndex++] = p->RemapDataMoment(dm1); + dataMoments8[mIndex++] = p->RemapDataMoment(dm3); + dataMoments8[mIndex++] = p->RemapDataMoment(dm4); + } + else + { + // If smoothing is enabled, gate should never start at zero + // (radar site origin) + logger_->error("Smoothing enabled, gate should not start at zero"); + continue; } // Store vertices @@ -374,19 +481,17 @@ void Level3RadialView::ComputeSweep() vertices[vIndex++] = coordinates[offset2]; vertices[vIndex++] = coordinates[offset2 + 1]; - vertices[vIndex++] = coordinates[offset3]; - vertices[vIndex++] = coordinates[offset3 + 1]; + vertices[vIndex++] = coordinates[offset4]; + vertices[vIndex++] = coordinates[offset4 + 1]; + + vertices[vIndex++] = coordinates[offset1]; + vertices[vIndex++] = coordinates[offset1 + 1]; vertices[vIndex++] = coordinates[offset3]; vertices[vIndex++] = coordinates[offset3 + 1]; vertices[vIndex++] = coordinates[offset4]; vertices[vIndex++] = coordinates[offset4 + 1]; - - vertices[vIndex++] = coordinates[offset2]; - vertices[vIndex++] = coordinates[offset2 + 1]; - - vertexCount = 6; } else { @@ -409,8 +514,6 @@ void Level3RadialView::ComputeSweep() vertices[vIndex++] = coordinates[offset2]; vertices[vIndex++] = coordinates[offset2 + 1]; - - vertexCount = 3; } } } @@ -428,8 +531,24 @@ void Level3RadialView::ComputeSweep() Q_EMIT SweepComputed(); } +std::uint8_t +Level3RadialView::Impl::RemapDataMoment(std::uint8_t dataMoment) const +{ + if (dataMoment != 0 && + (dataMoment != RANGE_FOLDED || showSmoothedRangeFolding_)) + { + return dataMoment; + } + else + { + return edgeValue_; + } +} + void Level3RadialView::Impl::ComputeCoordinates( - const std::shared_ptr& radialData) + const std::shared_ptr& radialData, + bool smoothingEnabled, + float gateSize) { logger_->debug("ComputeCoordinates()"); @@ -440,51 +559,72 @@ void Level3RadialView::Impl::ComputeCoordinates( auto radarProductManager = self_->radar_product_manager(); auto radarSite = radarProductManager->radar_site(); - const float gateSize = radarProductManager->gate_size(); const double radarLatitude = radarSite->latitude(); const double radarLongitude = radarSite->longitude(); // Calculate azimuth coordinates timer.start(); + coordinates_.resize(kMaxCoordinates_); + const std::uint16_t numRadials = radialData->number_of_radials(); const std::uint16_t numRangeBins = radialData->number_of_range_bins(); auto radials = boost::irange(0u, numRadials); auto gates = boost::irange(0u, numRangeBins); - std::for_each(std::execution::par_unseq, - radials.begin(), - radials.end(), - [&](std::uint32_t radial) - { - const float angle = radialData->start_angle(radial); + const float gateRangeOffset = (smoothingEnabled) ? + // Center of the first gate is half the gate + // size distance from the radar site + 0.5f : + // Far end of the first gate is the gate + // size distance from the radar site + 1.0f; - std::for_each(std::execution::par_unseq, - gates.begin(), - gates.end(), - [&](std::uint32_t gate) - { - const std::uint32_t radialGate = - radial * common::MAX_DATA_MOMENT_GATES + - gate; - const float range = (gate + 1) * gateSize; - const std::size_t offset = radialGate * 2; + std::for_each( + std::execution::par_unseq, + radials.begin(), + radials.end(), + [&](std::uint32_t radial) + { + float angle = radialData->start_angle(radial); - double latitude; - double longitude; + if (smoothingEnabled) + { + static constexpr float kDeltaAngleFactor = 0.5f; + angle += radialData->delta_angle(radial) * kDeltaAngleFactor; + } - geodesic.Direct(radarLatitude, - radarLongitude, - angle, - range, - latitude, - longitude); + std::for_each( + std::execution::par_unseq, + gates.begin(), + gates.end(), + [&](std::uint32_t gate) + { + const std::uint32_t radialGate = + radial * common::MAX_DATA_MOMENT_GATES + gate; + const float range = + (static_cast(gate) + gateRangeOffset) * gateSize; + const std::size_t offset = static_cast(radialGate) * 2; + if (offset + 1 >= coordinates_.size()) + { + return; + } - coordinates_[offset] = latitude; - coordinates_[offset + 1] = longitude; - }); - }); + double latitude = 0.0; + double longitude = 0.0; + + geodesic.Direct(radarLatitude, + radarLongitude, + angle, + range, + latitude, + longitude); + + coordinates_[offset] = static_cast(latitude); + coordinates_[offset + 1] = static_cast(longitude); + }); + }); timer.stop(); logger_->debug("Coordinates calculated in {}", timer.format(6, "%ws")); } diff --git a/scwx-qt/source/scwx/qt/view/level3_radial_view.hpp b/scwx-qt/source/scwx/qt/view/level3_radial_view.hpp index f99f4e63..86d550e6 100644 --- a/scwx-qt/source/scwx/qt/view/level3_radial_view.hpp +++ b/scwx-qt/source/scwx/qt/view/level3_radial_view.hpp @@ -6,11 +6,7 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace view +namespace scwx::qt::view { class Level3RadialView : public Level3ProductView @@ -21,17 +17,24 @@ public: explicit Level3RadialView( const std::string& product, std::shared_ptr radarProductManager); - ~Level3RadialView(); + ~Level3RadialView() override; - float range() const override; - std::chrono::system_clock::time_point sweep_time() const override; - std::uint16_t vcp() const override; - const std::vector& vertices() const override; + Level3RadialView(const Level3RadialView&) = delete; + Level3RadialView(Level3RadialView&&) = delete; + Level3RadialView& operator=(const Level3RadialView&) = delete; + Level3RadialView& operator=(Level3RadialView&&) = delete; - std::tuple + [[nodiscard]] std::optional elevation() const override; + [[nodiscard]] float range() const override; + [[nodiscard]] std::chrono::system_clock::time_point + sweep_time() const override; + [[nodiscard]] std::uint16_t vcp() const override; + [[nodiscard]] const std::vector& vertices() const override; + + [[nodiscard]] std::tuple GetMomentData() const override; - std::optional + [[nodiscard]] std::optional GetBinLevel(const common::Coordinate& coordinate) const override; static std::shared_ptr @@ -49,6 +52,4 @@ private: std::unique_ptr p; }; -} // namespace view -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::view diff --git a/scwx-qt/source/scwx/qt/view/level3_raster_view.cpp b/scwx-qt/source/scwx/qt/view/level3_raster_view.cpp index fefeb587..3056cc03 100644 --- a/scwx-qt/source/scwx/qt/view/level3_raster_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level3_raster_view.cpp @@ -33,12 +33,20 @@ public: } ~Level3RasterViewImpl() { threadPool_.join(); }; + [[nodiscard]] inline std::uint8_t + RemapDataMoment(std::uint8_t dataMoment) const; + boost::asio::thread_pool threadPool_ {1u}; - std::vector vertices_; - std::vector dataMoments8_; + std::vector vertices_ {}; + std::vector dataMoments8_ {}; + std::uint8_t edgeValue_ {}; + + bool showSmoothedRangeFolding_ {false}; std::shared_ptr lastRasterData_ {}; + bool lastShowSmoothedRangeFolding_ {false}; + bool lastSmoothingEnabled_ {false}; float latitude_; float longitude_; @@ -101,7 +109,7 @@ std::tuple Level3RasterView::GetMomentData() const void Level3RasterView::ComputeSweep() { - logger_->debug("ComputeSweep()"); + logger_->trace("ComputeSweep()"); boost::timer::cpu_timer timer; @@ -109,6 +117,9 @@ void Level3RasterView::ComputeSweep() std::shared_ptr radarProductManager = radar_product_manager(); + const bool smoothingEnabled = smoothing_enabled(); + p->showSmoothedRangeFolding_ = show_smoothed_range_folding(); + const bool& showSmoothedRangeFolding = p->showSmoothedRangeFolding_; // Retrieve message from Radar Product Manager std::shared_ptr message; @@ -139,7 +150,10 @@ void Level3RasterView::ComputeSweep() Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData); return; } - else if (gpm == graphic_product_message()) + else if (gpm == graphic_product_message() && + smoothingEnabled == p->lastSmoothingEnabled_ && + (showSmoothedRangeFolding == p->lastShowSmoothedRangeFolding_ || + !smoothingEnabled)) { // Skip if this is the message we previously processed Q_EMIT SweepNotComputed(types::NoUpdateReason::NoChange); @@ -147,6 +161,9 @@ void Level3RasterView::ComputeSweep() } set_graphic_product_message(gpm); + p->lastShowSmoothedRangeFolding_ = showSmoothedRangeFolding; + p->lastSmoothingEnabled_ = smoothingEnabled; + // A message with radial data should have a Product Description Block and // Product Symbology Block std::shared_ptr descriptionBlock = @@ -169,6 +186,8 @@ void Level3RasterView::ComputeSweep() return; } + logger_->debug("Computing Sweep"); + // A message with raster data should have a Raster Data Packet std::shared_ptr rasterData = nullptr; @@ -229,16 +248,18 @@ void Level3RasterView::ComputeSweep() const GeographicLib::Geodesic& geodesic = util::GeographicLib::DefaultGeodesic(); - const uint16_t xResolution = descriptionBlock->x_resolution_raw(); - const uint16_t yResolution = descriptionBlock->y_resolution_raw(); - double iCoordinate = + const std::uint16_t xResolution = descriptionBlock->x_resolution_raw(); + const std::uint16_t yResolution = descriptionBlock->y_resolution_raw(); + const double iCoordinate = (-rasterData->i_coordinate_start() - 1.0 - p->range_) * 1000.0; - double jCoordinate = + const double jCoordinate = (rasterData->j_coordinate_start() + 1.0 + p->range_) * 1000.0; + const double xOffset = (smoothingEnabled) ? xResolution * 0.5 : 0.0; + const double yOffset = (smoothingEnabled) ? yResolution * 0.5 : 0.0; - size_t numCoordinates = + const std::size_t numCoordinates = static_cast(rows + 1) * static_cast(maxColumns + 1); - auto coordinateRange = + const auto coordinateRange = boost::irange(0, static_cast(numCoordinates)); std::vector coordinates; @@ -258,8 +279,8 @@ void Level3RasterView::ComputeSweep() const uint32_t col = index % (rows + 1); const uint32_t row = index / (rows + 1); - const double i = iCoordinate + xResolution * col; - const double j = jCoordinate - yResolution * row; + const double i = iCoordinate + xResolution * col + xOffset; + const double j = jCoordinate - yResolution * row - yOffset; // Calculate polar coordinates based on i and j const double angle = std::atan2(i, j) * 180.0 / M_PI; @@ -297,25 +318,83 @@ void Level3RasterView::ComputeSweep() // Compute threshold at which to display an individual bin const uint16_t snrThreshold = descriptionBlock->threshold(); - for (size_t row = 0; row < rasterData->number_of_rows(); ++row) + const std::size_t rowCount = (smoothingEnabled) ? + rasterData->number_of_rows() - 1 : + rasterData->number_of_rows(); + + if (smoothingEnabled) { - const auto dataMomentsArray8 = + // For most products other than reflectivity, the edge should not go to + // the bottom of the color table + p->edgeValue_ = ComputeEdgeValue(); + } + + for (std::size_t row = 0; row < rowCount; ++row) + { + const std::size_t nextRow = + (row == static_cast(rasterData->number_of_rows() - 1)) ? + 0 : + row + 1; + + const auto& dataMomentsArray8 = rasterData->level(static_cast(row)); + const auto& nextDataMomentsArray8 = + rasterData->level(static_cast(nextRow)); for (size_t bin = 0; bin < dataMomentsArray8.size(); ++bin) { - constexpr size_t vertexCount = 6; - - // Store data moment value - uint8_t dataValue = dataMomentsArray8[bin]; - if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) + if (!smoothingEnabled) { - continue; + static constexpr std::size_t vertexCount = 6; + + // Store data moment value + const std::uint8_t& dataValue = dataMomentsArray8[bin]; + if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) + { + continue; + } + + for (size_t m = 0; m < vertexCount; m++) + { + dataMoments8[mIndex++] = dataValue; + } } - - for (size_t m = 0; m < vertexCount; m++) + else { - dataMoments8[mIndex++] = dataValue; + // Validate indices are all in range + if (bin + 1 >= dataMomentsArray8.size() || + bin + 1 >= nextDataMomentsArray8.size()) + { + continue; + } + + const std::uint8_t& dm1 = dataMomentsArray8[bin]; + const std::uint8_t& dm2 = dataMomentsArray8[bin + 1]; + const std::uint8_t& dm3 = nextDataMomentsArray8[bin]; + const std::uint8_t& dm4 = nextDataMomentsArray8[bin + 1]; + + if ((!showSmoothedRangeFolding && // + (dm1 < snrThreshold || dm1 == RANGE_FOLDED) && + (dm2 < snrThreshold || dm2 == RANGE_FOLDED) && + (dm3 < snrThreshold || dm3 == RANGE_FOLDED) && + (dm4 < snrThreshold || dm4 == RANGE_FOLDED)) || + (showSmoothedRangeFolding && // + dm1 < snrThreshold && dm1 != RANGE_FOLDED && + dm2 < snrThreshold && dm2 != RANGE_FOLDED && + dm3 < snrThreshold && dm3 != RANGE_FOLDED && + dm4 < snrThreshold && dm4 != RANGE_FOLDED)) + { + // Skip only if all data moments are hidden + continue; + } + + // The order must match the store vertices section below + dataMoments8[mIndex++] = p->RemapDataMoment(dm1); + dataMoments8[mIndex++] = p->RemapDataMoment(dm2); + dataMoments8[mIndex++] = p->RemapDataMoment(dm4); + dataMoments8[mIndex++] = p->RemapDataMoment(dm1); + dataMoments8[mIndex++] = p->RemapDataMoment(dm3); + dataMoments8[mIndex++] = p->RemapDataMoment(dm4); } // Store vertices @@ -330,17 +409,17 @@ void Level3RasterView::ComputeSweep() vertices[vIndex++] = coordinates[offset2]; vertices[vIndex++] = coordinates[offset2 + 1]; - vertices[vIndex++] = coordinates[offset3]; - vertices[vIndex++] = coordinates[offset3 + 1]; + vertices[vIndex++] = coordinates[offset4]; + vertices[vIndex++] = coordinates[offset4 + 1]; + + vertices[vIndex++] = coordinates[offset1]; + vertices[vIndex++] = coordinates[offset1 + 1]; vertices[vIndex++] = coordinates[offset3]; vertices[vIndex++] = coordinates[offset3 + 1]; vertices[vIndex++] = coordinates[offset4]; vertices[vIndex++] = coordinates[offset4 + 1]; - - vertices[vIndex++] = coordinates[offset2]; - vertices[vIndex++] = coordinates[offset2 + 1]; } } vertices.resize(vIndex); @@ -357,6 +436,20 @@ void Level3RasterView::ComputeSweep() Q_EMIT SweepComputed(); } +std::uint8_t +Level3RasterViewImpl::RemapDataMoment(std::uint8_t dataMoment) const +{ + if (dataMoment != 0 && + (dataMoment != RANGE_FOLDED || showSmoothedRangeFolding_)) + { + return dataMoment; + } + else + { + return edgeValue_; + } +} + std::optional Level3RasterView::GetBinLevel(const common::Coordinate& coordinate) const { diff --git a/scwx-qt/source/scwx/qt/view/overlay_product_view.cpp b/scwx-qt/source/scwx/qt/view/overlay_product_view.cpp index c33494c2..ccc41b9d 100644 --- a/scwx-qt/source/scwx/qt/view/overlay_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/overlay_product_view.cpp @@ -116,8 +116,9 @@ void OverlayProductView::Impl::ConnectRadarProductManager() radarProductManager_.get(), &manager::RadarProductManager::NewDataAvailable, self_, - [this](common::RadarProductGroup group, - const std::string& product, + [this](common::RadarProductGroup group, + const std::string& product, + bool /*isChunks*/, std::chrono::system_clock::time_point latestTime) { if (autoRefreshEnabled_ && @@ -186,7 +187,7 @@ void OverlayProductView::Impl::LoadProduct( header.date_of_message(), header.time_of_message() * 1000); // If the record is from the last 30 minutes - if (productTime + 30min >= std::chrono::system_clock::now() || + if (productTime + 30min >= scwx::util::time::now() || (selectedTime_ != std::chrono::system_clock::time_point {} && productTime + 30min >= selectedTime_)) { diff --git a/scwx-qt/source/scwx/qt/view/radar_product_view.cpp b/scwx-qt/source/scwx/qt/view/radar_product_view.cpp index e2ca6c21..dc50383c 100644 --- a/scwx-qt/source/scwx/qt/view/radar_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/radar_product_view.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -28,26 +29,44 @@ class RadarProductViewImpl { public: explicit RadarProductViewImpl( + RadarProductView* self, std::shared_ptr radarProductManager) : + self_ {self}, initialized_ {false}, sweepMutex_ {}, selectedTime_ {}, radarProductManager_ {radarProductManager} { + auto& productSettings = settings::ProductSettings::Instance(); + connection_ = productSettings.changed_signal().connect( + [this]() + { + showSmoothedRangeFolding_ = settings::ProductSettings::Instance() + .show_smoothed_range_folding() + .GetValue(); + self_->Update(); + }); + ; } ~RadarProductViewImpl() {} + RadarProductView* self_; + bool initialized_; std::mutex sweepMutex_; std::chrono::system_clock::time_point selectedTime_; + bool showSmoothedRangeFolding_ {false}; + bool smoothingEnabled_ {false}; std::shared_ptr radarProductManager_; + + boost::signals2::scoped_connection connection_; }; RadarProductView::RadarProductView( std::shared_ptr radarProductManager) : - p(std::make_unique(radarProductManager)) {}; + p(std::make_unique(this, radarProductManager)) {}; RadarProductView::~RadarProductView() = default; const std::vector& @@ -66,9 +85,9 @@ std::uint16_t RadarProductView::color_table_max() const return kDefaultColorTableMax_; } -float RadarProductView::elevation() const +std::optional RadarProductView::elevation() const { - return 0.0f; + return {}; } std::shared_ptr @@ -87,6 +106,16 @@ std::chrono::system_clock::time_point RadarProductView::selected_time() const return p->selectedTime_; } +bool RadarProductView::show_smoothed_range_folding() const +{ + return p->showSmoothedRangeFolding_; +} + +bool RadarProductView::smoothing_enabled() const +{ + return p->smoothingEnabled_; +} + std::chrono::system_clock::time_point RadarProductView::sweep_time() const { return {}; @@ -105,6 +134,11 @@ void RadarProductView::set_radar_product_manager( ConnectRadarProductManager(); } +void RadarProductView::set_smoothing_enabled(bool smoothingEnabled) +{ + p->smoothingEnabled_ = smoothingEnabled; +} + void RadarProductView::Initialize() { ComputeSweep(); diff --git a/scwx-qt/source/scwx/qt/view/radar_product_view.hpp b/scwx-qt/source/scwx/qt/view/radar_product_view.hpp index c695a9e5..2801b74e 100644 --- a/scwx-qt/source/scwx/qt/view/radar_product_view.hpp +++ b/scwx-qt/source/scwx/qt/view/radar_product_view.hpp @@ -16,11 +16,7 @@ #include #include -namespace scwx -{ -namespace qt -{ -namespace view +namespace scwx::qt::view { class RadarProductViewImpl; @@ -32,27 +28,38 @@ class RadarProductView : public QObject public: explicit RadarProductView( std::shared_ptr radarProductManager); - virtual ~RadarProductView(); + ~RadarProductView() override; - virtual std::shared_ptr color_table() const = 0; - virtual const std::vector& - color_table_lut() const; - virtual std::uint16_t color_table_min() const; - virtual std::uint16_t color_table_max() const; - virtual float elevation() const; - virtual float range() const; - virtual std::chrono::system_clock::time_point sweep_time() const; - virtual float unit_scale() const = 0; - virtual std::string units() const = 0; - virtual std::uint16_t vcp() const = 0; - virtual const std::vector& vertices() const = 0; + RadarProductView(const RadarProductView&) = delete; + RadarProductView(RadarProductView&&) = delete; + RadarProductView& operator=(const RadarProductView&) = delete; + RadarProductView& operator=(RadarProductView&&) = delete; - std::shared_ptr radar_product_manager() const; - std::chrono::system_clock::time_point selected_time() const; - std::mutex& sweep_mutex(); + [[nodiscard]] virtual std::shared_ptr + color_table() const = 0; + [[nodiscard]] virtual const std::vector& + color_table_lut() const; + [[nodiscard]] virtual std::uint16_t color_table_min() const; + [[nodiscard]] virtual std::uint16_t color_table_max() const; + [[nodiscard]] virtual std::optional elevation() const; + [[nodiscard]] virtual float range() const; + [[nodiscard]] virtual std::chrono::system_clock::time_point + sweep_time() const; + [[nodiscard]] virtual float unit_scale() const = 0; + [[nodiscard]] virtual std::string units() const = 0; + [[nodiscard]] virtual std::uint16_t vcp() const = 0; + [[nodiscard]] virtual const std::vector& vertices() const = 0; + + [[nodiscard]] std::shared_ptr + radar_product_manager() const; + [[nodiscard]] std::chrono::system_clock::time_point selected_time() const; + [[nodiscard]] bool show_smoothed_range_folding() const; + [[nodiscard]] bool smoothing_enabled() const; + [[nodiscard]] std::mutex& sweep_mutex(); void set_radar_product_manager( std::shared_ptr radarProductManager); + void set_smoothing_enabled(bool smoothingEnabled); void Initialize(); virtual void @@ -62,24 +69,26 @@ public: void SelectTime(std::chrono::system_clock::time_point time); void Update(); - bool IsInitialized() const; + [[nodiscard]] bool IsInitialized() const; - virtual common::RadarProductGroup GetRadarProductGroup() const = 0; - virtual std::string GetRadarProductName() const = 0; - virtual std::vector GetElevationCuts() const; - virtual std::tuple + [[nodiscard]] virtual common::RadarProductGroup + GetRadarProductGroup() const = 0; + [[nodiscard]] virtual std::string GetRadarProductName() const = 0; + [[nodiscard]] virtual std::vector GetElevationCuts() const; + [[nodiscard]] virtual std::tuple GetMomentData() const = 0; - virtual std::tuple + [[nodiscard]] virtual std::tuple GetCfpMomentData() const; - virtual std::optional + [[nodiscard]] virtual std::optional GetBinLevel(const common::Coordinate& coordinate) const = 0; - virtual std::optional - GetDataLevelCode(std::uint16_t level) const = 0; - virtual std::optional GetDataValue(std::uint16_t level) const = 0; - virtual bool IgnoreUnits() const; + [[nodiscard]] virtual std::optional + GetDataLevelCode(std::uint16_t level) const = 0; + [[nodiscard]] virtual std::optional + GetDataValue(std::uint16_t level) const = 0; + [[nodiscard]] virtual bool IgnoreUnits() const; - virtual std::vector> + [[nodiscard]] virtual std::vector> GetDescriptionFields() const; protected: @@ -101,6 +110,4 @@ private: std::unique_ptr p; }; -} // namespace view -} // namespace qt -} // namespace scwx +} // namespace scwx::qt::view diff --git a/setup-debug.bat b/setup-debug.bat deleted file mode 100644 index 7ad46d78..00000000 --- a/setup-debug.bat +++ /dev/null @@ -1,12 +0,0 @@ -call tools\setup-common.bat - -set build_dir=build-debug -set build_type=Debug -set qt_version=6.7.2 - -mkdir %build_dir% -cmake -B %build_dir% -S . ^ - -DCMAKE_BUILD_TYPE=%build_type% ^ - -DCMAKE_CONFIGURATION_TYPES=%build_type% ^ - -DCMAKE_PREFIX_PATH=C:/Qt/%qt_version%/msvc2019_64 -pause diff --git a/setup-debug.sh b/setup-debug.sh deleted file mode 100755 index 87067a3b..00000000 --- a/setup-debug.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -./tools/setup-common.sh - -build_dir=${1:-build-debug} -build_type=Debug -qt_version=6.7.2 -script_dir="$(dirname "$(readlink -f "$0")")" - -mkdir -p ${build_dir} -cmake -B ${build_dir} -S . \ - -DCMAKE_BUILD_TYPE=${build_type} \ - -DCMAKE_CONFIGURATION_TYPES=${build_type} \ - -DCMAKE_INSTALL_PREFIX=${build_dir}/${build_type}/supercell-wx \ - -DCMAKE_PREFIX_PATH=/opt/Qt/${qt_version}/gcc_64 \ - -G Ninja diff --git a/setup-release.bat b/setup-release.bat deleted file mode 100644 index 3c08d78d..00000000 --- a/setup-release.bat +++ /dev/null @@ -1,12 +0,0 @@ -call tools\setup-common.bat - -set build_dir=build-release -set build_type=Release -set qt_version=6.7.2 - -mkdir %build_dir% -cmake -B %build_dir% -S . ^ - -DCMAKE_BUILD_TYPE=%build_type% ^ - -DCMAKE_CONFIGURATION_TYPES=%build_type% ^ - -DCMAKE_PREFIX_PATH=C:/Qt/%qt_version%/msvc2019_64 -pause diff --git a/setup-release.sh b/setup-release.sh deleted file mode 100755 index 6d81ff8f..00000000 --- a/setup-release.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -./tools/setup-common.sh - -build_dir=${1:-build-release} -build_type=Release -qt_version=6.7.2 -script_dir="$(dirname "$(readlink -f "$0")")" - -mkdir -p ${build_dir} -cmake -B ${build_dir} -S . \ - -DCMAKE_BUILD_TYPE=${build_type} \ - -DCMAKE_CONFIGURATION_TYPES=${build_type} \ - -DCMAKE_INSTALL_PREFIX=${build_dir}/${build_type}/supercell-wx \ - -DCMAKE_PREFIX_PATH=/opt/Qt/${qt_version}/gcc_64 \ - -G Ninja diff --git a/test/.clang-tidy b/test/.clang-tidy new file mode 100644 index 00000000..254b44ad --- /dev/null +++ b/test/.clang-tidy @@ -0,0 +1,20 @@ +Checks: + - '-*' + - 'bugprone-*' + - 'clang-analyzer-*' + - 'cppcoreguidelines-*' + - 'misc-*' + - 'modernize-*' + - 'performance-*' + - '-bugprone-easily-swappable-parameters' + - '-cppcoreguidelines-avoid-magic-numbers' + - '-cppcoreguidelines-avoid-do-while' + - '-cppcoreguidelines-avoid-non-const-global-variables' + - '-cppcoreguidelines-pro-type-reinterpret-cast' + - '-cppcoreguidelines-pro-type-union-access' + - '-misc-include-cleaner' + - '-misc-non-private-member-variables-in-classes' + - '-misc-use-anonymous-namespace' + - '-modernize-return-braced-init-list' + - '-modernize-use-trailing-return-type' +FormatStyle: 'file' diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d1db7851..b5428d86 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.24) set_property(DIRECTORY APPEND diff --git a/test/data b/test/data index 5a91ded6..c68bee74 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit 5a91ded677d4032b0de9370ed767a16708c0ecff +Subproject commit c68bee74549963e9a02e0fa998efad0f10f8256b diff --git a/test/source/scwx/awips/wmo_header.test.cpp b/test/source/scwx/awips/wmo_header.test.cpp new file mode 100644 index 00000000..17cd9706 --- /dev/null +++ b/test/source/scwx/awips/wmo_header.test.cpp @@ -0,0 +1,130 @@ +#include + +#include + +namespace scwx::awips +{ + +static const std::string logPrefix_ = "scwx::awips::wmo_header.test"; + +static const std::string kWmoHeaderSample_ { + "887\n" + "WFUS54 KOUN 280044\n" + "TOROUN"}; + +TEST(WmoHeader, WmoFields) +{ + std::stringstream ss {kWmoHeaderSample_}; + WmoHeader header; + const bool valid = header.Parse(ss); + + EXPECT_EQ(valid, true); + EXPECT_EQ(header.sequence_number(), "887"); + EXPECT_EQ(header.data_type(), "WF"); + EXPECT_EQ(header.geographic_designator(), "US"); + EXPECT_EQ(header.bulletin_id(), "54"); + EXPECT_EQ(header.icao(), "KOUN"); + EXPECT_EQ(header.date_time(), "280044"); + EXPECT_EQ(header.bbb_indicator(), ""); + EXPECT_EQ(header.product_category(), "TOR"); + EXPECT_EQ(header.product_designator(), "OUN"); + EXPECT_EQ(header.GetDateTime(), + std::chrono::sys_time {}); +} + +TEST(WmoHeader, DateHintBeforeParse) +{ + using namespace std::chrono; + + std::stringstream ss {kWmoHeaderSample_}; + WmoHeader header; + + header.SetDateHint(2022y / October); + const bool valid = header.Parse(ss); + + EXPECT_EQ(valid, true); + EXPECT_EQ(header.GetDateTime(), + sys_days {2022y / October / 28d} + 0h + 44min); +} + +TEST(WmoHeader, DateHintAfterParse) +{ + using namespace std::chrono; + + std::stringstream ss {kWmoHeaderSample_}; + WmoHeader header; + + const bool valid = header.Parse(ss); + header.SetDateHint(2022y / October); + + EXPECT_EQ(valid, true); + EXPECT_EQ(header.GetDateTime(), + sys_days {2022y / October / 28d} + 0h + 44min); +} + +TEST(WmoHeader, EndTimeHintSameMonth) +{ + using namespace std::chrono; + + std::stringstream ss {kWmoHeaderSample_}; + WmoHeader header; + + const bool valid = header.Parse(ss); + + auto endTimeHint = sys_days {2022y / October / 29d} + 0h + 0min + 0s; + + EXPECT_EQ(valid, true); + EXPECT_EQ(header.GetDateTime(endTimeHint), + sys_days {2022y / October / 28d} + 0h + 44min); +} + +TEST(WmoHeader, EndTimeHintPreviousMonth) +{ + using namespace std::chrono; + + std::stringstream ss {kWmoHeaderSample_}; + WmoHeader header; + + const bool valid = header.Parse(ss); + + auto endTimeHint = sys_days {2022y / October / 27d} + 0h + 0min + 0s; + + EXPECT_EQ(valid, true); + EXPECT_EQ(header.GetDateTime(endTimeHint), + sys_days {2022y / September / 28d} + 0h + 44min); +} + +TEST(WmoHeader, EndTimeHintPreviousYear) +{ + using namespace std::chrono; + + std::stringstream ss {kWmoHeaderSample_}; + WmoHeader header; + + const bool valid = header.Parse(ss); + + auto endTimeHint = sys_days {2022y / January / 27d} + 0h + 0min + 0s; + + EXPECT_EQ(valid, true); + EXPECT_EQ(header.GetDateTime(endTimeHint), + sys_days {2021y / December / 28d} + 0h + 44min); +} + +TEST(WmoHeader, EndTimeHintIgnored) +{ + using namespace std::chrono; + + std::stringstream ss {kWmoHeaderSample_}; + WmoHeader header; + + header.SetDateHint(2022y / October); + const bool valid = header.Parse(ss); + + auto endTimeHint = sys_days {2020y / January / 1d} + 0h + 0min + 0s; + + EXPECT_EQ(valid, true); + EXPECT_EQ(header.GetDateTime(endTimeHint), + sys_days {2022y / October / 28d} + 0h + 44min); +} + +} // namespace scwx::awips diff --git a/test/source/scwx/common/products.test.cpp b/test/source/scwx/common/products.test.cpp index 5b945bf3..bec8a29b 100644 --- a/test/source/scwx/common/products.test.cpp +++ b/test/source/scwx/common/products.test.cpp @@ -1,6 +1,7 @@ #include #include +#include namespace scwx { diff --git a/test/source/scwx/network/ntp_client.test.cpp b/test/source/scwx/network/ntp_client.test.cpp new file mode 100644 index 00000000..1450b324 --- /dev/null +++ b/test/source/scwx/network/ntp_client.test.cpp @@ -0,0 +1,32 @@ +#include + +#include + +namespace scwx::network +{ + +TEST(NtpClient, Poll) +{ + NtpClient client {}; + + const std::string firstServer = client.RotateServer(); + std::string currentServer = firstServer; + std::string lastServer = firstServer; + bool error = false; + + do + { + client.RunOnce(); + error = client.error(); + + EXPECT_EQ(error, false); + + // Loop until the current server repeats the first server, or fails to + // rotate + lastServer = currentServer; + currentServer = client.RotateServer(); + } while (currentServer != firstServer && currentServer != lastServer && + !error); +} + +} // namespace scwx::network diff --git a/test/source/scwx/provider/iem_api_provider.test.cpp b/test/source/scwx/provider/iem_api_provider.test.cpp new file mode 100644 index 00000000..e3e25669 --- /dev/null +++ b/test/source/scwx/provider/iem_api_provider.test.cpp @@ -0,0 +1,57 @@ +#include + +#include + +namespace scwx::provider +{ + +TEST(IemApiProviderTest, ListTextProducts) +{ + using namespace std::chrono; + using sys_days = time_point; + + auto date = sys_days {2023y / March / 25d}; + + auto torProducts = IemApiProvider::ListTextProducts(date, {}, "TOR"); + + ASSERT_EQ(torProducts.has_value(), true); + EXPECT_EQ(torProducts.value().size(), 35); + + if (torProducts.value().size() >= 1) + { + EXPECT_EQ(torProducts.value().at(0).productId_, + "202303250016-KMEG-WFUS54-TORMEG"); + } + if (torProducts.value().size() >= 35) + { + EXPECT_EQ(torProducts.value().at(34).productId_, + "202303252015-KFFC-WFUS52-TORFFC"); + } +} + +TEST(IemApiProviderTest, LoadTextProducts) +{ + static const std::vector productIds { + "202303250016-KMEG-WFUS54-TORMEG", + "202303252015-KFFC-WFUS52-TORFFC", + "202303311942-KLZK-WWUS54-SVSLZK"}; + + auto textProducts = IemApiProvider::LoadTextProducts(productIds); + + EXPECT_EQ(textProducts.size(), 3); + + if (textProducts.size() >= 1) + { + EXPECT_EQ(textProducts.at(0)->message_count(), 1); + } + if (textProducts.size() >= 2) + { + EXPECT_EQ(textProducts.at(1)->message_count(), 1); + } + if (textProducts.size() >= 3) + { + EXPECT_EQ(textProducts.at(2)->message_count(), 2); + } +} + +} // namespace scwx::provider diff --git a/test/source/scwx/provider/warnings_provider.test.cpp b/test/source/scwx/provider/warnings_provider.test.cpp index 78ef9b95..c1c824da 100644 --- a/test/source/scwx/provider/warnings_provider.test.cpp +++ b/test/source/scwx/provider/warnings_provider.test.cpp @@ -13,53 +13,27 @@ static const std::string& kAlternateUrl {"https://warnings.cod.edu"}; class WarningsProviderTest : public testing::TestWithParam { }; -TEST_P(WarningsProviderTest, ListFiles) -{ - WarningsProvider provider(GetParam()); - - auto [newObjects, totalObjects] = provider.ListFiles(); - - // No objects, skip test - if (totalObjects == 0) - { - GTEST_SKIP(); - } - - EXPECT_GT(newObjects, 0); - EXPECT_GT(totalObjects, 0); - EXPECT_EQ(newObjects, totalObjects); -} TEST_P(WarningsProviderTest, LoadUpdatedFiles) { WarningsProvider provider(GetParam()); - auto [newObjects, totalObjects] = provider.ListFiles(); - auto updatedFiles = provider.LoadUpdatedFiles(); + const std::chrono::sys_time now = + std::chrono::floor(std::chrono::system_clock::now()); + const std::chrono::sys_time startTime = + now - std::chrono::days {3}; + + auto updatedFiles = provider.LoadUpdatedFiles(startTime); // No objects, skip test - if (totalObjects == 0) + if (updatedFiles.empty()) { GTEST_SKIP(); } - EXPECT_GT(newObjects, 0); - EXPECT_GT(totalObjects, 0); - EXPECT_EQ(newObjects, totalObjects); - EXPECT_EQ(updatedFiles.size(), newObjects); + EXPECT_GT(updatedFiles.size(), 0); - auto [newObjects2, totalObjects2] = provider.ListFiles(); - auto updatedFiles2 = provider.LoadUpdatedFiles(); - - // There should be no more than 2 updated warnings files since the last query - // (assumption that the previous newest file was updated, and a new file was - // created on the hour) - EXPECT_LE(newObjects2, 2); - EXPECT_EQ(updatedFiles2.size(), newObjects2); - - // The total number of objects may have changed, since the oldest file could - // have dropped off the list - EXPECT_GT(totalObjects2, 0); + auto updatedFiles2 = provider.LoadUpdatedFiles(); } INSTANTIATE_TEST_SUITE_P(WarningsProvider, diff --git a/test/source/scwx/qt/model/marker_model.test.cpp b/test/source/scwx/qt/model/marker_model.test.cpp new file mode 100644 index 00000000..74ad28a9 --- /dev/null +++ b/test/source/scwx/qt/model/marker_model.test.cpp @@ -0,0 +1,269 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace model +{ + +static const std::string EMPTY_MARKERS_FILE = + std::string(SCWX_TEST_DATA_DIR) + "/json/markers/markers-empty.json"; +static const std::string TEMP_MARKERS_FILE = + std::string(SCWX_TEST_DATA_DIR) + "/json/markers/markers-temp.json"; +static const std::string ONE_MARKERS_FILE = + std::string(SCWX_TEST_DATA_DIR) + "/json/markers/markers-one.json"; +static const std::string FIVE_MARKERS_FILE = + std::string(SCWX_TEST_DATA_DIR) + "/json/markers/markers-five.json"; +static const std::string PART1_MARKER_FILE = + std::string(SCWX_TEST_DATA_DIR) + "/json/markers/markers-part1.json"; + +static std::mutex initializedMutex {}; +static std::condition_variable initializedCond {}; +static bool initialized; + +static const boost::gil::rgba8_pixel_t defaultIconColor = + util::color::ToRgba8PixelT("#ffff0000"); +static const std::string defaultIconName = "images/location-marker"; + +void CompareFiles(const std::string& file1, const std::string& file2) +{ + std::ifstream ifs1 {file1}; + std::stringstream buffer1; + buffer1 << ifs1.rdbuf(); + + std::ifstream ifs2 {file2}; + std::stringstream buffer2; + buffer2 << ifs2.rdbuf(); + + EXPECT_EQ(buffer1.str(), buffer2.str()); +} + +void CopyFile(const std::string& from, const std::string& to) +{ + std::filesystem::copy_file(from, to); + CompareFiles(from, to); +} + +using TestFunction = void(std::shared_ptr, + MarkerModel&); + +void RunTest(const std::string& filename, TestFunction testFunction) +{ + { + main::Application::ResetInitilization(); + MarkerModel model = MarkerModel(); + std::shared_ptr manager = + manager::MarkerManager::Instance(); + + manager->set_marker_settings_path(TEMP_MARKERS_FILE); + + initialized = false; + QObject::connect(manager.get(), + &manager::MarkerManager::MarkersInitialized, + []() + { + std::unique_lock lock(initializedMutex); + initialized = true; + initializedCond.notify_all(); + }); + + main::Application::FinishInitialization(); + + std::unique_lock lock(initializedMutex); + while (!initialized) + { + initializedCond.wait(lock); + } + // This is not jank + model.HandleMarkersInitialized(manager->marker_count()); + + testFunction(manager, model); + } + + EXPECT_EQ(std::filesystem::exists(TEMP_MARKERS_FILE), true); + + CompareFiles(TEMP_MARKERS_FILE, filename); +} + +TEST(MarkerModelTest, CreateJson) +{ + // Verify file doesn't exist prior to test start + EXPECT_EQ(std::filesystem::exists(TEMP_MARKERS_FILE), false); + + RunTest(EMPTY_MARKERS_FILE, + [](std::shared_ptr, MarkerModel&) {}); + + std::filesystem::remove(TEMP_MARKERS_FILE); + EXPECT_EQ(std::filesystem::exists(TEMP_MARKERS_FILE), false); +} + +TEST(MarkerModelTest, LoadEmpty) +{ + CopyFile(EMPTY_MARKERS_FILE, TEMP_MARKERS_FILE); + + RunTest(EMPTY_MARKERS_FILE, + [](std::shared_ptr, MarkerModel&) {}); + + std::filesystem::remove(TEMP_MARKERS_FILE); + EXPECT_EQ(std::filesystem::exists(TEMP_MARKERS_FILE), false); +} + +TEST(MarkerModelTest, AddRemove) +{ + CopyFile(EMPTY_MARKERS_FILE, TEMP_MARKERS_FILE); + + RunTest(ONE_MARKERS_FILE, + [](std::shared_ptr manager, MarkerModel&) + { + manager->add_marker(types::MarkerInfo( + "Null", 0, 0, defaultIconName, defaultIconColor)); + }); + RunTest( + EMPTY_MARKERS_FILE, + [](std::shared_ptr manager, MarkerModel& model) + { + std::optional id = model.getId(0); + EXPECT_TRUE(id); + if (id) + { + manager->remove_marker(*id); + } + }); + + std::filesystem::remove(TEMP_MARKERS_FILE); + EXPECT_EQ(std::filesystem::exists(TEMP_MARKERS_FILE), false); +} + +TEST(MarkerModelTest, AddFive) +{ + CopyFile(EMPTY_MARKERS_FILE, TEMP_MARKERS_FILE); + + RunTest(FIVE_MARKERS_FILE, + [](std::shared_ptr manager, MarkerModel&) + { + manager->add_marker(types::MarkerInfo( + "Null", 0, 0, defaultIconName, defaultIconColor)); + manager->add_marker(types::MarkerInfo( + "North", 90, 0, defaultIconName, defaultIconColor)); + manager->add_marker(types::MarkerInfo( + "South", -90, 0, defaultIconName, defaultIconColor)); + manager->add_marker(types::MarkerInfo( + "East", 0, 90, defaultIconName, defaultIconColor)); + manager->add_marker(types::MarkerInfo( + "West", 0, -90, defaultIconName, defaultIconColor)); + }); + + std::filesystem::remove(TEMP_MARKERS_FILE); + EXPECT_EQ(std::filesystem::exists(TEMP_MARKERS_FILE), false); +} + +TEST(MarkerModelTest, AddFour) +{ + CopyFile(ONE_MARKERS_FILE, TEMP_MARKERS_FILE); + + RunTest(FIVE_MARKERS_FILE, + [](std::shared_ptr manager, MarkerModel&) + { + manager->add_marker(types::MarkerInfo( + "North", 90, 0, defaultIconName, defaultIconColor)); + manager->add_marker(types::MarkerInfo( + "South", -90, 0, defaultIconName, defaultIconColor)); + manager->add_marker(types::MarkerInfo( + "East", 0, 90, defaultIconName, defaultIconColor)); + manager->add_marker(types::MarkerInfo( + "West", 0, -90, defaultIconName, defaultIconColor)); + }); + + std::filesystem::remove(TEMP_MARKERS_FILE); + EXPECT_EQ(std::filesystem::exists(TEMP_MARKERS_FILE), false); +} + +TEST(MarkerModelTest, RemoveFive) +{ + CopyFile(FIVE_MARKERS_FILE, TEMP_MARKERS_FILE); + + RunTest( + EMPTY_MARKERS_FILE, + [](std::shared_ptr manager, MarkerModel& model) + { + std::optional id; + id = model.getId(4); + EXPECT_TRUE(id); + manager->remove_marker(*id); + + id = model.getId(3); + EXPECT_TRUE(id); + manager->remove_marker(*id); + + id = model.getId(2); + EXPECT_TRUE(id); + manager->remove_marker(*id); + + id = model.getId(1); + EXPECT_TRUE(id); + manager->remove_marker(*id); + + id = model.getId(0); + EXPECT_TRUE(id); + manager->remove_marker(*id); + }); + + std::filesystem::remove(TEMP_MARKERS_FILE); + EXPECT_EQ(std::filesystem::exists(TEMP_MARKERS_FILE), false); +} + +TEST(MarkerModelTest, RemoveFour) +{ + CopyFile(FIVE_MARKERS_FILE, TEMP_MARKERS_FILE); + + RunTest( + ONE_MARKERS_FILE, + [](std::shared_ptr manager, MarkerModel& model) + { + std::optional id; + id = model.getId(4); + EXPECT_TRUE(id); + manager->remove_marker(*id); + + id = model.getId(3); + EXPECT_TRUE(id); + manager->remove_marker(*id); + + id = model.getId(2); + EXPECT_TRUE(id); + manager->remove_marker(*id); + + id = model.getId(1); + EXPECT_TRUE(id); + manager->remove_marker(*id); + }); + + std::filesystem::remove(TEMP_MARKERS_FILE); + EXPECT_EQ(std::filesystem::exists(TEMP_MARKERS_FILE), false); +} + +TEST(MarkerModelTest, UpdateFromPart1) +{ + CopyFile(PART1_MARKER_FILE, TEMP_MARKERS_FILE); + + RunTest(ONE_MARKERS_FILE, + [](std::shared_ptr, MarkerModel&) {}); + + std::filesystem::remove(TEMP_MARKERS_FILE); + EXPECT_EQ(std::filesystem::exists(TEMP_MARKERS_FILE), false); +} + +} // namespace model +} // namespace qt +} // namespace scwx diff --git a/test/source/scwx/qt/util/network.test.cpp b/test/source/scwx/qt/util/network.test.cpp new file mode 100644 index 00000000..21524d0c --- /dev/null +++ b/test/source/scwx/qt/util/network.test.cpp @@ -0,0 +1,76 @@ +#include + +#include + + + +namespace scwx +{ +namespace qt +{ +namespace util +{ + +const std::vector> testUrls = { + {" https://example.com/path/to+a+test/file.txt ", + "https://example.com/path/to+a+test/file.txt"}, + {"\thttps://example.com/path/to+a+test/file.txt\t", + "https://example.com/path/to+a+test/file.txt"}, + {"\nhttps://example.com/path/to+a+test/file.txt\n", + "https://example.com/path/to+a+test/file.txt"}, + {"\rhttps://example.com/path/to+a+test/file.txt\r", + "https://example.com/path/to+a+test/file.txt"}, + {"\r\nhttps://example.com/path/to+a+test/file.txt\r\n", + "https://example.com/path/to+a+test/file.txt"}, + {" https://example.com/path/to+a+test/file.txt ", + "https://example.com/path/to+a+test/file.txt"}, + {" \nhttps://example.com/path/to+a+test/file.txt \n ", + "https://example.com/path/to+a+test/file.txt"}, + + // Only tested for this OS because NormalizeUrl uses native separators +#ifdef _WIN32 + {" C:\\path\\to a test\\file.txt ", "C:\\path\\to a test\\file.txt"}, + {"\tC:\\path\\to a test\\file.txt\t", "C:\\path\\to a test\\file.txt"}, + {"\nC:\\path\\to a test\\file.txt\n", "C:\\path\\to a test\\file.txt"}, + {"\rC:\\path\\to a test\\file.txt\r", "C:\\path\\to a test\\file.txt"}, + {"\r\nC:\\path\\to a test\\file.txt\r\n", "C:\\path\\to a test\\file.txt"}, + {" C:\\path\\to a test\\file.txt ", "C:\\path\\to a test\\file.txt"}, + {" \nC:\\path\\to a test\\file.txt \n ", + "C:\\path\\to a test\\file.txt"}, + + {" C:/path/to a test/file.txt ", "C:\\path\\to a test\\file.txt"}, + {"\tC:/path/to a test/file.txt\t", "C:\\path\\to a test\\file.txt"}, + {"\nC:/path/to a test/file.txt\n", "C:\\path\\to a test\\file.txt"}, + {"\rC:/path/to a test/file.txt\r", "C:\\path\\to a test\\file.txt"}, + {"\r\nC:/path/to a test/file.txt\r\n", "C:\\path\\to a test\\file.txt"}, + {" C:/path/to a test/file.txt ", "C:\\path\\to a test\\file.txt"}, + {" \nC:/path/to a test/file.txt \n ", "C:\\path\\to a test\\file.txt"}, +#else + + {" /path/to a test/file.txt ", "/path/to a test/file.txt"}, + {"\t/path/to a test/file.txt\t", "/path/to a test/file.txt"}, + {"\n/path/to a test/file.txt\n", "/path/to a test/file.txt"}, + {"\r/path/to a test/file.txt\r", "/path/to a test/file.txt"}, + {"\r\n/path/to a test/file.txt\r\n", "/path/to a test/file.txt"}, + {" /path/to a test/file.txt ", "/path/to a test/file.txt"}, + {" \n/path/to a test/file.txt \n ", "/path/to a test/file.txt"}, +#endif +}; + +TEST(network, NormalizeUrl) +{ + for (auto& pair : testUrls) + { + const std::string& preNormalized = pair.first; + const std::string& expNormalized = pair.second; + + std::string normalized = network::NormalizeUrl(preNormalized); + EXPECT_EQ(normalized, expNormalized); + } + +} + + +} // namespace util +} // namespace qt +} // namespace scwx diff --git a/test/test.cmake b/test/test.cmake index b3cfedd2..57fbc37e 100644 --- a/test/test.cmake +++ b/test/test.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.24) project(scwx-test CXX) include(GoogleTest) @@ -12,24 +12,29 @@ set(SRC_AWIPS_TESTS source/scwx/awips/coded_location.test.cpp source/scwx/awips/coded_time_motion_location.test.cpp source/scwx/awips/pvtec.test.cpp source/scwx/awips/text_product_file.test.cpp - source/scwx/awips/ugc.test.cpp) + source/scwx/awips/ugc.test.cpp + source/scwx/awips/wmo_header.test.cpp) set(SRC_COMMON_TESTS source/scwx/common/color_table.test.cpp source/scwx/common/products.test.cpp) set(SRC_GR_TESTS source/scwx/gr/placefile.test.cpp) -set(SRC_NETWORK_TESTS source/scwx/network/dir_list.test.cpp) +set(SRC_NETWORK_TESTS source/scwx/network/dir_list.test.cpp + source/scwx/network/ntp_client.test.cpp) set(SRC_PROVIDER_TESTS source/scwx/provider/aws_level2_data_provider.test.cpp source/scwx/provider/aws_level3_data_provider.test.cpp + source/scwx/provider/iem_api_provider.test.cpp source/scwx/provider/warnings_provider.test.cpp) set(SRC_QT_CONFIG_TESTS source/scwx/qt/config/county_database.test.cpp source/scwx/qt/config/radar_site.test.cpp) set(SRC_QT_MANAGER_TESTS source/scwx/qt/manager/settings_manager.test.cpp source/scwx/qt/manager/update_manager.test.cpp) set(SRC_QT_MAP_TESTS source/scwx/qt/map/map_provider.test.cpp) -set(SRC_QT_MODEL_TESTS source/scwx/qt/model/imgui_context_model.test.cpp) +set(SRC_QT_MODEL_TESTS source/scwx/qt/model/imgui_context_model.test.cpp + source/scwx/qt/model/marker_model.test.cpp) set(SRC_QT_SETTINGS_TESTS source/scwx/qt/settings/settings_container.test.cpp source/scwx/qt/settings/settings_variable.test.cpp) set(SRC_QT_UTIL_TESTS source/scwx/qt/util/q_file_input_stream.test.cpp - source/scwx/qt/util/geographic_lib.test.cpp) + source/scwx/qt/util/geographic_lib.test.cpp + source/scwx/qt/util/network.test.cpp) set(SRC_UTIL_TESTS source/scwx/util/float.test.cpp source/scwx/util/rangebuf.test.cpp source/scwx/util/streams.test.cpp diff --git a/tools/aqt-settings.ini b/tools/aqt-settings.ini new file mode 100644 index 00000000..9d20e0a2 --- /dev/null +++ b/tools/aqt-settings.ini @@ -0,0 +1,17 @@ +[aqt] +# Using this mirror instead of download.qt.io because of timeouts in CI +# Below is the default URL of the mirror +# baseurl: https://download.qt.io +baseurl: https://qt.mirror.constant.com + +[requests] +# Mirrors require sha1 instead of sha256 +hash_algorithm: sha1 + +[mirrors] +trusted_mirrors: + https://qt.mirror.constant.com +fallbacks: + https://qt.mirror.constant.com + https://mirrors.ocf.berkeley.edu + https://download.qt.io diff --git a/tools/conan/profiles/scwx-linux_clang-17 b/tools/conan/profiles/scwx-linux_clang-17 new file mode 100644 index 00000000..3ce71b57 --- /dev/null +++ b/tools/conan/profiles/scwx-linux_clang-17 @@ -0,0 +1,8 @@ +[settings] +arch=x86_64 +build_type=Release +compiler=clang +compiler.cppstd=20 +compiler.libcxx=libstdc++11 +compiler.version=17 +os=Linux diff --git a/tools/conan/profiles/scwx-linux_clang-17_armv8 b/tools/conan/profiles/scwx-linux_clang-17_armv8 new file mode 100644 index 00000000..21ab5248 --- /dev/null +++ b/tools/conan/profiles/scwx-linux_clang-17_armv8 @@ -0,0 +1,8 @@ +[settings] +arch=armv8 +build_type=Release +compiler=clang +compiler.cppstd=20 +compiler.libcxx=libstdc++11 +compiler.version=17 +os=Linux diff --git a/tools/conan/profiles/scwx-linux_clang-18 b/tools/conan/profiles/scwx-linux_clang-18 new file mode 100644 index 00000000..ea19016b --- /dev/null +++ b/tools/conan/profiles/scwx-linux_clang-18 @@ -0,0 +1,8 @@ +[settings] +arch=x86_64 +build_type=Release +compiler=clang +compiler.cppstd=20 +compiler.libcxx=libstdc++11 +compiler.version=18 +os=Linux diff --git a/tools/conan/profiles/scwx-linux_clang-18_armv8 b/tools/conan/profiles/scwx-linux_clang-18_armv8 new file mode 100644 index 00000000..8c567fd4 --- /dev/null +++ b/tools/conan/profiles/scwx-linux_clang-18_armv8 @@ -0,0 +1,8 @@ +[settings] +arch=armv8 +build_type=Release +compiler=clang +compiler.cppstd=20 +compiler.libcxx=libstdc++11 +compiler.version=18 +os=Linux diff --git a/tools/conan/profiles/scwx-linux_gcc-11 b/tools/conan/profiles/scwx-linux_gcc-11 new file mode 100644 index 00000000..d0d52fc9 --- /dev/null +++ b/tools/conan/profiles/scwx-linux_gcc-11 @@ -0,0 +1,8 @@ +[settings] +arch=x86_64 +build_type=Release +compiler=gcc +compiler.cppstd=20 +compiler.libcxx=libstdc++11 +compiler.version=11 +os=Linux diff --git a/tools/conan/profiles/scwx-linux_gcc-11_armv8 b/tools/conan/profiles/scwx-linux_gcc-11_armv8 new file mode 100644 index 00000000..803d1c8d --- /dev/null +++ b/tools/conan/profiles/scwx-linux_gcc-11_armv8 @@ -0,0 +1,8 @@ +[settings] +arch=armv8 +build_type=Release +compiler=gcc +compiler.cppstd=20 +compiler.libcxx=libstdc++11 +compiler.version=11 +os=Linux diff --git a/tools/conan/profiles/scwx-linux_gcc-12 b/tools/conan/profiles/scwx-linux_gcc-12 new file mode 100644 index 00000000..06f8e0bc --- /dev/null +++ b/tools/conan/profiles/scwx-linux_gcc-12 @@ -0,0 +1,8 @@ +[settings] +arch=x86_64 +build_type=Release +compiler=gcc +compiler.cppstd=20 +compiler.libcxx=libstdc++11 +compiler.version=12 +os=Linux diff --git a/tools/conan/profiles/scwx-linux_gcc-12_armv8 b/tools/conan/profiles/scwx-linux_gcc-12_armv8 new file mode 100644 index 00000000..0a540c20 --- /dev/null +++ b/tools/conan/profiles/scwx-linux_gcc-12_armv8 @@ -0,0 +1,8 @@ +[settings] +arch=armv8 +build_type=Release +compiler=gcc +compiler.cppstd=20 +compiler.libcxx=libstdc++11 +compiler.version=12 +os=Linux diff --git a/tools/conan/profiles/scwx-linux_gcc-13 b/tools/conan/profiles/scwx-linux_gcc-13 new file mode 100644 index 00000000..25ad5988 --- /dev/null +++ b/tools/conan/profiles/scwx-linux_gcc-13 @@ -0,0 +1,8 @@ +[settings] +arch=x86_64 +build_type=Release +compiler=gcc +compiler.cppstd=20 +compiler.libcxx=libstdc++11 +compiler.version=13 +os=Linux diff --git a/tools/conan/profiles/scwx-linux_gcc-13_armv8 b/tools/conan/profiles/scwx-linux_gcc-13_armv8 new file mode 100644 index 00000000..7e0c0c66 --- /dev/null +++ b/tools/conan/profiles/scwx-linux_gcc-13_armv8 @@ -0,0 +1,8 @@ +[settings] +arch=armv8 +build_type=Release +compiler=gcc +compiler.cppstd=20 +compiler.libcxx=libstdc++11 +compiler.version=13 +os=Linux diff --git a/tools/conan/profiles/scwx-linux_gcc-14 b/tools/conan/profiles/scwx-linux_gcc-14 new file mode 100644 index 00000000..a315550a --- /dev/null +++ b/tools/conan/profiles/scwx-linux_gcc-14 @@ -0,0 +1,8 @@ +[settings] +arch=x86_64 +build_type=Release +compiler=gcc +compiler.cppstd=20 +compiler.libcxx=libstdc++11 +compiler.version=14 +os=Linux diff --git a/tools/conan/profiles/scwx-linux_gcc-14_armv8 b/tools/conan/profiles/scwx-linux_gcc-14_armv8 new file mode 100644 index 00000000..1b95e2bb --- /dev/null +++ b/tools/conan/profiles/scwx-linux_gcc-14_armv8 @@ -0,0 +1,8 @@ +[settings] +arch=armv8 +build_type=Release +compiler=gcc +compiler.cppstd=20 +compiler.libcxx=libstdc++11 +compiler.version=14 +os=Linux diff --git a/tools/conan/profiles/scwx-macos_clang-18 b/tools/conan/profiles/scwx-macos_clang-18 new file mode 100644 index 00000000..f7a5b0ce --- /dev/null +++ b/tools/conan/profiles/scwx-macos_clang-18 @@ -0,0 +1,9 @@ +[settings] +arch=x86_64 +build_type=Release +compiler=clang +compiler.cppstd=20 +compiler.libcxx=libc++ +compiler.version=18 +os=Macos +os.version=12.0 diff --git a/tools/conan/profiles/scwx-macos_clang-18_armv8 b/tools/conan/profiles/scwx-macos_clang-18_armv8 new file mode 100644 index 00000000..65d9afd4 --- /dev/null +++ b/tools/conan/profiles/scwx-macos_clang-18_armv8 @@ -0,0 +1,9 @@ +[settings] +arch=armv8 +build_type=Release +compiler=clang +compiler.cppstd=20 +compiler.libcxx=libc++ +compiler.version=18 +os=Macos +os.version=12.0 diff --git a/tools/conan/profiles/scwx-windows_msvc2022_x64 b/tools/conan/profiles/scwx-windows_msvc2022_x64 new file mode 100644 index 00000000..072e4933 --- /dev/null +++ b/tools/conan/profiles/scwx-windows_msvc2022_x64 @@ -0,0 +1,8 @@ +[settings] +arch=x86_64 +build_type=Release +compiler=msvc +compiler.cppstd=20 +compiler.runtime=dynamic +compiler.version=194 +os=Windows diff --git a/tools/configure-environment.bat b/tools/configure-environment.bat new file mode 100644 index 00000000..bf9118a4 --- /dev/null +++ b/tools/configure-environment.bat @@ -0,0 +1,61 @@ +@setlocal enabledelayedexpansion + +@set script_dir=%~dp0 +@set venv_path=%script_dir%\..\.venv + +:: Assign user-specified Python Virtual Environment +@if not "%~1"=="" ( + if /i "%~1"=="none" ( + set venv_path= + ) else ( + set venv_path=%~f1 + ) +) + +:: Activate Python Virtual Environment +@if defined venv_path ( + echo Activating Python Virtual Environment: %venv_path% + python -m venv %venv_path% + call %venv_path%\Scripts\activate.bat +) + +:: Install Python packages +python -m pip install --upgrade pip +pip install --upgrade -r "%script_dir%\..\requirements.txt" + +:: Configure default Conan profile +@conan profile detect -e + +:: Conan profiles +@set profile_count=1 +@set /a last_profile=profile_count - 1 +@set conan_profile[0]=scwx-windows_msvc2022_x64 + +:: Install Conan profiles +@for /L %%i in (0,1,!last_profile!) do @( + :: Install the base profile + set "profile_name=!conan_profile[%%i]!" + set "profile_path=%script_dir%\conan\profiles\!profile_name!" + conan config install "!profile_path!" -tf profiles + + :: Create debug profile in temp directory + set "debug_profile_name=!profile_name!-debug" + set "debug_profile_path=%TEMP%\!debug_profile_name!" + copy "!profile_path!" "!debug_profile_path!" >nul + + :: Replace build_type=Release with build_type=Debug + powershell -Command "(Get-Content '!debug_profile_path!') -replace 'build_type=Release', 'build_type=Debug' | Set-Content '!debug_profile_path!'" + + :: Install the debug profile + conan config install "!debug_profile_path!" -tf profiles + + :: Remove temporary debug profile + del "!debug_profile_path!" +) + +:: Deactivate Python Virtual Environment +@if defined venv_path ( + call %venv_path%\Scripts\deactivate.bat +) + +@pause diff --git a/tools/configure-environment.sh b/tools/configure-environment.sh new file mode 100755 index 00000000..f88ef504 --- /dev/null +++ b/tools/configure-environment.sh @@ -0,0 +1,98 @@ +#!/bin/bash +script_source="${BASH_SOURCE[0]:-$0}" +script_dir="$(cd "$(dirname "${script_source}")" && pwd)" + +# Assign user-specified Python Virtual Environment +if [ "${1:-}" = "none" ]; then + unset venv_path +else + venv_arg="${1:-${script_dir}/../.venv}" + # Portable way to get absolute path without requiring the directory to exist + case "${venv_arg}" in + /*) venv_path="${venv_arg}" ;; + *) venv_path="$(cd "$(dirname "${venv_arg}")" && pwd)/$(basename "${venv_arg}")" ;; + esac + export venv_path +fi + +# Load custom build settings +if [ -f "${script_dir}/lib/user-setup.sh" ]; then + source "${script_dir}/lib/user-setup.sh" +fi + +# Activate Python Virtual Environment +if [ -n "${venv_path:-}" ]; then + python3 -m venv "${venv_path}" + source "${venv_path}/bin/activate" +fi + +# Detect if a Python Virtual Environment was specified above, or elsewhere +IN_VENV=$(python3 -c 'import sys; print(sys.prefix != getattr(sys, "base_prefix", sys.prefix))') + +if [ "${IN_VENV}" = "True" ]; then + # In a virtual environment, don't use --user + PIP_FLAGS="--upgrade" +else + # Not in a virtual environment, use --user + PIP_FLAGS="--upgrade --user" +fi + +# Install Python packages +python3 -m pip install ${PIP_FLAGS} --upgrade pip +python3 -m pip install ${PIP_FLAGS} -r "${script_dir}/../requirements.txt" + +# Configure default Conan profile +conan profile detect -e + +# Conan profiles +if [[ "$(uname)" == "Darwin" ]]; then + # macOS profiles + conan_profiles=( + "scwx-macos_clang-18" + "scwx-macos_clang-18_armv8" + ) +else + # Linux profiles + conan_profiles=( + "scwx-linux_clang-17" + "scwx-linux_clang-17_armv8" + "scwx-linux_clang-18" + "scwx-linux_clang-18_armv8" + "scwx-linux_gcc-11" + "scwx-linux_gcc-11_armv8" + "scwx-linux_gcc-12" + "scwx-linux_gcc-12_armv8" + "scwx-linux_gcc-13" + "scwx-linux_gcc-13_armv8" + "scwx-linux_gcc-14" + "scwx-linux_gcc-14_armv8" + ) +fi + +# Install Conan profiles +for profile_name in "${conan_profiles[@]}"; do + # Install original profile + conan config install "${script_dir}/conan/profiles/${profile_name}" -tf profiles + + # Create debug profile in temp directory + debug_profile="/tmp/${profile_name}-debug" + cp "${script_dir}/conan/profiles/${profile_name}" "${debug_profile}" + + # Replace build_type=Release with build_type=Debug + if [[ "$(uname)" == "Darwin" ]]; then + sed -i '' 's/build_type=Release/build_type=Debug/g' "${debug_profile}" + else + sed -i 's/build_type=Release/build_type=Debug/g' "${debug_profile}" + fi + + # Install the debug profile + conan config install "${debug_profile}" -tf profiles + + # Remove temporary debug profile + rm "${debug_profile}" +done + +# Deactivate Python Virtual Environment +if [ -n "${venv_path:-}" ]; then + deactivate +fi diff --git a/tools/lib/common-paths.bat b/tools/lib/common-paths.bat new file mode 100644 index 00000000..8cd0cb60 --- /dev/null +++ b/tools/lib/common-paths.bat @@ -0,0 +1 @@ +@set qt_version=6.9.2 diff --git a/tools/lib/common-paths.sh b/tools/lib/common-paths.sh new file mode 100755 index 00000000..aadbe417 --- /dev/null +++ b/tools/lib/common-paths.sh @@ -0,0 +1,2 @@ +#!/bin/bash +export qt_version=6.9.2 diff --git a/tools/lib/run-cmake-configure.bat b/tools/lib/run-cmake-configure.bat new file mode 100644 index 00000000..424bcc34 --- /dev/null +++ b/tools/lib/run-cmake-configure.bat @@ -0,0 +1,24 @@ +@set script_dir=%~dp0 + +@set cmake_args=-B "%build_dir%" -S "%script_dir%\..\.." ^ + -G "%generator%" ^ + -DCMAKE_PREFIX_PATH="%qt_base%/%qt_version%/%qt_arch%" ^ + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES="%script_dir%\..\..\external\cmake-conan\conan_provider.cmake" ^ + -DCONAN_HOST_PROFILE=%conan_profile% ^ + -DCONAN_BUILD_PROFILE=%conan_profile% ^ + -DSCWX_VIRTUAL_ENV=%venv_path% ^ + -DCMAKE_EXPORT_COMPILE_COMMANDS=on + +@if defined build_type ( + set cmake_args=%cmake_args% ^ + -DCMAKE_BUILD_TYPE=%build_type% ^ + -DCMAKE_CONFIGURATION_TYPES=%build_type% +) else ( + :: CMAKE_BUILD_TYPE isn't used to build, but is required by the Conan CMakeDeps generator + set cmake_args=%cmake_args% ^ + -DCMAKE_BUILD_TYPE=Release ^ + -DCMAKE_CONFIGURATION_TYPES=Debug;Release +) + +@mkdir "%build_dir%" +cmake %cmake_args% diff --git a/tools/lib/run-cmake-configure.sh b/tools/lib/run-cmake-configure.sh new file mode 100755 index 00000000..03bbcdb1 --- /dev/null +++ b/tools/lib/run-cmake-configure.sh @@ -0,0 +1,43 @@ +#!/bin/bash +script_source="${BASH_SOURCE[0]:-$0}" +script_dir="$(cd "$(dirname "${script_source}")" && pwd)" + +cmake_args=( + -B "${build_dir}" + -S "${script_dir}/../.." + -G "${generator}" + -DCMAKE_PREFIX_PATH="${qt_base}/${qt_version}/${qt_arch}" + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES="${script_dir}/../../external/cmake-conan/conan_provider.cmake" + -DCONAN_HOST_PROFILE="${conan_profile}" + -DCONAN_BUILD_PROFILE="${conan_profile}" + -DSCWX_VIRTUAL_ENV="${venv_path}" + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON +) + +if [[ -n "${build_type}" ]]; then + cmake_args+=( + -DCMAKE_BUILD_TYPE="${build_type}" + -DCMAKE_CONFIGURATION_TYPES="${build_type}" + -DCMAKE_INSTALL_PREFIX="${build_dir}/${build_type}/supercell-wx" + ) +else + # CMAKE_BUILD_TYPE isn't used to build, but is required by the Conan CMakeDeps generator + cmake_args+=( + -DCMAKE_BUILD_TYPE="Release" + -DCMAKE_CONFIGURATION_TYPES="Debug;Release" + ) +fi + +# Toggle address sanitizer based on argument +if [ "${address_sanitizer}" != "disabled" ]; then + cmake_args+=( + -DSCWX_ADDRESS_SANITIZER=ON + ) +else + cmake_args+=( + -DSCWX_ADDRESS_SANITIZER=OFF + ) +fi + +mkdir -p "${build_dir}" +cmake "${cmake_args[@]}" diff --git a/tools/lib/setup-common.bat b/tools/lib/setup-common.bat new file mode 100644 index 00000000..9ba510c2 --- /dev/null +++ b/tools/lib/setup-common.bat @@ -0,0 +1,39 @@ +@set script_dir=%~dp0 + +:: Import common paths +@call %script_dir%\common-paths.bat + +:: Activate Python Virtual Environment +@if defined venv_path ( + echo Activating Python Virtual Environment: %venv_path% + python -m venv %venv_path% + call %venv_path%\Scripts\activate.bat +) + +:: Install Python packages +python -m pip install --upgrade pip +pip install --upgrade -r "%script_dir%\..\..\requirements.txt" + +@if defined build_type ( + :: Install Conan profile and packages + call %script_dir%\setup-conan.bat +) else ( + :: Install Conan profile and debug packages + set build_type=Debug + call %script_dir%\setup-conan.bat + + :: Install Conan profile and release packages + set build_type=Release + call %script_dir%\setup-conan.bat + + :: Unset build_type + set build_type= +) + +:: Run CMake Configure +@call %script_dir%\run-cmake-configure.bat + +:: Deactivate Python Virtual Environment +@if defined venv_path ( + call %venv_path%\Scripts\deactivate.bat +) diff --git a/tools/lib/setup-common.sh b/tools/lib/setup-common.sh new file mode 100755 index 00000000..b11270e8 --- /dev/null +++ b/tools/lib/setup-common.sh @@ -0,0 +1,56 @@ +#!/bin/bash +script_source="${BASH_SOURCE[0]:-$0}" +script_dir="$(cd "$(dirname "${script_source}")" && pwd)" + +# Import common paths +source "${script_dir}/common-paths.sh" + +# Load custom build settings +if [ -f "${script_dir}/user-setup.sh" ]; then + source "${script_dir}/user-setup.sh" +fi + +# Activate python3 Virtual Environment +if [ -n "${venv_path:-}" ]; then + python3 -m venv "${venv_path}" + source "${venv_path}/bin/activate" +fi + +# Detect if a python3 Virtual Environment was specified above, or elsewhere +IN_VENV=$(python3 -c 'import sys; print(sys.prefix != getattr(sys, "base_prefix", sys.prefix))') + +if [ "${IN_VENV}" = "True" ]; then + # In a virtual environment, don't use --user + PIP_FLAGS="--upgrade" +else + # Not in a virtual environment, use --user + PIP_FLAGS="--upgrade --user" +fi + +# Install python3 packages +python3 -m pip install ${PIP_FLAGS} pip +python3 -m pip install ${PIP_FLAGS} -r "${script_dir}/../../requirements.txt" + +if [[ -n "${build_type}" ]]; then + # Install Conan profile and packages + "${script_dir}/setup-conan.sh" +else + # Install Conan profile and debug packages + export build_type=Debug + "${script_dir}/setup-conan.sh" + + # Install Conan profile and release packages + export build_type=Release + "${script_dir}/setup-conan.sh" + + # Unset build_type + unset build_type +fi + +# Run CMake Configure +"${script_dir}/run-cmake-configure.sh" + +# Deactivate python3 Virtual Environment +if [ -n "${venv_path:-}" ]; then + deactivate +fi diff --git a/tools/lib/setup-conan.bat b/tools/lib/setup-conan.bat new file mode 100644 index 00000000..e3f4f444 --- /dev/null +++ b/tools/lib/setup-conan.bat @@ -0,0 +1,15 @@ +@set script_dir=%~dp0 + +:: Configure default Conan profile +conan profile detect -e + +:: Install selected Conan profile +conan config install "%script_dir%\..\conan\profiles\%conan_profile%" -tf profiles + +:: Install Conan packages +conan install "%script_dir%\..\.." ^ + --remote conancenter ^ + --build missing ^ + --profile:all %conan_profile% ^ + --settings:all build_type=%build_type% ^ + --output-folder "%build_dir%\conan" diff --git a/tools/lib/setup-conan.sh b/tools/lib/setup-conan.sh new file mode 100755 index 00000000..0b6c5004 --- /dev/null +++ b/tools/lib/setup-conan.sh @@ -0,0 +1,17 @@ +#!/bin/bash +script_source="${BASH_SOURCE[0]:-$0}" +script_dir="$(cd "$(dirname "${script_source}")" && pwd)" + +# Configure default Conan profile +conan profile detect -e + +# Install selected Conan profile +conan config install "${script_dir}/../conan/profiles/${conan_profile}" -tf profiles + +# Install Conan packages +conan install "${script_dir}/../.." \ + --remote conancenter \ + --build missing \ + --profile:all ${conan_profile} \ + --settings:all build_type=${build_type} \ + --output-folder "${build_dir}/conan" diff --git a/tools/lib/user-setup.example.sh b/tools/lib/user-setup.example.sh new file mode 100644 index 00000000..5ea76d24 --- /dev/null +++ b/tools/lib/user-setup.example.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Example user setup script. Copy as user-setup.sh and modify as required. + +# gcc-13 is not the default gcc version +export CC=/usr/bin/gcc-13 +export CXX=/usr/bin/c++-13 + +# Override conan profile to be gcc-13 +export conan_profile=scwx-linux_gcc-13 diff --git a/tools/net.supercellwx.app.yml b/tools/net.supercellwx.app.yml new file mode 100644 index 00000000..762edf28 --- /dev/null +++ b/tools/net.supercellwx.app.yml @@ -0,0 +1,37 @@ +id: net.supercellwx.app +version: '0.5.1' +runtime: "org.freedesktop.Platform" +runtime-version: "23.08" +sdk: "org.freedesktop.Sdk" +command: supercell-wx +modules: + - name: supercell-wx + buildsystem: simple + build-commands: + - install -Dm644 net.supercellwx.app.desktop /app/share/applications/${FLATPAK_ID}.desktop + - install -Dm644 scwx-256.png /app/share/icons/hicolor/256x256/apps/net.supercellwx.app.png + - install -Dm644 scwx-64.png /app/share/icons/hicolor/64x64/apps/net.supercellwx.app.png + - rm net.supercellwx.app.desktop scwx-256.png scwx-64.png + - cp -r * /app/ + sources: + - type: dir + path: ../../supercell-wx-flatpak + - type: file + path: ../scwx-qt/res/linux/net.supercellwx.app.desktop + - type: file + path: ../scwx-qt/res/icons/scwx-256.png + - type: file + path: ../scwx-qt/res/icons/scwx-64.png + +finish-args: + # X11 + XShm access + - --share=ipc + - --socket=fallback-x11 + # Wayland access + - --socket=wayland + # GPU acceleration if needed + - --device=all + # Needs to talk to the network: + - --share=network + # Needs to save files locally + - --filesystem=xdg-documents diff --git a/tools/scwx_config.cmake b/tools/scwx_config.cmake new file mode 100644 index 00000000..93205f90 --- /dev/null +++ b/tools/scwx_config.cmake @@ -0,0 +1,58 @@ +macro(scwx_output_dirs_setup) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_BINARY_DIR}/Release/bin) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_CURRENT_BINARY_DIR}/RelWithDebInfo/bin) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_CURRENT_BINARY_DIR}/MinSizeRel/bin) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_BINARY_DIR}/Debug/bin) + + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_BINARY_DIR}/Release/lib) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_CURRENT_BINARY_DIR}/RelWithDebInfo/lib) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_CURRENT_BINARY_DIR}/MinSizeRel/lib) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_BINARY_DIR}/Debug/lib) + + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_BINARY_DIR}/Release/lib) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_CURRENT_BINARY_DIR}/RelWithDebInfo/lib) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_CURRENT_BINARY_DIR}/MinSizeRel/lib) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_BINARY_DIR}/Debug/lib) +endmacro() + +macro(scwx_python_setup) + set(SCWX_VIRTUAL_ENV "" CACHE STRING "Python Virtual Environment") + + # Use a Python Virtual Environment + if (SCWX_VIRTUAL_ENV) + set(ENV{VIRTUAL_ENV} "${SCWX_VIRTUAL_ENV}") + + if (WIN32) + set(Python3_EXECUTABLE "$ENV{VIRTUAL_ENV}/Scripts/python.exe") + else() + set(Python3_EXECUTABLE "$ENV{VIRTUAL_ENV}/bin/python") + endif() + + # Add virtual environment to program search paths + set(CMAKE_PROGRAM_PATH "$ENV{VIRTUAL_ENV}/bin" ${CMAKE_PROGRAM_PATH}) + + message(STATUS "Using virtual environment: $ENV{VIRTUAL_ENV}") + else() + message(STATUS "Python virtual environment undefined") + endif() + + # Find Python + find_package(Python3 REQUIRED COMPONENTS Interpreter) + + # Verify we're using the right Python + message(STATUS "Python executable: ${Python3_EXECUTABLE}") + message(STATUS "Python version: ${Python3_VERSION}") + + # Only if we are in an application defined virtual environment + if (SCWX_VIRTUAL_ENV) + # Setup pip + set(PIP_ARGS install --upgrade -r "${CMAKE_SOURCE_DIR}/requirements.txt") + + # Install requirements + execute_process(COMMAND ${Python3_EXECUTABLE} -m pip ${PIP_ARGS} + RESULT_VARIABLE PIP_RESULT) + endif() +endmacro() diff --git a/tools/setup-common.bat b/tools/setup-common.bat deleted file mode 100644 index 8362be70..00000000 --- a/tools/setup-common.bat +++ /dev/null @@ -1,4 +0,0 @@ -pip install --upgrade "conan<2.0" -pip install --upgrade geopandas -pip install --upgrade GitPython -conan profile new default --detect diff --git a/tools/setup-common.sh b/tools/setup-common.sh deleted file mode 100755 index d2c61d5a..00000000 --- a/tools/setup-common.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -pip install --upgrade --user "conan<2.0" -pip install --upgrade --user geopandas -pip install --upgrade --user GitPython -conan profile new default --detect diff --git a/tools/setup-linux-ninja-debug.sh b/tools/setup-linux-ninja-debug.sh new file mode 100755 index 00000000..9b896d40 --- /dev/null +++ b/tools/setup-linux-ninja-debug.sh @@ -0,0 +1,16 @@ +#!/bin/bash +script_dir="$(dirname "$(readlink -f "$0")")" + +export build_dir="$(readlink -f "${1:-${script_dir}/../build-debug}")" +export build_type=Debug +export conan_profile=${2:-scwx-linux_gcc-11} +export generator=Ninja +export qt_base=/opt/Qt +export qt_arch=gcc_64 +export address_sanitizer=${4:-disabled} + +# Assign user-specified Python Virtual Environment +[ "${3:-}" = "none" ] && unset venv_path || export venv_path="$(readlink -f "${3:-${script_dir}/../.venv}")" + +# Perform common setup +"${script_dir}/lib/setup-common.sh" diff --git a/tools/setup-linux-ninja-multi.sh b/tools/setup-linux-ninja-multi.sh new file mode 100755 index 00000000..85bb9a97 --- /dev/null +++ b/tools/setup-linux-ninja-multi.sh @@ -0,0 +1,19 @@ +#!/bin/bash +script_dir="$(dirname "$(readlink -f "$0")")" + +export build_dir="$(readlink -f "${1:-${script_dir}/../build-multi}")" +export conan_profile=${2:-scwx-linux_gcc-11} +export generator="Ninja Multi-Config" +export qt_base=/opt/Qt +export qt_arch=gcc_64 +export address_sanitizer=${4:-disabled} + +# Assign user-specified Python Virtual Environment +[ "${3:-}" = "none" ] && unset venv_path || export venv_path="$(readlink -f "${3:-${script_dir}/../.venv}")" + +# FIXME: aws-sdk-cpp fails to configure using Ninja Multi-Config +echo "Ninja Multi-Config is not supported in Linux" +read -p "Press Enter to continue..." + +# Perform common setup +# "${script_dir}/lib/setup-common.sh" diff --git a/tools/setup-linux-ninja-release.sh b/tools/setup-linux-ninja-release.sh new file mode 100755 index 00000000..e7e10db0 --- /dev/null +++ b/tools/setup-linux-ninja-release.sh @@ -0,0 +1,16 @@ +#!/bin/bash +script_dir="$(dirname "$(readlink -f "$0")")" + +export build_dir="$(readlink -f "${1:-${script_dir}/../build-release}")" +export build_type=Release +export conan_profile=${2:-scwx-linux_gcc-11} +export generator=Ninja +export qt_base=/opt/Qt +export qt_arch=gcc_64 +export address_sanitizer=${4:-disabled} + +# Assign user-specified Python Virtual Environment +[ "${3:-}" = "none" ] && unset venv_path || export venv_path="$(readlink -f "${3:-${script_dir}/../.venv}")" + +# Perform common setup +"${script_dir}/lib/setup-common.sh" diff --git a/tools/setup-macos-ninja-debug.sh b/tools/setup-macos-ninja-debug.sh new file mode 100755 index 00000000..ec24dfe2 --- /dev/null +++ b/tools/setup-macos-ninja-debug.sh @@ -0,0 +1,30 @@ +#!/bin/bash +script_source="${BASH_SOURCE[0]:-$0}" +script_dir="$(cd "$(dirname "${script_source}")" && pwd)" + +export build_dir="$(python3 -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' "${1:-${script_dir}/../build-debug}")" +export build_type=Debug +export conan_profile=${2:-scwx-macos_clang-18_armv8} +export generator=Ninja +export qt_base="/Users/${USER}/Qt" +export qt_arch=macos +export address_sanitizer=${4:-disabled} + +# Set explicit compiler paths +export CC=$(brew --prefix llvm@18)/bin/clang +export CXX=$(brew --prefix llvm@18)/bin/clang++ +export PATH="$(brew --prefix llvm@18)/bin:$PATH" + +export LDFLAGS="-L$(brew --prefix llvm@18)/lib -L$(brew --prefix llvm@18)/lib/c++" +export CPPFLAGS="-I$(brew --prefix llvm@18)/include" + +# Assign user-specified Python Virtual Environment +if [ "${3:-}" = "none" ]; then + unset venv_path +else + # macOS does not have 'readlink -f', use python for realpath + export venv_path="$(python3 -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' "${3:-${script_dir}/../.venv}")" +fi + +# Perform common setup +"${script_dir}/lib/setup-common.sh" diff --git a/tools/setup-macos-ninja-release.sh b/tools/setup-macos-ninja-release.sh new file mode 100755 index 00000000..870dd841 --- /dev/null +++ b/tools/setup-macos-ninja-release.sh @@ -0,0 +1,30 @@ +#!/bin/bash +script_source="${BASH_SOURCE[0]:-$0}" +script_dir="$(cd "$(dirname "${script_source}")" && pwd)" + +export build_dir="$(python3 -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' "${1:-${script_dir}/../build-release}")" +export build_type=Release +export conan_profile=${2:-scwx-macos_clang-18_armv8} +export generator=Ninja +export qt_base="/Users/${USER}/Qt" +export qt_arch=macos +export address_sanitizer=${4:-disabled} + +# Set explicit compiler paths +export CC=$(brew --prefix llvm@18)/bin/clang +export CXX=$(brew --prefix llvm@18)/bin/clang++ +export PATH="$(brew --prefix llvm@18)/bin:$PATH" + +export LDFLAGS="-L$(brew --prefix llvm@18)/lib -L$(brew --prefix llvm@18)/lib/c++" +export CPPFLAGS="-I$(brew --prefix llvm@18)/include" + +# Assign user-specified Python Virtual Environment +if [ "${3:-}" = "none" ]; then + unset venv_path +else + # macOS does not have 'readlink -f', use python for realpath + export venv_path="$(python3 -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' "${3:-${script_dir}/../.venv}")" +fi + +# Perform common setup +"${script_dir}/lib/setup-common.sh" diff --git a/tools/setup-macos-xcode-debug.sh b/tools/setup-macos-xcode-debug.sh new file mode 100755 index 00000000..ced08867 --- /dev/null +++ b/tools/setup-macos-xcode-debug.sh @@ -0,0 +1,34 @@ +#!/bin/bash +script_source="${BASH_SOURCE[0]:-$0}" +script_dir="$(cd "$(dirname "${script_source}")" && pwd)" + +export build_dir="$(python3 -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' "${1:-${script_dir}/../build-xcode-debug}")" +export build_type=Debug +export conan_profile=${2:-scwx-macos_clang-18_armv8} +export generator=Xcode +export qt_base="/Users/${USER}/Qt" +export qt_arch=macos +export address_sanitizer=${4:-disabled} + +# Set explicit compiler paths +export CC=$(brew --prefix llvm@18)/bin/clang +export CXX=$(brew --prefix llvm@18)/bin/clang++ +export PATH="$(brew --prefix llvm@18)/bin:$PATH" + +export LDFLAGS="-L$(brew --prefix llvm@18)/lib -L$(brew --prefix llvm@18)/lib/c++" +export CPPFLAGS="-I$(brew --prefix llvm@18)/include" + +# Assign user-specified Python Virtual Environment +if [ "${3:-}" = "none" ]; then + unset venv_path +else + # macOS does not have 'readlink -f', use python for realpath + export venv_path="$(python3 -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' "${3:-${script_dir}/../.venv}")" +fi + +# FIXME: aws-sdk-cpp fails to configure using Xcode +echo "Xcode is not supported" +read -p "Press Enter to continue..." + +# Perform common setup +# "${script_dir}/lib/setup-common.sh" diff --git a/tools/setup-macos-xcode-multi.sh b/tools/setup-macos-xcode-multi.sh new file mode 100755 index 00000000..f706b072 --- /dev/null +++ b/tools/setup-macos-xcode-multi.sh @@ -0,0 +1,33 @@ +#!/bin/bash +script_source="${BASH_SOURCE[0]:-$0}" +script_dir="$(cd "$(dirname "${script_source}")" && pwd)" + +export build_dir="$(python3 -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' "${1:-${script_dir}/../build-xcode}")" +export conan_profile=${2:-scwx-macos_clang-18_armv8} +export generator=Xcode +export qt_base=/opt/Qt +export qt_arch=gcc_64 +export address_sanitizer=${4:-disabled} + +# Set explicit compiler paths +export CC=$(brew --prefix llvm@18)/bin/clang +export CXX=$(brew --prefix llvm@18)/bin/clang++ +export PATH="$(brew --prefix llvm@18)/bin:$PATH" + +export LDFLAGS="-L$(brew --prefix llvm@18)/lib -L$(brew --prefix llvm@18)/lib/c++" +export CPPFLAGS="-I$(brew --prefix llvm@18)/include" + +# Assign user-specified Python Virtual Environment +if [ "${3:-}" = "none" ]; then + unset venv_path +else + # macOS does not have 'readlink -f', use python for realpath + export venv_path="$(python3 -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' "${3:-${script_dir}/../.venv}")" +fi + +# FIXME: aws-sdk-cpp fails to configure using Xcode +echo "Xcode is not supported" +read -p "Press Enter to continue..." + +# Perform common setup +# "${script_dir}/lib/setup-common.sh" diff --git a/tools/setup-macos-xcode-release.sh b/tools/setup-macos-xcode-release.sh new file mode 100755 index 00000000..ab788c7e --- /dev/null +++ b/tools/setup-macos-xcode-release.sh @@ -0,0 +1,34 @@ +#!/bin/bash +script_source="${BASH_SOURCE[0]:-$0}" +script_dir="$(cd "$(dirname "${script_source}")" && pwd)" + +export build_dir="$(python3 -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' "${1:-${script_dir}/../build-xcode-release}")" +export build_type=Release +export conan_profile=${2:-scwx-macos_clang-18_armv8} +export generator=Xcode +export qt_base="/Users/${USER}/Qt" +export qt_arch=macos +export address_sanitizer=${4:-disabled} + +# Set explicit compiler paths +export CC=$(brew --prefix llvm@18)/bin/clang +export CXX=$(brew --prefix llvm@18)/bin/clang++ +export PATH="$(brew --prefix llvm@18)/bin:$PATH" + +export LDFLAGS="-L$(brew --prefix llvm@18)/lib -L$(brew --prefix llvm@18)/lib/c++" +export CPPFLAGS="-I$(brew --prefix llvm@18)/include" + +# Assign user-specified Python Virtual Environment +if [ "${3:-}" = "none" ]; then + unset venv_path +else + # macOS does not have 'readlink -f', use python for realpath + export venv_path="$(python3 -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' "${3:-${script_dir}/../.venv}")" +fi + +# FIXME: aws-sdk-cpp fails to configure using Xcode +echo "Xcode is not supported" +read -p "Press Enter to continue..." + +# Perform common setup +# "${script_dir}/lib/setup-common.sh" diff --git a/tools/setup-windows-msvc2022-debug.bat b/tools/setup-windows-msvc2022-debug.bat new file mode 100644 index 00000000..9ef029a3 --- /dev/null +++ b/tools/setup-windows-msvc2022-debug.bat @@ -0,0 +1,26 @@ +@set script_dir=%~dp0 + +@set build_dir=%script_dir%\..\build-debug-msvc2022 +@set build_type=Debug +@set conan_profile=scwx-windows_msvc2022_x64 +@set generator=Visual Studio 17 2022 +@set qt_base=C:/Qt +@set qt_arch=msvc2022_64 +@set venv_path=%script_dir%\..\.venv + +:: Assign user-specified build directory +@if not "%~1"=="" set build_dir=%~f1 + +:: Assign user-specified Python Virtual Environment +@if not "%~2"=="" ( + if /i "%~2"=="none" ( + set venv_path= + ) else ( + set venv_path=%~f2 + ) +) + +:: Perform common setup +@call %script_dir%\lib\setup-common.bat + +@pause diff --git a/tools/setup-windows-msvc2022-multi.bat b/tools/setup-windows-msvc2022-multi.bat new file mode 100644 index 00000000..87e19fd3 --- /dev/null +++ b/tools/setup-windows-msvc2022-multi.bat @@ -0,0 +1,25 @@ +@set script_dir=%~dp0 + +@set build_dir=%script_dir%\..\build-msvc2022 +@set conan_profile=scwx-windows_msvc2022_x64 +@set generator=Visual Studio 17 2022 +@set qt_base=C:/Qt +@set qt_arch=msvc2022_64 +@set venv_path=%script_dir%\..\.venv + +:: Assign user-specified build directory +@if not "%~1"=="" set build_dir=%~f1 + +:: Assign user-specified Python Virtual Environment +@if not "%~2"=="" ( + if /i "%~2"=="none" ( + set venv_path= + ) else ( + set venv_path=%~f2 + ) +) + +:: Perform common setup +@call %script_dir%\lib\setup-common.bat + +@pause diff --git a/tools/setup-windows-msvc2022-release.bat b/tools/setup-windows-msvc2022-release.bat new file mode 100644 index 00000000..a4f805cc --- /dev/null +++ b/tools/setup-windows-msvc2022-release.bat @@ -0,0 +1,26 @@ +@set script_dir=%~dp0 + +@set build_dir=%script_dir%\..\build-release-msvc2022 +@set build_type=Release +@set conan_profile=scwx-windows_msvc2022_x64 +@set generator=Visual Studio 17 2022 +@set qt_base=C:/Qt +@set qt_arch=msvc2022_64 +@set venv_path=%script_dir%\..\.venv + +:: Assign user-specified build directory +@if not "%~1"=="" set build_dir=%~f1 + +:: Assign user-specified Python Virtual Environment +@if not "%~2"=="" ( + if /i "%~2"=="none" ( + set venv_path= + ) else ( + set venv_path=%~f2 + ) +) + +:: Perform common setup +@call %script_dir%\lib\setup-common.bat + +@pause diff --git a/tools/setup-windows-ninja-debug.bat b/tools/setup-windows-ninja-debug.bat new file mode 100644 index 00000000..182c7956 --- /dev/null +++ b/tools/setup-windows-ninja-debug.bat @@ -0,0 +1,26 @@ +@set script_dir=%~dp0 + +@set build_dir=%script_dir%\..\build-debug-ninja +@set build_type=Debug +@set conan_profile=scwx-windows_msvc2022_x64 +@set generator=Ninja +@set qt_base=C:/Qt +@set qt_arch=msvc2022_64 +@set venv_path=%script_dir%\..\.venv + +:: Assign user-specified build directory +@if not "%~1"=="" set build_dir=%~f1 + +:: Assign user-specified Python Virtual Environment +@if not "%~2"=="" ( + if /i "%~2"=="none" ( + set venv_path= + ) else ( + set venv_path=%~f2 + ) +) + +:: Perform common setup +@call %script_dir%\lib\setup-common.bat + +@pause diff --git a/tools/setup-windows-ninja-multi.bat b/tools/setup-windows-ninja-multi.bat new file mode 100644 index 00000000..8962fc2b --- /dev/null +++ b/tools/setup-windows-ninja-multi.bat @@ -0,0 +1,25 @@ +@set script_dir=%~dp0 + +@set build_dir=%script_dir%\..\build-ninja +@set conan_profile=scwx-windows_msvc2022_x64 +@set generator=Ninja Multi-Config +@set qt_base=C:/Qt +@set qt_arch=msvc2022_64 +@set venv_path=%script_dir%\..\.venv + +:: Assign user-specified build directory +@if not "%~1"=="" set build_dir=%~f1 + +:: Assign user-specified Python Virtual Environment +@if not "%~2"=="" ( + if /i "%~2"=="none" ( + set venv_path= + ) else ( + set venv_path=%~f2 + ) +) + +:: Perform common setup +@call %script_dir%\lib\setup-common.bat + +@pause diff --git a/tools/setup-windows-ninja-release.bat b/tools/setup-windows-ninja-release.bat new file mode 100644 index 00000000..96ba0e55 --- /dev/null +++ b/tools/setup-windows-ninja-release.bat @@ -0,0 +1,26 @@ +@set script_dir=%~dp0 + +@set build_dir=%script_dir%\..\build-release-ninja +@set build_type=Release +@set conan_profile=scwx-windows_msvc2022_x64 +@set generator=Ninja +@set qt_base=C:/Qt +@set qt_arch=msvc2022_64 +@set venv_path=%script_dir%\..\.venv + +:: Assign user-specified build directory +@if not "%~1"=="" set build_dir=%~f1 + +:: Assign user-specified Python Virtual Environment +@if not "%~2"=="" ( + if /i "%~2"=="none" ( + set venv_path= + ) else ( + set venv_path=%~f2 + ) +) + +:: Perform common setup +@call %script_dir%\lib\setup-common.bat + +@pause diff --git a/wxdata/CMakeLists.txt b/wxdata/CMakeLists.txt index 845ab381..c5d91595 100644 --- a/wxdata/CMakeLists.txt +++ b/wxdata/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.24) set_property(DIRECTORY APPEND diff --git a/wxdata/cpp-feature-tests/chrono_feature_test.cpp b/wxdata/cpp-feature-tests/chrono_feature_test.cpp new file mode 100644 index 00000000..c23acffb --- /dev/null +++ b/wxdata/cpp-feature-tests/chrono_feature_test.cpp @@ -0,0 +1,8 @@ +#include + +int main() +{ +#if (__cpp_lib_chrono < 201907L) +# error("Old chrono version") +#endif +} diff --git a/wxdata/include/scwx/awips/coded_location.hpp b/wxdata/include/scwx/awips/coded_location.hpp index 0ac03d95..1f29faa7 100644 --- a/wxdata/include/scwx/awips/coded_location.hpp +++ b/wxdata/include/scwx/awips/coded_location.hpp @@ -7,8 +7,18 @@ #include #include +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-parameter" +#endif + +#include #include +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + namespace scwx { namespace awips diff --git a/wxdata/include/scwx/awips/coded_time_motion_location.hpp b/wxdata/include/scwx/awips/coded_time_motion_location.hpp index 6b7b9a19..1d950ee0 100644 --- a/wxdata/include/scwx/awips/coded_time_motion_location.hpp +++ b/wxdata/include/scwx/awips/coded_time_motion_location.hpp @@ -8,8 +8,18 @@ #include #include +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-parameter" +#endif + +#include #include +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + namespace scwx { namespace awips diff --git a/wxdata/include/scwx/awips/impact_based_warnings.hpp b/wxdata/include/scwx/awips/impact_based_warnings.hpp index a7b22288..3d956893 100644 --- a/wxdata/include/scwx/awips/impact_based_warnings.hpp +++ b/wxdata/include/scwx/awips/impact_based_warnings.hpp @@ -1,11 +1,16 @@ #pragma once +#include + #include +#include namespace scwx { namespace awips { +namespace ibw +{ enum class ThreatCategory : int { @@ -17,8 +22,18 @@ enum class ThreatCategory : int Unknown }; +struct ImpactBasedWarningInfo +{ + bool hasObservedTag_ {false}; + bool hasTornadoPossibleTag_ {false}; + std::vector threatCategories_ {ThreatCategory::Base}; +}; + +const ImpactBasedWarningInfo& GetImpactBasedWarningInfo(Phenomenon phenomenon); + ThreatCategory GetThreatCategory(const std::string& name); const std::string& GetThreatCategoryName(ThreatCategory threatCategory); +} // namespace ibw } // namespace awips } // namespace scwx diff --git a/wxdata/include/scwx/awips/message.hpp b/wxdata/include/scwx/awips/message.hpp index 6a386065..5cd81000 100644 --- a/wxdata/include/scwx/awips/message.hpp +++ b/wxdata/include/scwx/awips/message.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -13,12 +14,8 @@ # include #endif -namespace scwx +namespace scwx::awips { -namespace awips -{ - -class MessageImpl; class Message { @@ -56,78 +53,165 @@ public: static float SwapFloat(float f) { - std::uint32_t temp; - std::memcpy(&temp, &f, sizeof(std::uint32_t)); - temp = ntohl(temp); - std::memcpy(&f, &temp, sizeof(float)); + if constexpr (std::endian::native == std::endian::little) + { + // Variable is initialized by memcpy + // NOLINTNEXTLINE(cppcoreguidelines-init-variables) + std::uint32_t temp; + std::memcpy(&temp, &f, sizeof(std::uint32_t)); + temp = ntohl(temp); + std::memcpy(&f, &temp, sizeof(float)); + } return f; } - template - static void SwapArray(std::array& arr, - std::size_t size = _Size) + static double SwapDouble(double d) { - std::transform(std::execution::par_unseq, - arr.begin(), - arr.begin() + size, - arr.begin(), - [](float f) { return SwapFloat(f); }); + if constexpr (std::endian::native == std::endian::little) + { + // Variable is initialized by memcpy + // NOLINTNEXTLINE(cppcoreguidelines-init-variables) + std::uint64_t temp; + std::memcpy(&temp, &d, sizeof(std::uint64_t)); + temp = Swap64(temp); + std::memcpy(&d, &temp, sizeof(float)); + } + return d; } - template - static void SwapArray(std::array& arr, - std::size_t size = _Size) + static std::uint64_t Swap64(std::uint64_t value) { - std::transform(std::execution::par_unseq, - arr.begin(), - arr.begin() + size, - arr.begin(), - [](std::int16_t u) { return ntohs(u); }); + if constexpr (std::endian::native == std::endian::little) + { + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + const std::uint32_t high = + ntohl(static_cast(value >> 32)); + const std::uint32_t low = + ntohl(static_cast(value & 0xFFFFFFFFULL)); + return (static_cast(low) << 32) | high; + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + } + else + { + return value; + } } - template - static void SwapArray(std::array& arr, - std::size_t size = _Size) + template + static void SwapArray(std::array& arr, + std::size_t size = kSize) { - std::transform(std::execution::par_unseq, - arr.begin(), - arr.begin() + size, - arr.begin(), - [](std::uint16_t u) { return ntohs(u); }); + if constexpr (std::endian::native == std::endian::little) + { + std::transform(std::execution::par_unseq, + arr.begin(), + arr.begin() + size, + arr.begin(), + [](float f) { return SwapFloat(f); }); + } } - template - static void SwapArray(std::array& arr, - std::size_t size = _Size) + template + static void SwapArray(std::array& arr, + std::size_t size = kSize) { - std::transform(std::execution::par_unseq, - arr.begin(), - arr.begin() + size, - arr.begin(), - [](std::uint32_t u) { return ntohl(u); }); + if constexpr (std::endian::native == std::endian::little) + { + std::transform(std::execution::par_unseq, + arr.begin(), + arr.begin() + size, + arr.begin(), + [](std::int16_t u) { return ntohs(u); }); + } + } + + template + static void SwapArray(std::array& arr, + std::size_t size = kSize) + { + if constexpr (std::endian::native == std::endian::little) + { + std::transform(std::execution::par_unseq, + arr.begin(), + arr.begin() + size, + arr.begin(), + [](std::uint16_t u) { return ntohs(u); }); + } + } + + template + static void SwapArray(std::array& arr, + std::size_t size = kSize) + { + if constexpr (std::endian::native == std::endian::little) + { + std::transform(std::execution::par_unseq, + arr.begin(), + arr.begin() + size, + arr.begin(), + [](std::uint32_t u) { return ntohl(u); }); + } } template static void SwapMap(std::map& m) { - std::for_each(std::execution::par_unseq, - m.begin(), - m.end(), - [](auto& p) { p.second = SwapFloat(p.second); }); + if constexpr (std::endian::native == std::endian::little) + { + std::for_each(std::execution::par_unseq, + m.begin(), + m.end(), + [](auto& p) { p.second = SwapFloat(p.second); }); + } } - static void SwapVector(std::vector& v) + template + static void SwapVector(std::vector& v) { - std::transform(std::execution::par_unseq, - v.begin(), - v.end(), - v.begin(), - [](std::uint16_t u) { return ntohs(u); }); + if constexpr (std::endian::native == std::endian::little) + { + std::transform( + std::execution::par_unseq, + v.begin(), + v.end(), + v.begin(), + [](T u) + { + if constexpr (std::is_same_v || + std::is_same_v) + { + return static_cast(ntohs(u)); + } + else if constexpr (std::is_same_v || + std::is_same_v) + { + return static_cast(ntohl(u)); + } + else if constexpr (std::is_same_v || + std::is_same_v) + { + return static_cast(Swap64(u)); + } + else if constexpr (std::is_same_v) + { + return SwapFloat(u); + } + else if constexpr (std::is_same_v) + { + return SwapDouble(u); + } + else + { + static_assert(std::is_same_v, + "Unsupported type for SwapVector"); + } + }); + } } private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; -} // namespace awips -} // namespace scwx +} // namespace scwx::awips diff --git a/wxdata/include/scwx/awips/phenomenon.hpp b/wxdata/include/scwx/awips/phenomenon.hpp index 013a9947..234e473a 100644 --- a/wxdata/include/scwx/awips/phenomenon.hpp +++ b/wxdata/include/scwx/awips/phenomenon.hpp @@ -69,6 +69,7 @@ enum class Phenomenon }; Phenomenon GetPhenomenon(const std::string& code); +Phenomenon GetPhenomenonFromText(const std::string& text); const std::string& GetPhenomenonCode(Phenomenon phenomenon); const std::string& GetPhenomenonText(Phenomenon phenomenon); diff --git a/wxdata/include/scwx/awips/text_product_file.hpp b/wxdata/include/scwx/awips/text_product_file.hpp index 478a93b4..b0d1c965 100644 --- a/wxdata/include/scwx/awips/text_product_file.hpp +++ b/wxdata/include/scwx/awips/text_product_file.hpp @@ -5,9 +5,7 @@ #include #include -namespace scwx -{ -namespace awips +namespace scwx::awips { class TextProductFileImpl; @@ -24,16 +22,17 @@ public: TextProductFile(TextProductFile&&) noexcept; TextProductFile& operator=(TextProductFile&&) noexcept; - size_t message_count() const; - std::vector> messages() const; - std::shared_ptr message(size_t i) const; + [[nodiscard]] std::size_t message_count() const; + [[nodiscard]] std::vector> + messages() const; + [[nodiscard]] std::shared_ptr message(size_t i) const; bool LoadFile(const std::string& filename); - bool LoadData(std::istream& is); + bool LoadData(const std::string& filename, std::istream& is); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; -} // namespace awips -} // namespace scwx +} // namespace scwx::awips diff --git a/wxdata/include/scwx/awips/text_product_message.hpp b/wxdata/include/scwx/awips/text_product_message.hpp index d402cfd5..373dc223 100644 --- a/wxdata/include/scwx/awips/text_product_message.hpp +++ b/wxdata/include/scwx/awips/text_product_message.hpp @@ -13,6 +13,8 @@ #include #include +#include + namespace scwx { namespace awips @@ -64,9 +66,9 @@ struct Segment std::optional codedLocation_ {}; std::optional codedMotion_ {}; - bool observed_ {false}; - ThreatCategory threatCategory_ {ThreatCategory::Base}; - bool tornadoPossible_ {false}; + bool observed_ {false}; + ibw::ThreatCategory threatCategory_ {ibw::ThreatCategory::Base}; + bool tornadoPossible_ {false}; Segment() = default; @@ -94,6 +96,7 @@ public: TextProductMessage(TextProductMessage&&) noexcept; TextProductMessage& operator=(TextProductMessage&&) noexcept; + [[nodiscard]] boost::uuids::uuid uuid() const; std::string message_content() const; std::shared_ptr wmo_header() const; std::vector mnd_header() const; @@ -105,7 +108,7 @@ public: std::chrono::system_clock::time_point segment_event_begin(std::size_t s) const; - std::size_t data_size() const; + std::size_t data_size() const override; bool Parse(std::istream& is) override; diff --git a/wxdata/include/scwx/awips/wmo_header.hpp b/wxdata/include/scwx/awips/wmo_header.hpp index f3487b6d..889c955e 100644 --- a/wxdata/include/scwx/awips/wmo_header.hpp +++ b/wxdata/include/scwx/awips/wmo_header.hpp @@ -1,11 +1,11 @@ #pragma once +#include #include +#include #include -namespace scwx -{ -namespace awips +namespace scwx::awips { class WmoHeaderImpl; @@ -27,7 +27,7 @@ public: explicit WmoHeader(); ~WmoHeader(); - WmoHeader(const WmoHeader&) = delete; + WmoHeader(const WmoHeader&) = delete; WmoHeader& operator=(const WmoHeader&) = delete; WmoHeader(WmoHeader&&) noexcept; @@ -35,21 +35,53 @@ public: bool operator==(const WmoHeader& o) const; - std::string sequence_number() const; - std::string data_type() const; - std::string geographic_designator() const; - std::string bulletin_id() const; - std::string icao() const; - std::string date_time() const; - std::string bbb_indicator() const; - std::string product_category() const; - std::string product_designator() const; + [[nodiscard]] std::string sequence_number() const; + [[nodiscard]] std::string data_type() const; + [[nodiscard]] std::string geographic_designator() const; + [[nodiscard]] std::string bulletin_id() const; + [[nodiscard]] std::string icao() const; + [[nodiscard]] std::string date_time() const; + [[nodiscard]] std::string bbb_indicator() const; + [[nodiscard]] std::string product_category() const; + [[nodiscard]] std::string product_designator() const; + /** + * @brief Get the WMO date/time + * + * Gets the WMO date/time. Uses the optional date hint provided via + * SetDateHint(std::chrono::year_month). If the date hint has not been + * provided, the endTimeHint parameter is required. + * + * @param [in] endTimeHint The optional end time bounds to provide. This is + * ignored if a date hint has been provided to determine an absolute date. + */ + [[nodiscard]] std::chrono::sys_time GetDateTime( + std::optional endTimeHint = + std::nullopt); + + /** + * @brief Parse a WMO header + * + * @param [in] is The input stream to parse + */ bool Parse(std::istream& is); + /** + * @brief Provide a date hint for the WMO parser + * + * The WMO header contains a date/time in the format DDHHMM. The year and + * month must be derived using another source. The date hint provides the + * additional context required to determine the absolute product time. + * + * This function will update any absolute date/time already calculated, or + * affect the calculation of a subsequent absolute date/time. + * + * @param [in] dateHint The date hint to provide the WMO header parser + */ + void SetDateHint(std::chrono::year_month dateHint); + private: std::unique_ptr p; }; -} // namespace awips -} // namespace scwx +} // namespace scwx::awips diff --git a/wxdata/include/scwx/common/geographic.hpp b/wxdata/include/scwx/common/geographic.hpp index 8945db17..8b234fd2 100644 --- a/wxdata/include/scwx/common/geographic.hpp +++ b/wxdata/include/scwx/common/geographic.hpp @@ -3,6 +3,8 @@ #include #include +#include + namespace scwx { namespace common @@ -46,6 +48,17 @@ enum class DistanceType Miles }; +/** + * Calculate the absolute angle delta between two angles. + * + * @param [in] angle1 First angle + * @param [in] angle2 Second angle + * + * @return Absolute angle delta normalized to [0, 360) + */ +units::degrees GetAngleDelta(units::degrees angle1, + units::degrees angle2); + /** * Calculate the geographic midpoint of a set of coordinates. Uses Method A * described at http://www.geomidpoint.com/calculation.html. diff --git a/wxdata/include/scwx/common/products.hpp b/wxdata/include/scwx/common/products.hpp index 97d9c324..7451aa39 100644 --- a/wxdata/include/scwx/common/products.hpp +++ b/wxdata/include/scwx/common/products.hpp @@ -73,8 +73,9 @@ Level2Product GetLevel2Product(const std::string& name); const std::string& GetLevel3CategoryName(Level3ProductCategory category); const std::string& GetLevel3CategoryDescription(Level3ProductCategory category); -const std::string& -GetLevel3CategoryDefaultProduct(Level3ProductCategory category); +std::string +GetLevel3CategoryDefaultProduct(Level3ProductCategory category, + const Level3ProductCategoryMap& categoryMap); Level3ProductCategory GetLevel3Category(const std::string& categoryName); Level3ProductCategory GetLevel3CategoryByProduct(const std::string& productName); diff --git a/wxdata/include/scwx/network/ntp_client.hpp b/wxdata/include/scwx/network/ntp_client.hpp new file mode 100644 index 00000000..8be912b3 --- /dev/null +++ b/wxdata/include/scwx/network/ntp_client.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include + +namespace scwx::network +{ + +/** + * @brief NTP Client + */ +class NtpClient +{ +public: + explicit NtpClient(); + ~NtpClient(); + + NtpClient(const NtpClient&) = delete; + NtpClient& operator=(const NtpClient&) = delete; + + NtpClient(NtpClient&&) noexcept; + NtpClient& operator=(NtpClient&&) noexcept; + + bool error(); + + [[nodiscard]] std::chrono::system_clock::duration time_offset() const; + + void Start(); + void Stop(); + + void Open(std::string_view host, std::string_view service); + void OpenCurrentServer(); + void Poll(); + std::string RotateServer(); + void RunOnce(); + + void WaitForInitialOffset(); + + static std::shared_ptr Instance(); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace scwx::network diff --git a/wxdata/include/scwx/provider/aws_level2_chunks_data_provider.hpp b/wxdata/include/scwx/provider/aws_level2_chunks_data_provider.hpp new file mode 100644 index 00000000..abd70787 --- /dev/null +++ b/wxdata/include/scwx/provider/aws_level2_chunks_data_provider.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include + +#include + +namespace Aws::S3 +{ +class S3Client; +} // namespace Aws::S3 + +namespace scwx::provider +{ + +/** + * @brief AWS Level 2 Data Provider + */ +class AwsLevel2ChunksDataProvider : public NexradDataProvider +{ +public: + explicit AwsLevel2ChunksDataProvider(const std::string& radarSite); + explicit AwsLevel2ChunksDataProvider(const std::string& radarSite, + const std::string& bucketName, + const std::string& region); + ~AwsLevel2ChunksDataProvider() override; + + AwsLevel2ChunksDataProvider(const AwsLevel2ChunksDataProvider&) = delete; + AwsLevel2ChunksDataProvider& + operator=(const AwsLevel2ChunksDataProvider&) = delete; + + AwsLevel2ChunksDataProvider(AwsLevel2ChunksDataProvider&&) noexcept; + AwsLevel2ChunksDataProvider& + operator=(AwsLevel2ChunksDataProvider&&) noexcept; + + [[nodiscard]] std::chrono::system_clock::time_point + GetTimePointByKey(const std::string& key) const override; + + [[nodiscard]] size_t cache_size() const override; + + [[nodiscard]] std::chrono::system_clock::time_point + last_modified() const override; + [[nodiscard]] std::chrono::seconds update_period() const override; + + std::string FindKey(std::chrono::system_clock::time_point time) override; + std::string FindLatestKey() override; + std::chrono::system_clock::time_point FindLatestTime() override; + std::vector + GetTimePointsByDate(std::chrono::system_clock::time_point date) override; + std::tuple + ListObjects(std::chrono::system_clock::time_point date) override; + std::shared_ptr + LoadObjectByKey(const std::string& key) override; + std::shared_ptr + LoadObjectByTime(std::chrono::system_clock::time_point time) override; + std::pair Refresh() override; + + void RequestAvailableProducts() override; + std::vector GetAvailableProducts() override; + + std::optional GetCurrentElevation(); + + void SetLevel2DataProvider( + const std::shared_ptr& provider); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace scwx::provider diff --git a/wxdata/include/scwx/provider/aws_nexrad_data_provider.hpp b/wxdata/include/scwx/provider/aws_nexrad_data_provider.hpp index 462d293d..d6ddcd7c 100644 --- a/wxdata/include/scwx/provider/aws_nexrad_data_provider.hpp +++ b/wxdata/include/scwx/provider/aws_nexrad_data_provider.hpp @@ -39,12 +39,15 @@ public: std::string FindKey(std::chrono::system_clock::time_point time) override; std::string FindLatestKey() override; + std::chrono::system_clock::time_point FindLatestTime() override; std::vector GetTimePointsByDate(std::chrono::system_clock::time_point date) override; std::tuple ListObjects(std::chrono::system_clock::time_point date) override; std::shared_ptr LoadObjectByKey(const std::string& key) override; + std::shared_ptr + LoadObjectByTime(std::chrono::system_clock::time_point time) override; std::pair Refresh() override; protected: diff --git a/wxdata/include/scwx/provider/iem_api_provider.hpp b/wxdata/include/scwx/provider/iem_api_provider.hpp new file mode 100644 index 00000000..1aac31b2 --- /dev/null +++ b/wxdata/include/scwx/provider/iem_api_provider.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4702) +#endif + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif + +namespace scwx::provider +{ + +/** + * @brief Warnings Provider + */ +class IemApiProvider +{ +public: + explicit IemApiProvider(); + ~IemApiProvider(); + + IemApiProvider(const IemApiProvider&) = delete; + IemApiProvider& operator=(const IemApiProvider&) = delete; + + IemApiProvider(IemApiProvider&&) noexcept; + IemApiProvider& operator=(IemApiProvider&&) noexcept; + + static boost::outcome_v2::result> + ListTextProducts(std::chrono::sys_days date, + std::optional cccc = {}, + std::optional pil = {}); + + template + requires std::same_as, + std::chrono::sys_days> && + std::same_as, + std::string_view> && + std::same_as, std::string_view> + static boost::outcome_v2::result> + ListTextProducts(DateRange dates, CcccRange ccccs, PilRange pils); + + template + requires std::same_as, std::string> + static std::vector> + LoadTextProducts(const Range& textProducts); + +private: + class Impl; + std::unique_ptr p; + + static boost::outcome_v2::result> + ProcessTextProductLists(std::vector& asyncResponses); + static std::vector> + ProcessTextProductFiles( + std::vector>& asyncResponses); + + static const std::shared_ptr logger_; + + static const std::string kBaseUrl_; + static const std::string kListNwsTextProductsEndpoint_; + static const std::string kNwsTextProductEndpoint_; +}; + +} // namespace scwx::provider diff --git a/wxdata/include/scwx/provider/iem_api_provider.ipp b/wxdata/include/scwx/provider/iem_api_provider.ipp new file mode 100644 index 00000000..25187376 --- /dev/null +++ b/wxdata/include/scwx/provider/iem_api_provider.ipp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +#if (__cpp_lib_chrono < 201907L) +# include +#endif + +namespace scwx::provider +{ + +template + requires std::same_as, + std::chrono::sys_days> && + std::same_as, std::string_view> && + std::same_as, std::string_view> +boost::outcome_v2::result> +IemApiProvider::ListTextProducts(DateRange dates, + CcccRange ccccs, + PilRange pils) +{ + using namespace std::chrono; + +#if (__cpp_lib_chrono >= 201907L) + namespace df = std; + + static constexpr std::string_view kDateFormat {"{:%Y-%m-%d}"}; +#else + using namespace date; + namespace df = date; + +# define kDateFormat "%Y-%m-%d" +#endif + + auto formattedDates = dates | ranges::views::transform( + [](const std::chrono::sys_days& date) + { return df::format(kDateFormat, date); }); + + logger_->debug("Listing text products for: {}", + boost::algorithm::join(formattedDates, ", ")); + + std::vector asyncResponses {}; + + for (const auto& [date, cccc, pil] : + ranges::views::cartesian_product(dates, ccccs, pils)) + { + auto parameters = + cpr::Parameters {{"date", df::format(kDateFormat, date)}}; + + // WMO Source Code + if (!cccc.empty()) + { + parameters.Add({"cccc", std::string {cccc}}); + } + + // AFOS / AWIPS ID / 3-6 length identifier + if (!pil.empty()) + { + parameters.Add({"pil", std::string {pil}}); + } + + asyncResponses.emplace_back( + cpr::GetAsync(cpr::Url {kBaseUrl_ + kListNwsTextProductsEndpoint_}, + network::cpr::GetHeader(), + parameters)); + } + + return ProcessTextProductLists(asyncResponses); +} + +template + requires std::same_as, std::string> +std::vector> +IemApiProvider::LoadTextProducts(const Range& textProducts) +{ + auto parameters = cpr::Parameters {{"nolimit", "true"}}; + + logger_->debug("Loading {} text products", textProducts.size()); + + std::vector> asyncResponses {}; + asyncResponses.reserve(textProducts.size()); + + const std::string endpointUrl = kBaseUrl_ + kNwsTextProductEndpoint_; + + for (const auto& productId : textProducts) + { + asyncResponses.emplace_back( + productId, + cpr::GetAsync(cpr::Url {endpointUrl + productId}, + network::cpr::GetHeader(), + parameters)); + } + + return ProcessTextProductFiles(asyncResponses); +} + +#ifdef kDateFormat +# undef kDateFormat +#endif + +} // namespace scwx::provider diff --git a/wxdata/include/scwx/provider/nexrad_data_provider.hpp b/wxdata/include/scwx/provider/nexrad_data_provider.hpp index 14a75815..2a7320d2 100644 --- a/wxdata/include/scwx/provider/nexrad_data_provider.hpp +++ b/wxdata/include/scwx/provider/nexrad_data_provider.hpp @@ -59,6 +59,13 @@ public: */ virtual std::string FindLatestKey() = 0; + /** + * Finds the most recent time in the cache. + * + * @return NEXRAD data key + */ + virtual std::chrono::system_clock::time_point FindLatestTime() = 0; + /** * Lists NEXRAD objects for the date supplied, and adds them to the cache. * @@ -81,6 +88,16 @@ public: virtual std::shared_ptr LoadObjectByKey(const std::string& key) = 0; + /** + * Loads a NEXRAD file object at the given time + * + * @param time NEXRAD time + * + * @return NEXRAD data + */ + virtual std::shared_ptr + LoadObjectByTime(std::chrono::system_clock::time_point time) = 0; + /** * Lists NEXRAD objects for the current date, and adds them to the cache. If * no objects have been added to the cache for the current date, the previous diff --git a/wxdata/include/scwx/provider/nexrad_data_provider_factory.hpp b/wxdata/include/scwx/provider/nexrad_data_provider_factory.hpp index bdd51b9e..510ee14c 100644 --- a/wxdata/include/scwx/provider/nexrad_data_provider_factory.hpp +++ b/wxdata/include/scwx/provider/nexrad_data_provider_factory.hpp @@ -27,6 +27,9 @@ public: static std::shared_ptr CreateLevel2DataProvider(const std::string& radarSite); + static std::shared_ptr + CreateLevel2ChunksDataProvider(const std::string& radarSite); + static std::shared_ptr CreateLevel3DataProvider(const std::string& radarSite, const std::string& product); diff --git a/wxdata/include/scwx/provider/warnings_provider.hpp b/wxdata/include/scwx/provider/warnings_provider.hpp index e519ec5d..0d14d258 100644 --- a/wxdata/include/scwx/provider/warnings_provider.hpp +++ b/wxdata/include/scwx/provider/warnings_provider.hpp @@ -2,9 +2,7 @@ #include -namespace scwx -{ -namespace provider +namespace scwx::provider { /** @@ -22,15 +20,12 @@ public: WarningsProvider(WarningsProvider&&) noexcept; WarningsProvider& operator=(WarningsProvider&&) noexcept; - std::pair - ListFiles(std::chrono::system_clock::time_point newerThan = {}); std::vector> - LoadUpdatedFiles(std::chrono::system_clock::time_point newerThan = {}); + LoadUpdatedFiles(std::chrono::sys_time newerThan = {}); private: class Impl; std::unique_ptr p; }; -} // namespace provider -} // namespace scwx +} // namespace scwx::provider diff --git a/wxdata/include/scwx/types/iem_types.hpp b/wxdata/include/scwx/types/iem_types.hpp new file mode 100644 index 00000000..ee461c36 --- /dev/null +++ b/wxdata/include/scwx/types/iem_types.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include +#include + +#include + +namespace scwx::types::iem +{ + +/** + * @brief AFOS Entry object + * + * + */ +struct AfosEntry +{ + std::int64_t index_ {}; + std::string entered_ {}; + std::string pil_ {}; + std::string productId_ {}; + std::string cccc_ {}; + std::int64_t count_ {}; + std::string link_ {}; + std::string textLink_ {}; +}; + +/** + * @brief AFOS List object + * + * + */ +struct AfosList +{ + std::vector data_ {}; +}; + +/** + * @brief Bad Request (400) object + */ +struct BadRequest +{ + std::string detail_ {}; +}; + +/** + * @brief Validation Error (422) object + */ +struct ValidationError +{ + struct Detail + { + std::string type_ {}; + std::vector> loc_ {}; + std::string msg_ {}; + std::string input_ {}; + struct Context + { + std::string error_ {}; + } ctx_; + }; + + std::vector detail_ {}; +}; + +AfosList tag_invoke(boost::json::value_to_tag, + const boost::json::value& jv); +BadRequest tag_invoke(boost::json::value_to_tag, + const boost::json::value& jv); +ValidationError tag_invoke(boost::json::value_to_tag, + const boost::json::value& jv); + +} // namespace scwx::types::iem diff --git a/wxdata/include/scwx/types/ntp_types.hpp b/wxdata/include/scwx/types/ntp_types.hpp new file mode 100644 index 00000000..db1aa61f --- /dev/null +++ b/wxdata/include/scwx/types/ntp_types.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +namespace scwx::types::ntp +{ + +/* Adapted from: + * https://github.com/lettier/ntpclient/blob/master/source/c/main.c + * + * Copyright (c) 2014 David Lettier + * Copyright (c) 2020 Krystian Stasiowski + * Distributed under the BSD 3-Clause License (See + * https://github.com/lettier/ntpclient/blob/master/LICENSE) + */ + +#pragma pack(push, 1) + +struct NtpPacket +{ + struct LiVnMode + { + std::uint8_t mode : 3; // Client will pick mode 3 for client. + std::uint8_t vn : 3; // Version number of the protocol. + std::uint8_t li : 2; // Leap indicator. + }; + + union + { + std::uint8_t li_vn_mode; + LiVnMode fields; + }; + + std::uint8_t stratum; // Stratum level of the local clock. + std::uint8_t poll; // Maximum interval between successive messages. + std::uint8_t precision; // Precision of the local clock. + + std::uint32_t rootDelay; // Total round trip delay time. + std::uint32_t rootDispersion; // Max error aloud from primary clock source. + std::uint32_t refId; // Reference clock identifier. + + std::uint32_t refTm_s; // Reference time-stamp seconds. + std::uint32_t refTm_f; // Reference time-stamp fraction of a second. + + std::uint32_t origTm_s; // Originate time-stamp seconds. + std::uint32_t origTm_f; // Originate time-stamp fraction of a second. + + std::uint32_t rxTm_s; // Received time-stamp seconds. + std::uint32_t rxTm_f; // Received time-stamp fraction of a second. + + std::uint32_t txTm_s; // Transmit time-stamp seconds. + std::uint32_t txTm_f; // Transmit time-stamp fraction of a second. + + static NtpPacket Parse(const std::span data); +}; +// Total: 48 bytes. + +#pragma pack(pop) + +} // namespace scwx::types::ntp diff --git a/wxdata/include/scwx/util/json.hpp b/wxdata/include/scwx/util/json.hpp new file mode 100644 index 00000000..ab836edc --- /dev/null +++ b/wxdata/include/scwx/util/json.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace scwx::util::json +{ + +boost::json::value ReadJsonFile(const std::string& path); +boost::json::value ReadJsonStream(std::istream& is); +boost::json::value ReadJsonString(std::string_view sv); +void WriteJsonFile(const std::string& path, + const boost::json::value& json, + bool prettyPrint = true); + +} // namespace scwx::util::json diff --git a/wxdata/include/scwx/util/time.hpp b/wxdata/include/scwx/util/time.hpp index 62242589..fe052756 100644 --- a/wxdata/include/scwx/util/time.hpp +++ b/wxdata/include/scwx/util/time.hpp @@ -6,16 +6,14 @@ #include #include -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) # include #endif -namespace scwx -{ -namespace util +namespace scwx::util::time { -#if defined(_MSC_VER) +#if (__cpp_lib_chrono >= 201907L) typedef std::chrono::time_zone time_zone; #else typedef date::time_zone time_zone; @@ -34,6 +32,9 @@ typedef scwx::util:: ClockFormat GetClockFormat(const std::string& name); const std::string& GetClockFormatName(ClockFormat clockFormat); +template +std::chrono::time_point now(); + std::chrono::system_clock::time_point TimePoint(uint32_t modifiedJulianDate, uint32_t milliseconds); @@ -46,5 +47,17 @@ template std::optional> TryParseDateTime(const std::string& dateTimeFormat, const std::string& str); -} // namespace util -} // namespace scwx +} // namespace scwx::util::time + +namespace scwx::util +{ +// Add types and functions to scwx::util for compatibility +using time::ClockFormat; +using time::ClockFormatIterator; +using time::GetClockFormat; +using time::GetClockFormatName; +using time::time_zone; +using time::TimePoint; +using time::TimeString; +using time::TryParseDateTime; +} // namespace scwx::util diff --git a/wxdata/include/scwx/wsr88d/ar2v_file.hpp b/wxdata/include/scwx/wsr88d/ar2v_file.hpp index 1f3ab0cc..64319283 100644 --- a/wxdata/include/scwx/wsr88d/ar2v_file.hpp +++ b/wxdata/include/scwx/wsr88d/ar2v_file.hpp @@ -32,6 +32,9 @@ public: Ar2vFile(Ar2vFile&&) noexcept; Ar2vFile& operator=(Ar2vFile&&) noexcept; + Ar2vFile(const std::shared_ptr& current, + const std::shared_ptr& last); + std::uint32_t julian_date() const; std::uint32_t milliseconds() const; std::string icao() const; @@ -53,6 +56,9 @@ public: bool LoadFile(const std::string& filename); bool LoadData(std::istream& is); + bool LoadLDMRecords(std::istream& is); + bool IndexFile(); + private: std::unique_ptr p; }; diff --git a/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp index ba911898..0441f17a 100644 --- a/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp +++ b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp @@ -2,11 +2,7 @@ #include -namespace scwx -{ -namespace wsr88d -{ -namespace rda +namespace scwx::wsr88d::rda { class DigitalRadarDataGeneric : public GenericRadarData @@ -27,30 +23,31 @@ public: DigitalRadarDataGeneric(DigitalRadarDataGeneric&&) noexcept; DigitalRadarDataGeneric& operator=(DigitalRadarDataGeneric&&) noexcept; - std::string radar_identifier() const; - std::uint32_t collection_time() const; - std::uint16_t modified_julian_date() const; - std::uint16_t azimuth_number() const; - units::degrees azimuth_angle() const; - std::uint8_t compression_indicator() const; - std::uint16_t radial_length() const; - std::uint8_t azimuth_resolution_spacing() const; - std::uint8_t radial_status() const; - std::uint16_t elevation_number() const; - std::uint8_t cut_sector_number() const; - units::degrees elevation_angle() const; - std::uint8_t radial_spot_blanking_status() const; - std::uint8_t azimuth_indexing_mode() const; - std::uint16_t data_block_count() const; - std::uint16_t volume_coverage_pattern_number() const; + [[nodiscard]] std::string radar_identifier() const; + [[nodiscard]] std::uint32_t collection_time() const override; + [[nodiscard]] std::uint16_t modified_julian_date() const override; + [[nodiscard]] std::uint16_t azimuth_number() const override; + [[nodiscard]] units::degrees azimuth_angle() const override; + [[nodiscard]] std::uint8_t compression_indicator() const; + [[nodiscard]] std::uint16_t radial_length() const; + [[nodiscard]] std::uint8_t azimuth_resolution_spacing() const; + [[nodiscard]] std::uint8_t radial_status() const; + [[nodiscard]] std::uint16_t elevation_number() const override; + [[nodiscard]] std::uint8_t cut_sector_number() const; + [[nodiscard]] units::degrees elevation_angle() const; + [[nodiscard]] std::uint8_t radial_spot_blanking_status() const; + [[nodiscard]] std::uint8_t azimuth_indexing_mode() const; + [[nodiscard]] std::uint16_t data_block_count() const; + [[nodiscard]] std::uint16_t volume_coverage_pattern_number() const override; - std::shared_ptr elevation_data_block() const; - std::shared_ptr radial_data_block() const; - std::shared_ptr volume_data_block() const; - std::shared_ptr - moment_data_block(DataBlockType type) const; + [[nodiscard]] std::shared_ptr + elevation_data_block() const; + [[nodiscard]] std::shared_ptr radial_data_block() const; + [[nodiscard]] std::shared_ptr volume_data_block() const; + [[nodiscard]] std::shared_ptr + moment_data_block(DataBlockType type) const override; - bool Parse(std::istream& is); + bool Parse(std::istream& is) override; static std::shared_ptr Create(Level2MessageHeader&& header, std::istream& is); @@ -65,11 +62,14 @@ class DigitalRadarDataGeneric::DataBlock protected: explicit DataBlock(const std::string& dataBlockType, const std::string& dataName); + +public: virtual ~DataBlock(); DataBlock(const DataBlock&) = delete; DataBlock& operator=(const DataBlock&) = delete; +protected: DataBlock(DataBlock&&) noexcept; DataBlock& operator=(DataBlock&&) noexcept; @@ -118,17 +118,19 @@ public: MomentDataBlock(MomentDataBlock&&) noexcept; MomentDataBlock& operator=(MomentDataBlock&&) noexcept; - std::uint16_t number_of_data_moment_gates() const; - units::kilometers data_moment_range() const; - std::int16_t data_moment_range_raw() const; - units::kilometers data_moment_range_sample_interval() const; - std::uint16_t data_moment_range_sample_interval_raw() const; - float snr_threshold() const; - std::int16_t snr_threshold_raw() const; - std::uint8_t data_word_size() const; - float scale() const; - float offset() const; - const void* data_moments() const; + [[nodiscard]] std::uint16_t number_of_data_moment_gates() const override; + [[nodiscard]] units::kilometers data_moment_range() const override; + [[nodiscard]] std::int16_t data_moment_range_raw() const override; + [[nodiscard]] units::kilometers + data_moment_range_sample_interval() const override; + [[nodiscard]] std::uint16_t + data_moment_range_sample_interval_raw() const override; + [[nodiscard]] float snr_threshold() const; + [[nodiscard]] std::int16_t snr_threshold_raw() const override; + [[nodiscard]] std::uint8_t data_word_size() const override; + [[nodiscard]] float scale() const override; + [[nodiscard]] float offset() const override; + [[nodiscard]] const void* data_moments() const override; static std::shared_ptr Create(const std::string& dataBlockType, @@ -155,7 +157,7 @@ public: RadialDataBlock(RadialDataBlock&&) noexcept; RadialDataBlock& operator=(RadialDataBlock&&) noexcept; - float unambiguous_range() const; + [[nodiscard]] float unambiguous_range() const; static std::shared_ptr Create(const std::string& dataBlockType, @@ -182,9 +184,9 @@ public: VolumeDataBlock(VolumeDataBlock&&) noexcept; VolumeDataBlock& operator=(VolumeDataBlock&&) noexcept; - float latitude() const; - float longitude() const; - std::uint16_t volume_coverage_pattern_number() const; + [[nodiscard]] float latitude() const; + [[nodiscard]] float longitude() const; + [[nodiscard]] std::uint16_t volume_coverage_pattern_number() const; static std::shared_ptr Create(const std::string& dataBlockType, @@ -198,6 +200,4 @@ private: bool Parse(std::istream& is); }; -} // namespace rda -} // namespace wsr88d -} // namespace scwx +} // namespace scwx::wsr88d::rda diff --git a/wxdata/include/scwx/wsr88d/rda/level2_message_factory.hpp b/wxdata/include/scwx/wsr88d/rda/level2_message_factory.hpp index 7359e72b..ac2b8234 100644 --- a/wxdata/include/scwx/wsr88d/rda/level2_message_factory.hpp +++ b/wxdata/include/scwx/wsr88d/rda/level2_message_factory.hpp @@ -2,28 +2,19 @@ #include -namespace scwx -{ -namespace wsr88d -{ -namespace rda +namespace scwx::wsr88d::rda { struct Level2MessageInfo { - std::shared_ptr message; - bool headerValid; - bool messageValid; - - Level2MessageInfo() : - message(nullptr), headerValid(false), messageValid(false) - { - } + std::shared_ptr message {nullptr}; + bool headerValid {false}; + bool messageValid {false}; }; class Level2MessageFactory { -private: +public: explicit Level2MessageFactory() = delete; ~Level2MessageFactory() = delete; @@ -33,7 +24,6 @@ private: Level2MessageFactory(Level2MessageFactory&&) noexcept = delete; Level2MessageFactory& operator=(Level2MessageFactory&&) noexcept = delete; -public: struct Context; static std::shared_ptr CreateContext(); @@ -41,6 +31,4 @@ public: std::shared_ptr& ctx); }; -} // namespace rda -} // namespace wsr88d -} // namespace scwx +} // namespace scwx::wsr88d::rda diff --git a/wxdata/include/scwx/wsr88d/rda/performance_maintenance_data.hpp b/wxdata/include/scwx/wsr88d/rda/performance_maintenance_data.hpp index 8e1c91ea..c1bf3869 100644 --- a/wxdata/include/scwx/wsr88d/rda/performance_maintenance_data.hpp +++ b/wxdata/include/scwx/wsr88d/rda/performance_maintenance_data.hpp @@ -2,14 +2,8 @@ #include -namespace scwx +namespace scwx::wsr88d::rda { -namespace wsr88d -{ -namespace rda -{ - -class PerformanceMaintenanceDataImpl; class PerformanceMaintenanceData : public Level2Message { @@ -24,267 +18,261 @@ public: PerformanceMaintenanceData(PerformanceMaintenanceData&&) noexcept; PerformanceMaintenanceData& operator=(PerformanceMaintenanceData&&) noexcept; - uint16_t loop_back_set_status() const; - uint32_t t1_output_frames() const; - uint32_t t1_input_frames() const; - uint32_t router_memory_used() const; - uint32_t router_memory_free() const; - uint16_t router_memory_utilization() const; - uint16_t route_to_rpg() const; - uint32_t csu_loss_of_signal() const; - uint32_t csu_loss_of_frames() const; - uint32_t csu_yellow_alarms() const; - uint32_t csu_blue_alarms() const; - uint32_t csu_24hr_errored_seconds() const; - uint32_t csu_24hr_severely_errored_seconds() const; - uint32_t csu_24hr_severely_errored_framing_seconds() const; - uint32_t csu_24hr_unavailable_seconds() const; - uint32_t csu_24hr_controlled_slip_seconds() const; - uint32_t csu_24hr_path_coding_violations() const; - uint32_t csu_24hr_line_errored_seconds() const; - uint32_t csu_24hr_bursty_errored_seconds() const; - uint32_t csu_24hr_degraded_minutes() const; - uint32_t lan_switch_cpu_utilization() const; - uint16_t lan_switch_memory_utilization() const; - uint16_t ifdr_chasis_temperature() const; - uint16_t ifdr_fpga_temperature() const; - int32_t gps_satellites() const; - uint16_t ipc_status() const; - uint16_t commanded_channel_control() const; - uint16_t polarization() const; - float ame_internal_temperature() const; - float ame_receiver_module_temperature() const; - float ame_bite_cal_module_temperature() const; - uint16_t ame_peltier_pulse_width_modulation() const; - uint16_t ame_peltier_status() const; - uint16_t ame_a_d_converter_status() const; - uint16_t ame_state() const; - float ame_3_3v_ps_voltage() const; - float ame_5v_ps_voltage() const; - float ame_6_5v_ps_voltage() const; - float ame_15v_ps_voltage() const; - float ame_48v_ps_voltage() const; - float ame_stalo_power() const; - float peltier_current() const; - float adc_calibration_reference_voltage() const; - uint16_t ame_mode() const; - uint16_t ame_peltier_mode() const; - float ame_peltier_inside_fan_current() const; - float ame_peltier_outside_fan_current() const; - float horizontal_tr_limiter_voltage() const; - float vertical_tr_limiter_voltage() const; - float adc_calibration_offset_voltage() const; - float adc_calibration_gain_correction() const; - uint16_t rcp_status() const; - std::string rcp_string() const; - uint16_t spip_power_buttons() const; - float master_power_administrator_load() const; - float expansion_power_administrator_load() const; - uint16_t _5vdc_ps() const; - uint16_t _15vdc_ps() const; - uint16_t _28vdc_ps() const; - uint16_t neg_15vdc_ps() const; - uint16_t _45vdc_ps() const; - uint16_t filament_ps_voltage() const; - uint16_t vacuum_pump_ps_voltage() const; - uint16_t focus_coil_ps_voltage() const; - uint16_t filament_ps() const; - uint16_t klystron_warmup() const; - uint16_t transmitter_available() const; - uint16_t wg_switch_position() const; - uint16_t wg_pfn_transfer_interlock() const; - uint16_t maintenance_mode() const; - uint16_t maintenance_required() const; - uint16_t pfn_switch_position() const; - uint16_t modulator_overload() const; - uint16_t modulator_inv_current() const; - uint16_t modulator_switch_fail() const; - uint16_t main_power_voltage() const; - uint16_t charging_system_fail() const; - uint16_t inverse_diode_current() const; - uint16_t trigger_amplifier() const; - uint16_t circulator_temperature() const; - uint16_t spectrum_filter_pressure() const; - uint16_t wg_arc_vswr() const; - uint16_t cabinet_interlock() const; - uint16_t cabinet_air_temperature() const; - uint16_t cabinet_airflow() const; - uint16_t klystron_current() const; - uint16_t klystron_filament_current() const; - uint16_t klystron_vacion_current() const; - uint16_t klystron_air_temperature() const; - uint16_t klystron_airflow() const; - uint16_t modulator_switch_maintenance() const; - uint16_t post_charge_regulator_maintenance() const; - uint16_t wg_pressure_humidity() const; - uint16_t transmitter_overvoltage() const; - uint16_t transmitter_overcurrent() const; - uint16_t focus_coil_current() const; - uint16_t focus_coil_airflow() const; - uint16_t oil_temperature() const; - uint16_t prf_limit() const; - uint16_t transmitter_oil_level() const; - uint16_t transmitter_battery_charging() const; - uint16_t high_voltage_status() const; - uint16_t transmitter_recycling_summary() const; - uint16_t transmitter_inoperable() const; - uint16_t transmitter_air_filter() const; - uint16_t zero_test_bit(unsigned i) const; - uint16_t one_test_bit(unsigned i) const; - uint16_t xmtr_spip_interface() const; - uint16_t transmitter_summary_status() const; - float transmitter_rf_power() const; - float horizontal_xmtr_peak_power() const; - float xmtr_peak_power() const; - float vertical_xmtr_peak_power() const; - float xmtr_rf_avg_power() const; - uint32_t xmtr_recycle_count() const; - float receiver_bias() const; - float transmit_imbalance() const; - float xmtr_power_meter_zero() const; - uint16_t ac_unit1_compressor_shut_off() const; - uint16_t ac_unit2_compressor_shut_off() const; - uint16_t generator_maintenance_required() const; - uint16_t generator_battery_voltage() const; - uint16_t generator_engine() const; - uint16_t generator_volt_frequency() const; - uint16_t power_source() const; - uint16_t transitional_power_source() const; - uint16_t generator_auto_run_off_switch() const; - uint16_t aircraft_hazard_lighting() const; - uint16_t equipment_shelter_fire_detection_system() const; - uint16_t equipment_shelter_fire_smoke() const; - uint16_t generator_shelter_fire_smoke() const; - uint16_t utility_voltage_frequency() const; - uint16_t site_security_alarm() const; - uint16_t security_equipment() const; - uint16_t security_system() const; - uint16_t receiver_connected_to_antenna() const; - uint16_t radome_hatch() const; - uint16_t ac_unit1_filter_dirty() const; - uint16_t ac_unit2_filter_dirty() const; - float equipment_shelter_temperature() const; - float outside_ambient_temperature() const; - float transmitter_leaving_air_temp() const; - float ac_unit1_discharge_air_temp() const; - float generator_shelter_temperature() const; - float radome_air_temperature() const; - float ac_unit2_discharge_air_temp() const; - float spip_15v_ps() const; - float spip_neg_15v_ps() const; - uint16_t spip_28v_ps_status() const; - float spip_5v_ps() const; - uint16_t converted_generator_fuel_level() const; - uint16_t elevation_pos_dead_limit() const; - uint16_t _150v_overvoltage() const; - uint16_t _150v_undervoltage() const; - uint16_t elevation_servo_amp_inhibit() const; - uint16_t elevation_servo_amp_short_circuit() const; - uint16_t elevation_servo_amp_overtemp() const; - uint16_t elevation_motor_overtemp() const; - uint16_t elevation_stow_pin() const; - uint16_t elevation_housing_5v_ps() const; - uint16_t elevation_neg_dead_limit() const; - uint16_t elevation_pos_normal_limit() const; - uint16_t elevation_neg_normal_limit() const; - uint16_t elevation_encoder_light() const; - uint16_t elevation_gearbox_oil() const; - uint16_t elevation_handwheel() const; - uint16_t elevation_amp_ps() const; - uint16_t azimuth_servo_amp_inhibit() const; - uint16_t azimuth_servo_amp_short_circuit() const; - uint16_t azimuth_servo_amp_overtemp() const; - uint16_t azimuth_motor_overtemp() const; - uint16_t azimuth_stow_pin() const; - uint16_t azimuth_housing_5v_ps() const; - uint16_t azimuth_encoder_light() const; - uint16_t azimuth_gearbox_oil() const; - uint16_t azimuth_bull_gear_oil() const; - uint16_t azimuth_handwheel() const; - uint16_t azimuth_servo_amp_ps() const; - uint16_t servo() const; - uint16_t pedestal_interlock_switch() const; - uint16_t coho_clock() const; - uint16_t rf_generator_frequency_select_oscillator() const; - uint16_t rf_generator_rf_stalo() const; - uint16_t rf_generator_phase_shifted_coho() const; - uint16_t _9v_receiver_ps() const; - uint16_t _5v_receiver_ps() const; - uint16_t _18v_receiver_ps() const; - uint16_t neg_9v_receiver_ps() const; - uint16_t _5v_single_channel_rdaiu_ps() const; - float horizontal_short_pulse_noise() const; - float horizontal_long_pulse_noise() const; - float horizontal_noise_temperature() const; - float vertical_short_pulse_noise() const; - float vertical_long_pulse_noise() const; - float vertical_noise_temperature() const; - float horizontal_linearity() const; - float horizontal_dynamic_range() const; - float horizontal_delta_dbz0() const; - float vertical_delta_dbz0() const; - float kd_peak_measured() const; - float short_pulse_horizontal_dbz0() const; - float long_pulse_horizontal_dbz0() const; - uint16_t velocity_processed() const; - uint16_t width_processed() const; - uint16_t velocity_rf_gen() const; - uint16_t width_rf_gen() const; - float horizontal_i0() const; - float vertical_i0() const; - float vertical_dynamic_range() const; - float short_pulse_vertical_dbz0() const; - float long_pulse_vertical_dbz0() const; - float horizontal_power_sense() const; - float vertical_power_sense() const; - float zdr_bias() const; - float clutter_suppression_delta() const; - float clutter_suppression_unfiltered_power() const; - float clutter_suppression_filtered_power() const; - float vertical_linearity() const; - uint16_t state_file_read_status() const; - uint16_t state_file_write_status() const; - uint16_t bypass_map_file_read_status() const; - uint16_t bypass_map_file_write_status() const; - uint16_t current_adaptation_file_read_status() const; - uint16_t current_adaptation_file_write_status() const; - uint16_t censor_zone_file_read_status() const; - uint16_t censor_zone_file_write_status() const; - uint16_t remote_vcp_file_read_status() const; - uint16_t remote_vcp_file_write_status() const; - uint16_t baseline_adaptation_file_read_status() const; - uint16_t read_status_of_prf_sets() const; - uint16_t clutter_filter_map_file_read_status() const; - uint16_t clutter_filter_map_file_write_status() const; - uint16_t generatl_disk_io_error() const; - uint8_t rsp_status() const; - uint8_t motherboard_temperature() const; - uint8_t cpu1_temperature() const; - uint8_t cpu2_temperature() const; - uint16_t cpu1_fan_speed() const; - uint16_t cpu2_fan_speed() const; - uint16_t rsp_fan1_speed() const; - uint16_t rsp_fan2_speed() const; - uint16_t rsp_fan3_speed() const; - uint16_t spip_comm_status() const; - uint16_t hci_comm_status() const; - uint16_t signal_processor_command_status() const; - uint16_t ame_communication_status() const; - uint16_t rms_link_status() const; - uint16_t rpg_link_status() const; - uint16_t interpanel_link_status() const; - uint32_t performance_check_time() const; - uint16_t version() const; + [[nodiscard]] std::uint16_t loop_back_set_status() const; + [[nodiscard]] std::uint32_t t1_output_frames() const; + [[nodiscard]] std::uint32_t t1_input_frames() const; + [[nodiscard]] std::uint32_t router_memory_used() const; + [[nodiscard]] std::uint32_t router_memory_free() const; + [[nodiscard]] std::uint16_t router_memory_utilization() const; + [[nodiscard]] std::uint16_t route_to_rpg() const; + [[nodiscard]] std::uint16_t t1_port_status() const; + [[nodiscard]] std::uint16_t router_dedicated_ethernet_port_status() const; + [[nodiscard]] std::uint16_t router_commercial_ethernet_port_status() const; + [[nodiscard]] std::uint32_t csu_24hr_errored_seconds() const; + [[nodiscard]] std::uint32_t csu_24hr_severely_errored_seconds() const; + [[nodiscard]] std::uint32_t + csu_24hr_severely_errored_framing_seconds() const; + [[nodiscard]] std::uint32_t csu_24hr_unavailable_seconds() const; + [[nodiscard]] std::uint32_t csu_24hr_controlled_slip_seconds() const; + [[nodiscard]] std::uint32_t csu_24hr_path_coding_violations() const; + [[nodiscard]] std::uint32_t csu_24hr_line_errored_seconds() const; + [[nodiscard]] std::uint32_t csu_24hr_bursty_errored_seconds() const; + [[nodiscard]] std::uint32_t csu_24hr_degraded_minutes() const; + [[nodiscard]] std::uint32_t lan_switch_cpu_utilization() const; + [[nodiscard]] std::uint16_t lan_switch_memory_utilization() const; + [[nodiscard]] std::uint16_t ifdr_chasis_temperature() const; + [[nodiscard]] std::uint16_t ifdr_fpga_temperature() const; + [[nodiscard]] std::uint16_t ntp_status() const; + [[nodiscard]] std::uint16_t ipc_status() const; + [[nodiscard]] std::uint16_t commanded_channel_control() const; + [[nodiscard]] std::uint16_t polarization() const; + [[nodiscard]] float ame_internal_temperature() const; + [[nodiscard]] float ame_receiver_module_temperature() const; + [[nodiscard]] float ame_bite_cal_module_temperature() const; + [[nodiscard]] std::uint16_t ame_peltier_pulse_width_modulation() const; + [[nodiscard]] std::uint16_t ame_peltier_status() const; + [[nodiscard]] std::uint16_t ame_a_d_converter_status() const; + [[nodiscard]] std::uint16_t ame_state() const; + [[nodiscard]] float ame_3_3v_ps_voltage() const; + [[nodiscard]] float ame_5v_ps_voltage() const; + [[nodiscard]] float ame_6_5v_ps_voltage() const; + [[nodiscard]] float ame_15v_ps_voltage() const; + [[nodiscard]] float ame_48v_ps_voltage() const; + [[nodiscard]] float ame_stalo_power() const; + [[nodiscard]] float peltier_current() const; + [[nodiscard]] float adc_calibration_reference_voltage() const; + [[nodiscard]] std::uint16_t ame_mode() const; + [[nodiscard]] std::uint16_t ame_peltier_mode() const; + [[nodiscard]] float ame_peltier_inside_fan_current() const; + [[nodiscard]] float ame_peltier_outside_fan_current() const; + [[nodiscard]] float horizontal_tr_limiter_voltage() const; + [[nodiscard]] float vertical_tr_limiter_voltage() const; + [[nodiscard]] float adc_calibration_offset_voltage() const; + [[nodiscard]] float adc_calibration_gain_correction() const; + [[nodiscard]] std::uint16_t rcp_status() const; + [[nodiscard]] std::string rcp_string() const; + [[nodiscard]] std::uint16_t spip_power_buttons() const; + [[nodiscard]] float master_power_administrator_load() const; + [[nodiscard]] float expansion_power_administrator_load() const; + [[nodiscard]] std::uint16_t _5vdc_ps() const; + [[nodiscard]] std::uint16_t _15vdc_ps() const; + [[nodiscard]] std::uint16_t _28vdc_ps() const; + [[nodiscard]] std::uint16_t neg_15vdc_ps() const; + [[nodiscard]] std::uint16_t _45vdc_ps() const; + [[nodiscard]] std::uint16_t filament_ps_voltage() const; + [[nodiscard]] std::uint16_t vacuum_pump_ps_voltage() const; + [[nodiscard]] std::uint16_t focus_coil_ps_voltage() const; + [[nodiscard]] std::uint16_t filament_ps() const; + [[nodiscard]] std::uint16_t klystron_warmup() const; + [[nodiscard]] std::uint16_t transmitter_available() const; + [[nodiscard]] std::uint16_t wg_switch_position() const; + [[nodiscard]] std::uint16_t wg_pfn_transfer_interlock() const; + [[nodiscard]] std::uint16_t maintenance_mode() const; + [[nodiscard]] std::uint16_t maintenance_required() const; + [[nodiscard]] std::uint16_t pfn_switch_position() const; + [[nodiscard]] std::uint16_t modulator_overload() const; + [[nodiscard]] std::uint16_t modulator_inv_current() const; + [[nodiscard]] std::uint16_t modulator_switch_fail() const; + [[nodiscard]] std::uint16_t main_power_voltage() const; + [[nodiscard]] std::uint16_t charging_system_fail() const; + [[nodiscard]] std::uint16_t inverse_diode_current() const; + [[nodiscard]] std::uint16_t trigger_amplifier() const; + [[nodiscard]] std::uint16_t circulator_temperature() const; + [[nodiscard]] std::uint16_t spectrum_filter_pressure() const; + [[nodiscard]] std::uint16_t wg_arc_vswr() const; + [[nodiscard]] std::uint16_t cabinet_interlock() const; + [[nodiscard]] std::uint16_t cabinet_air_temperature() const; + [[nodiscard]] std::uint16_t cabinet_airflow() const; + [[nodiscard]] std::uint16_t klystron_current() const; + [[nodiscard]] std::uint16_t klystron_filament_current() const; + [[nodiscard]] std::uint16_t klystron_vacion_current() const; + [[nodiscard]] std::uint16_t klystron_air_temperature() const; + [[nodiscard]] std::uint16_t klystron_airflow() const; + [[nodiscard]] std::uint16_t modulator_switch_maintenance() const; + [[nodiscard]] std::uint16_t post_charge_regulator_maintenance() const; + [[nodiscard]] std::uint16_t wg_pressure_humidity() const; + [[nodiscard]] std::uint16_t transmitter_overvoltage() const; + [[nodiscard]] std::uint16_t transmitter_overcurrent() const; + [[nodiscard]] std::uint16_t focus_coil_current() const; + [[nodiscard]] std::uint16_t focus_coil_airflow() const; + [[nodiscard]] std::uint16_t oil_temperature() const; + [[nodiscard]] std::uint16_t prf_limit() const; + [[nodiscard]] std::uint16_t transmitter_oil_level() const; + [[nodiscard]] std::uint16_t transmitter_battery_charging() const; + [[nodiscard]] std::uint16_t high_voltage_status() const; + [[nodiscard]] std::uint16_t transmitter_recycling_summary() const; + [[nodiscard]] std::uint16_t transmitter_inoperable() const; + [[nodiscard]] std::uint16_t transmitter_air_filter() const; + [[nodiscard]] std::uint16_t zero_test_bit(unsigned i) const; + [[nodiscard]] std::uint16_t one_test_bit(unsigned i) const; + [[nodiscard]] std::uint16_t xmtr_spip_interface() const; + [[nodiscard]] std::uint16_t transmitter_summary_status() const; + [[nodiscard]] float transmitter_rf_power() const; + [[nodiscard]] float horizontal_xmtr_peak_power() const; + [[nodiscard]] float xmtr_peak_power() const; + [[nodiscard]] float vertical_xmtr_peak_power() const; + [[nodiscard]] float xmtr_rf_avg_power() const; + [[nodiscard]] std::uint32_t xmtr_recycle_count() const; + [[nodiscard]] float receiver_bias() const; + [[nodiscard]] float transmit_imbalance() const; + [[nodiscard]] float xmtr_power_meter_zero() const; + [[nodiscard]] std::uint16_t ac_unit1_compressor_shut_off() const; + [[nodiscard]] std::uint16_t ac_unit2_compressor_shut_off() const; + [[nodiscard]] std::uint16_t generator_maintenance_required() const; + [[nodiscard]] std::uint16_t generator_battery_voltage() const; + [[nodiscard]] std::uint16_t generator_engine() const; + [[nodiscard]] std::uint16_t generator_volt_frequency() const; + [[nodiscard]] std::uint16_t power_source() const; + [[nodiscard]] std::uint16_t transitional_power_source() const; + [[nodiscard]] std::uint16_t generator_auto_run_off_switch() const; + [[nodiscard]] std::uint16_t aircraft_hazard_lighting() const; + [[nodiscard]] std::uint16_t equipment_shelter_fire_detection_system() const; + [[nodiscard]] std::uint16_t equipment_shelter_fire_smoke() const; + [[nodiscard]] std::uint16_t generator_shelter_fire_smoke() const; + [[nodiscard]] std::uint16_t utility_voltage_frequency() const; + [[nodiscard]] std::uint16_t site_security_alarm() const; + [[nodiscard]] std::uint16_t security_equipment() const; + [[nodiscard]] std::uint16_t security_system() const; + [[nodiscard]] std::uint16_t receiver_connected_to_antenna() const; + [[nodiscard]] std::uint16_t radome_hatch() const; + [[nodiscard]] std::uint16_t ac_unit1_filter_dirty() const; + [[nodiscard]] std::uint16_t ac_unit2_filter_dirty() const; + [[nodiscard]] float equipment_shelter_temperature() const; + [[nodiscard]] float outside_ambient_temperature() const; + [[nodiscard]] float transmitter_leaving_air_temp() const; + [[nodiscard]] float ac_unit1_discharge_air_temp() const; + [[nodiscard]] float generator_shelter_temperature() const; + [[nodiscard]] float radome_air_temperature() const; + [[nodiscard]] float ac_unit2_discharge_air_temp() const; + [[nodiscard]] float spip_15v_ps() const; + [[nodiscard]] float spip_neg_15v_ps() const; + [[nodiscard]] std::uint16_t spip_28v_ps_status() const; + [[nodiscard]] float spip_5v_ps() const; + [[nodiscard]] std::uint16_t converted_generator_fuel_level() const; + [[nodiscard]] std::uint16_t elevation_pos_dead_limit() const; + [[nodiscard]] std::uint16_t _150v_overvoltage() const; + [[nodiscard]] std::uint16_t _150v_undervoltage() const; + [[nodiscard]] std::uint16_t elevation_servo_amp_inhibit() const; + [[nodiscard]] std::uint16_t elevation_servo_amp_short_circuit() const; + [[nodiscard]] std::uint16_t elevation_servo_amp_overtemp() const; + [[nodiscard]] std::uint16_t elevation_motor_overtemp() const; + [[nodiscard]] std::uint16_t elevation_stow_pin() const; + [[nodiscard]] std::uint16_t elevation_housing_5v_ps() const; + [[nodiscard]] std::uint16_t elevation_neg_dead_limit() const; + [[nodiscard]] std::uint16_t elevation_pos_normal_limit() const; + [[nodiscard]] std::uint16_t elevation_neg_normal_limit() const; + [[nodiscard]] std::uint16_t elevation_encoder_light() const; + [[nodiscard]] std::uint16_t elevation_gearbox_oil() const; + [[nodiscard]] std::uint16_t elevation_handwheel() const; + [[nodiscard]] std::uint16_t elevation_amp_ps() const; + [[nodiscard]] std::uint16_t azimuth_servo_amp_inhibit() const; + [[nodiscard]] std::uint16_t azimuth_servo_amp_short_circuit() const; + [[nodiscard]] std::uint16_t azimuth_servo_amp_overtemp() const; + [[nodiscard]] std::uint16_t azimuth_motor_overtemp() const; + [[nodiscard]] std::uint16_t azimuth_stow_pin() const; + [[nodiscard]] std::uint16_t azimuth_housing_5v_ps() const; + [[nodiscard]] std::uint16_t azimuth_encoder_light() const; + [[nodiscard]] std::uint16_t azimuth_gearbox_oil() const; + [[nodiscard]] std::uint16_t azimuth_bull_gear_oil() const; + [[nodiscard]] std::uint16_t azimuth_handwheel() const; + [[nodiscard]] std::uint16_t azimuth_servo_amp_ps() const; + [[nodiscard]] std::uint16_t servo() const; + [[nodiscard]] std::uint16_t pedestal_interlock_switch() const; + [[nodiscard]] std::uint16_t coho_clock() const; + [[nodiscard]] std::uint16_t rf_generator_frequency_select_oscillator() const; + [[nodiscard]] std::uint16_t rf_generator_rf_stalo() const; + [[nodiscard]] std::uint16_t rf_generator_phase_shifted_coho() const; + [[nodiscard]] std::uint16_t _9v_receiver_ps() const; + [[nodiscard]] std::uint16_t _5v_receiver_ps() const; + [[nodiscard]] std::uint16_t _18v_receiver_ps() const; + [[nodiscard]] std::uint16_t neg_9v_receiver_ps() const; + [[nodiscard]] std::uint16_t _5v_single_channel_rdaiu_ps() const; + [[nodiscard]] float horizontal_short_pulse_noise() const; + [[nodiscard]] float horizontal_long_pulse_noise() const; + [[nodiscard]] float horizontal_noise_temperature() const; + [[nodiscard]] float vertical_short_pulse_noise() const; + [[nodiscard]] float vertical_long_pulse_noise() const; + [[nodiscard]] float vertical_noise_temperature() const; + [[nodiscard]] float horizontal_linearity() const; + [[nodiscard]] float horizontal_dynamic_range() const; + [[nodiscard]] float horizontal_delta_dbz0() const; + [[nodiscard]] float vertical_delta_dbz0() const; + [[nodiscard]] float kd_peak_measured() const; + [[nodiscard]] float short_pulse_horizontal_dbz0() const; + [[nodiscard]] float long_pulse_horizontal_dbz0() const; + [[nodiscard]] std::uint16_t velocity_processed() const; + [[nodiscard]] std::uint16_t width_processed() const; + [[nodiscard]] std::uint16_t velocity_rf_gen() const; + [[nodiscard]] std::uint16_t width_rf_gen() const; + [[nodiscard]] float horizontal_i0() const; + [[nodiscard]] float vertical_i0() const; + [[nodiscard]] float vertical_dynamic_range() const; + [[nodiscard]] float short_pulse_vertical_dbz0() const; + [[nodiscard]] float long_pulse_vertical_dbz0() const; + [[nodiscard]] float horizontal_power_sense() const; + [[nodiscard]] float vertical_power_sense() const; + [[nodiscard]] float zdr_offset() const; + [[nodiscard]] float clutter_suppression_delta() const; + [[nodiscard]] float clutter_suppression_unfiltered_power() const; + [[nodiscard]] float clutter_suppression_filtered_power() const; + [[nodiscard]] float vertical_linearity() const; + [[nodiscard]] std::uint16_t state_file_read_status() const; + [[nodiscard]] std::uint16_t state_file_write_status() const; + [[nodiscard]] std::uint16_t bypass_map_file_read_status() const; + [[nodiscard]] std::uint16_t bypass_map_file_write_status() const; + [[nodiscard]] std::uint16_t current_adaptation_file_read_status() const; + [[nodiscard]] std::uint16_t current_adaptation_file_write_status() const; + [[nodiscard]] std::uint16_t censor_zone_file_read_status() const; + [[nodiscard]] std::uint16_t censor_zone_file_write_status() const; + [[nodiscard]] std::uint16_t remote_vcp_file_read_status() const; + [[nodiscard]] std::uint16_t remote_vcp_file_write_status() const; + [[nodiscard]] std::uint16_t baseline_adaptation_file_read_status() const; + [[nodiscard]] std::uint16_t read_status_of_prf_sets() const; + [[nodiscard]] std::uint16_t clutter_filter_map_file_read_status() const; + [[nodiscard]] std::uint16_t clutter_filter_map_file_write_status() const; + [[nodiscard]] std::uint16_t general_disk_io_error() const; + [[nodiscard]] std::uint8_t rsp_status() const; + [[nodiscard]] std::uint8_t cpu1_temperature() const; + [[nodiscard]] std::uint8_t cpu2_temperature() const; + [[nodiscard]] std::uint16_t rsp_motherboard_power() const; + [[nodiscard]] std::uint16_t spip_comm_status() const; + [[nodiscard]] std::uint16_t hci_comm_status() const; + [[nodiscard]] std::uint16_t signal_processor_command_status() const; + [[nodiscard]] std::uint16_t ame_communication_status() const; + [[nodiscard]] std::uint16_t rms_link_status() const; + [[nodiscard]] std::uint16_t rpg_link_status() const; + [[nodiscard]] std::uint16_t interpanel_link_status() const; + [[nodiscard]] std::uint32_t performance_check_time() const; + [[nodiscard]] std::uint16_t version() const; - bool Parse(std::istream& is); + bool Parse(std::istream& is) override; static std::shared_ptr Create(Level2MessageHeader&& header, std::istream& is); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; -} // namespace rda -} // namespace wsr88d -} // namespace scwx +} // namespace scwx::wsr88d::rda diff --git a/wxdata/include/scwx/wsr88d/rda/rda_adaptation_data.hpp b/wxdata/include/scwx/wsr88d/rda/rda_adaptation_data.hpp index 38d519a1..60520e83 100644 --- a/wxdata/include/scwx/wsr88d/rda/rda_adaptation_data.hpp +++ b/wxdata/include/scwx/wsr88d/rda/rda_adaptation_data.hpp @@ -2,14 +2,8 @@ #include -namespace scwx +namespace scwx::wsr88d::rda { -namespace wsr88d -{ -namespace rda -{ - -class RdaAdaptationDataImpl; class RdaAdaptationData : public Level2Message { @@ -17,199 +11,205 @@ public: explicit RdaAdaptationData(); ~RdaAdaptationData(); - RdaAdaptationData(const RdaAdaptationData&) = delete; + RdaAdaptationData(const RdaAdaptationData&) = delete; RdaAdaptationData& operator=(const RdaAdaptationData&) = delete; RdaAdaptationData(RdaAdaptationData&&) noexcept; RdaAdaptationData& operator=(RdaAdaptationData&&) noexcept; - std::string adap_file_name() const; - std::string adap_format() const; - std::string adap_revision() const; - std::string adap_date() const; - std::string adap_time() const; - float lower_pre_limit() const; - float az_lat() const; - float upper_pre_limit() const; - float el_lat() const; - float parkaz() const; - float parkel() const; - float a_fuel_conv(unsigned i) const; - float a_min_shelter_temp() const; - float a_max_shelter_temp() const; - float a_min_shelter_ac_temp_diff() const; - float a_max_xmtr_air_temp() const; - float a_max_rad_temp() const; - float a_max_rad_temp_rise() const; - float lower_dead_limit() const; - float upper_dead_limit() const; - float a_min_gen_room_temp() const; - float a_max_gen_room_temp() const; - float spip_5v_reg_lim() const; - float spip_15v_reg_lim() const; - bool rpg_co_located() const; - bool spec_filter_installed() const; - bool tps_installed() const; - bool rms_installed() const; - uint32_t a_hvdl_tst_int() const; - uint32_t a_rpg_lt_int() const; - uint32_t a_min_stab_util_pwr_time() const; - uint32_t a_gen_auto_exer_interval() const; - uint32_t a_util_pwr_sw_req_interval() const; - float a_low_fuel_level() const; - uint32_t config_chan_number() const; - uint32_t redundant_chan_config() const; - float atten_table(unsigned i) const; - float path_losses(unsigned i) const; - float h_coupler_xmt_loss() const; - float h_coupler_cw_loss() const; - float v_coupler_xmt_loss() const; - float ame_ts_bias() const; - float v_coupler_cw_loss() const; - float pwr_sense_bias() const; - float ame_v_noise_enr() const; - float chan_cal_diff() const; - float v_ts_cw() const; - float h_rnscale(unsigned i) const; - float atmos(unsigned i) const; - float el_index(unsigned i) const; - uint32_t tfreq_mhz() const; - float base_data_tcn() const; - float refl_data_tover() const; - float tar_h_dbz0_lp() const; - float tar_v_dbz0_lp() const; - uint32_t init_phi_dp() const; - uint32_t norm_init_phi_dp() const; - float lx_lp() const; - float lx_sp() const; - float meteor_param() const; - float antenna_gain() const; - float vel_degrad_limit() const; - float wth_degrad_limit() const; - float h_noisetemp_dgrad_limit() const; - uint32_t h_min_noisetemp() const; - float v_noisetemp_dgrad_limit() const; - uint32_t v_min_noisetemp() const; - float kly_degrade_limit() const; - float ts_coho() const; - float h_ts_cw() const; - float ts_stalo() const; - float ame_h_noise_enr() const; - float xmtr_peak_pwr_high_limit() const; - float xmtr_peak_pwr_low_limit() const; - float h_dbz0_delta_limit() const; - float threshold1() const; - float threshold2() const; - float clut_supp_dgrad_lim() const; - float range0_value() const; - float xmtr_pwr_mtr_scale() const; - float v_dbz0_delta_limit() const; - float tar_h_dbz0_sp() const; - float tar_v_dbz0_sp() const; - uint32_t deltaprf() const; - uint32_t tau_sp() const; - uint32_t tau_lp() const; - uint32_t nc_dead_value() const; - uint32_t tau_rf_sp() const; - uint32_t tau_rf_lp() const; - float seg1_lim() const; - float slatsec() const; - float slonsec() const; - uint32_t slatdeg() const; - uint32_t slatmin() const; - uint32_t slondeg() const; - uint32_t slonmin() const; - char slatdir() const; - char slondir() const; - float az_correction_factor() const; - float el_correction_factor() const; - std::string site_name() const; - float ant_manual_setup_ielmin() const; - float ant_manual_setup_ielmax() const; - uint32_t ant_manual_setup_fazvelmax() const; - uint32_t ant_manual_setup_felvelmax() const; - int32_t ant_manual_setup_ignd_hgt() const; - uint32_t ant_manual_setup_irad_hgt() const; - float az_pos_sustain_drive() const; - float az_neg_sustain_drive() const; - float az_nom_pos_drive_slope() const; - float az_nom_neg_drive_slope() const; - float az_feedback_slope() const; - float el_pos_sustain_drive() const; - float el_neg_sustain_drive() const; - float el_nom_pos_drive_slope() const; - float el_nom_neg_drive_slope() const; - float el_feedback_slope() const; - float el_first_slope() const; - float el_second_slope() const; - float el_third_slope() const; - float el_droop_pos() const; - float el_off_neutral_drive() const; - float az_intertia() const; - float el_inertia() const; - uint32_t rvp8nv_iwaveguide_length() const; - float v_rnscale(unsigned i) const; - float vel_data_tover() const; - float width_data_tover() const; - float doppler_range_start() const; - uint32_t max_el_index() const; - float seg2_lim() const; - float seg3_lim() const; - float seg4_lim() const; - uint32_t nbr_el_segments() const; - float h_noise_long() const; - float ant_noise_temp() const; - float h_noise_short() const; - float h_noise_tolerance() const; - float min_h_dyn_range() const; - bool gen_installed() const; - bool gen_exercise() const; - float v_noise_tolerance() const; - float min_v_dyn_range() const; - float zdr_bias_dgrad_lim() const; - float baseline_zdr_bias() const; - float v_noise_long() const; - float v_noise_short() const; - float zdr_data_tover() const; - float phi_data_tover() const; - float rho_data_tover() const; - float stalo_power_dgrad_limit() const; - float stalo_power_maint_limit() const; - float min_h_pwr_sense() const; - float min_v_pwr_sense() const; - float h_pwr_sense_offset() const; - float v_pwr_sense_offset() const; - float ps_gain_ref() const; - float rf_pallet_broad_loss() const; - float ame_ps_tolerance() const; - float ame_max_temp() const; - float ame_min_temp() const; - float rcvr_mod_max_temp() const; - float rcvr_mod_min_temp() const; - float bite_mod_max_temp() const; - float bite_mod_min_temp() const; - uint32_t default_polarization() const; - float tr_limit_dgrad_limit() const; - float tr_limit_fail_limit() const; - bool rfp_stepper_enabled() const; - float ame_current_tolerance() const; - uint32_t h_only_polarization() const; - uint32_t v_only_polarization() const; - float sun_bias() const; - float a_min_shelter_temp_warn() const; - float power_meter_zero() const; - float txb_baseline() const; - float txb_alarm_thresh() const; + [[nodiscard]] std::string adap_file_name() const; + [[nodiscard]] std::string adap_format() const; + [[nodiscard]] std::string adap_revision() const; + [[nodiscard]] std::string adap_date() const; + [[nodiscard]] std::string adap_time() const; + [[nodiscard]] float lower_pre_limit() const; + [[nodiscard]] float az_lat() const; + [[nodiscard]] float upper_pre_limit() const; + [[nodiscard]] float el_lat() const; + [[nodiscard]] float parkaz() const; + [[nodiscard]] float parkel() const; + [[nodiscard]] float a_fuel_conv(unsigned i) const; + [[nodiscard]] float a_min_shelter_temp() const; + [[nodiscard]] float a_max_shelter_temp() const; + [[nodiscard]] float a_min_shelter_ac_temp_diff() const; + [[nodiscard]] float a_max_xmtr_air_temp() const; + [[nodiscard]] float a_max_rad_temp() const; + [[nodiscard]] float a_max_rad_temp_rise() const; + [[nodiscard]] float lower_dead_limit() const; + [[nodiscard]] float upper_dead_limit() const; + [[nodiscard]] float a_min_gen_room_temp() const; + [[nodiscard]] float a_max_gen_room_temp() const; + [[nodiscard]] float spip_5v_reg_lim() const; + [[nodiscard]] float spip_15v_reg_lim() const; + [[nodiscard]] bool rpg_co_located() const; + [[nodiscard]] bool spec_filter_installed() const; + [[nodiscard]] bool tps_installed() const; + [[nodiscard]] bool rms_installed() const; + [[nodiscard]] std::uint32_t a_hvdl_tst_int() const; + [[nodiscard]] std::uint32_t a_rpg_lt_int() const; + [[nodiscard]] std::uint32_t a_min_stab_util_pwr_time() const; + [[nodiscard]] std::uint32_t a_gen_auto_exer_interval() const; + [[nodiscard]] std::uint32_t a_util_pwr_sw_req_interval() const; + [[nodiscard]] float a_low_fuel_level() const; + [[nodiscard]] std::uint32_t config_chan_number() const; + [[nodiscard]] std::uint32_t redundant_chan_config() const; + [[nodiscard]] float atten_table(unsigned i) const; + [[nodiscard]] float path_losses(unsigned i) const; + [[nodiscard]] float h_coupler_xmt_loss() const; + [[nodiscard]] float h_coupler_cw_loss() const; + [[nodiscard]] float v_coupler_xmt_loss() const; + [[nodiscard]] float ame_ts_bias() const; + [[nodiscard]] float v_coupler_cw_loss() const; + [[nodiscard]] float pwr_sense_bias() const; + [[nodiscard]] float ame_v_noise_enr() const; + [[nodiscard]] float chan_cal_diff() const; + [[nodiscard]] float v_ts_cw() const; + [[nodiscard]] float h_rnscale(unsigned i) const; + [[nodiscard]] float atmos(unsigned i) const; + [[nodiscard]] float el_index(unsigned i) const; + [[nodiscard]] std::uint32_t tfreq_mhz() const; + [[nodiscard]] float base_data_tcn() const; + [[nodiscard]] float refl_data_tover() const; + [[nodiscard]] float tar_h_dbz0_lp() const; + [[nodiscard]] float tar_v_dbz0_lp() const; + [[nodiscard]] std::uint32_t init_phi_dp() const; + [[nodiscard]] std::uint32_t norm_init_phi_dp() const; + [[nodiscard]] float lx_lp() const; + [[nodiscard]] float lx_sp() const; + [[nodiscard]] float meteor_param() const; + [[nodiscard]] float antenna_gain() const; + [[nodiscard]] float vel_degrad_limit() const; + [[nodiscard]] float wth_degrad_limit() const; + [[nodiscard]] float h_noisetemp_dgrad_limit() const; + [[nodiscard]] std::uint32_t h_min_noisetemp() const; + [[nodiscard]] float v_noisetemp_dgrad_limit() const; + [[nodiscard]] std::uint32_t v_min_noisetemp() const; + [[nodiscard]] float kly_degrade_limit() const; + [[nodiscard]] float ts_coho() const; + [[nodiscard]] float h_ts_cw() const; + [[nodiscard]] float ts_stalo() const; + [[nodiscard]] float ame_h_noise_enr() const; + [[nodiscard]] float xmtr_peak_pwr_high_limit() const; + [[nodiscard]] float xmtr_peak_pwr_low_limit() const; + [[nodiscard]] float h_dbz0_delta_limit() const; + [[nodiscard]] float threshold1() const; + [[nodiscard]] float threshold2() const; + [[nodiscard]] float clut_supp_dgrad_lim() const; + [[nodiscard]] float range0_value() const; + [[nodiscard]] float xmtr_pwr_mtr_scale() const; + [[nodiscard]] float v_dbz0_delta_limit() const; + [[nodiscard]] float tar_h_dbz0_sp() const; + [[nodiscard]] float tar_v_dbz0_sp() const; + [[nodiscard]] std::uint32_t deltaprf() const; + [[nodiscard]] std::uint32_t tau_sp() const; + [[nodiscard]] std::uint32_t tau_lp() const; + [[nodiscard]] std::uint32_t nc_dead_value() const; + [[nodiscard]] std::uint32_t tau_rf_sp() const; + [[nodiscard]] std::uint32_t tau_rf_lp() const; + [[nodiscard]] float seg1_lim() const; + [[nodiscard]] float slatsec() const; + [[nodiscard]] float slonsec() const; + [[nodiscard]] std::uint32_t slatdeg() const; + [[nodiscard]] std::uint32_t slatmin() const; + [[nodiscard]] std::uint32_t slondeg() const; + [[nodiscard]] std::uint32_t slonmin() const; + [[nodiscard]] char slatdir() const; + [[nodiscard]] char slondir() const; + [[nodiscard]] double dig_rcvr_clock_freq() const; + [[nodiscard]] double coho_freq() const; + [[nodiscard]] float az_correction_factor() const; + [[nodiscard]] float el_correction_factor() const; + [[nodiscard]] std::string site_name() const; + [[nodiscard]] float ant_manual_setup_ielmin() const; + [[nodiscard]] float ant_manual_setup_ielmax() const; + [[nodiscard]] std::uint32_t ant_manual_setup_fazvelmax() const; + [[nodiscard]] std::uint32_t ant_manual_setup_felvelmax() const; + [[nodiscard]] std::int32_t ant_manual_setup_ignd_hgt() const; + [[nodiscard]] std::uint32_t ant_manual_setup_irad_hgt() const; + [[nodiscard]] float az_pos_sustain_drive() const; + [[nodiscard]] float az_neg_sustain_drive() const; + [[nodiscard]] float az_nom_pos_drive_slope() const; + [[nodiscard]] float az_nom_neg_drive_slope() const; + [[nodiscard]] float az_feedback_slope() const; + [[nodiscard]] float el_pos_sustain_drive() const; + [[nodiscard]] float el_neg_sustain_drive() const; + [[nodiscard]] float el_nom_pos_drive_slope() const; + [[nodiscard]] float el_nom_neg_drive_slope() const; + [[nodiscard]] float el_feedback_slope() const; + [[nodiscard]] float el_first_slope() const; + [[nodiscard]] float el_second_slope() const; + [[nodiscard]] float el_third_slope() const; + [[nodiscard]] float el_droop_pos() const; + [[nodiscard]] float el_off_neutral_drive() const; + [[nodiscard]] float az_intertia() const; + [[nodiscard]] float el_inertia() const; + [[nodiscard]] float az_stow_angle() const; + [[nodiscard]] float el_stow_angle() const; + [[nodiscard]] float az_encoder_alignment() const; + [[nodiscard]] float el_encoder_alignment() const; + [[nodiscard]] std::string refined_park() const; + [[nodiscard]] std::uint32_t rvp8nv_iwaveguide_length() const; + [[nodiscard]] float v_rnscale(unsigned i) const; + [[nodiscard]] float vel_data_tover() const; + [[nodiscard]] float width_data_tover() const; + [[nodiscard]] float doppler_range_start() const; + [[nodiscard]] std::uint32_t max_el_index() const; + [[nodiscard]] float seg2_lim() const; + [[nodiscard]] float seg3_lim() const; + [[nodiscard]] float seg4_lim() const; + [[nodiscard]] std::uint32_t nbr_el_segments() const; + [[nodiscard]] float h_noise_long() const; + [[nodiscard]] float ant_noise_temp() const; + [[nodiscard]] float h_noise_short() const; + [[nodiscard]] float h_noise_tolerance() const; + [[nodiscard]] float min_h_dyn_range() const; + [[nodiscard]] bool gen_installed() const; + [[nodiscard]] bool gen_exercise() const; + [[nodiscard]] float v_noise_tolerance() const; + [[nodiscard]] float min_v_dyn_range() const; + [[nodiscard]] float zdr_offset_dgrad_lim() const; + [[nodiscard]] float baseline_zdr_offset() const; + [[nodiscard]] float v_noise_long() const; + [[nodiscard]] float v_noise_short() const; + [[nodiscard]] float zdr_data_tover() const; + [[nodiscard]] float phi_data_tover() const; + [[nodiscard]] float rho_data_tover() const; + [[nodiscard]] float stalo_power_dgrad_limit() const; + [[nodiscard]] float stalo_power_maint_limit() const; + [[nodiscard]] float min_h_pwr_sense() const; + [[nodiscard]] float min_v_pwr_sense() const; + [[nodiscard]] float h_pwr_sense_offset() const; + [[nodiscard]] float v_pwr_sense_offset() const; + [[nodiscard]] float ps_gain_ref() const; + [[nodiscard]] float rf_pallet_broad_loss() const; + [[nodiscard]] float ame_ps_tolerance() const; + [[nodiscard]] float ame_max_temp() const; + [[nodiscard]] float ame_min_temp() const; + [[nodiscard]] float rcvr_mod_max_temp() const; + [[nodiscard]] float rcvr_mod_min_temp() const; + [[nodiscard]] float bite_mod_max_temp() const; + [[nodiscard]] float bite_mod_min_temp() const; + [[nodiscard]] std::uint32_t default_polarization() const; + [[nodiscard]] float tr_limit_dgrad_limit() const; + [[nodiscard]] float tr_limit_fail_limit() const; + [[nodiscard]] bool rfp_stepper_enabled() const; + [[nodiscard]] float ame_current_tolerance() const; + [[nodiscard]] std::uint32_t h_only_polarization() const; + [[nodiscard]] std::uint32_t v_only_polarization() const; + [[nodiscard]] float sun_bias() const; + [[nodiscard]] float a_min_shelter_temp_warn() const; + [[nodiscard]] float power_meter_zero() const; + [[nodiscard]] float txb_baseline() const; + [[nodiscard]] float txb_alarm_thresh() const; - bool Parse(std::istream& is); + bool Parse(std::istream& is) override; static std::shared_ptr Create(Level2MessageHeader&& header, std::istream& is); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; -} // namespace rda -} // namespace wsr88d -} // namespace scwx +} // namespace scwx::wsr88d::rda diff --git a/wxdata/include/scwx/wsr88d/rda/rda_prf_data.hpp b/wxdata/include/scwx/wsr88d/rda/rda_prf_data.hpp new file mode 100644 index 00000000..eb42268b --- /dev/null +++ b/wxdata/include/scwx/wsr88d/rda/rda_prf_data.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +namespace scwx::wsr88d::rda +{ + +class RdaPrfData : public Level2Message +{ +public: + explicit RdaPrfData(); + ~RdaPrfData() override; + + RdaPrfData(const RdaPrfData&) = delete; + RdaPrfData& operator=(const RdaPrfData&) = delete; + + RdaPrfData(RdaPrfData&&) noexcept; + RdaPrfData& operator=(RdaPrfData&&) noexcept; + + bool Parse(std::istream& is) override; + + static std::shared_ptr Create(Level2MessageHeader&& header, + std::istream& is); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace scwx::wsr88d::rda diff --git a/wxdata/include/scwx/wsr88d/rda/rda_status_data.hpp b/wxdata/include/scwx/wsr88d/rda/rda_status_data.hpp index 7dd51edf..fcd937ec 100644 --- a/wxdata/include/scwx/wsr88d/rda/rda_status_data.hpp +++ b/wxdata/include/scwx/wsr88d/rda/rda_status_data.hpp @@ -2,14 +2,8 @@ #include -namespace scwx +namespace scwx::wsr88d::rda { -namespace wsr88d -{ -namespace rda -{ - -class RdaStatusDataImpl; class RdaStatusData : public Level2Message { @@ -17,51 +11,51 @@ public: explicit RdaStatusData(); ~RdaStatusData(); - RdaStatusData(const RdaStatusData&) = delete; + RdaStatusData(const RdaStatusData&) = delete; RdaStatusData& operator=(const RdaStatusData&) = delete; RdaStatusData(RdaStatusData&&) noexcept; RdaStatusData& operator=(RdaStatusData&&) noexcept; - uint16_t rda_status() const; - uint16_t operability_status() const; - uint16_t control_status() const; - uint16_t auxiliary_power_generator_state() const; - uint16_t average_transmitter_power() const; - float horizontal_reflectivity_calibration_correction() const; - uint16_t data_transmission_enabled() const; - uint16_t volume_coverage_pattern_number() const; - uint16_t rda_control_authorization() const; - uint16_t rda_build_number() const; - uint16_t operational_mode() const; - uint16_t super_resolution_status() const; - uint16_t clutter_mitigation_decision_status() const; - uint16_t avset_ebc_rda_log_data_status() const; - uint16_t rda_alarm_summary() const; - uint16_t command_acknowledgement() const; - uint16_t channel_control_status() const; - uint16_t spot_blanking_status() const; - uint16_t bypass_map_generation_date() const; - uint16_t bypass_map_generation_time() const; - uint16_t clutter_filter_map_generation_date() const; - uint16_t clutter_filter_map_generation_time() const; - float vertical_reflectivity_calibration_correction() const; - uint16_t transition_power_source_status() const; - uint16_t rms_control_status() const; - uint16_t performance_check_status() const; - uint16_t alarm_codes(unsigned i) const; - uint16_t signal_processing_options() const; - uint16_t status_version() const; + [[nodiscard]] std::uint16_t rda_status() const; + [[nodiscard]] std::uint16_t operability_status() const; + [[nodiscard]] std::uint16_t control_status() const; + [[nodiscard]] std::uint16_t auxiliary_power_generator_state() const; + [[nodiscard]] std::uint16_t average_transmitter_power() const; + [[nodiscard]] float horizontal_reflectivity_calibration_correction() const; + [[nodiscard]] std::uint16_t data_transmission_enabled() const; + [[nodiscard]] std::uint16_t volume_coverage_pattern_number() const; + [[nodiscard]] std::uint16_t rda_control_authorization() const; + [[nodiscard]] std::uint16_t rda_build_number() const; + [[nodiscard]] std::uint16_t operational_mode() const; + [[nodiscard]] std::uint16_t super_resolution_status() const; + [[nodiscard]] std::uint16_t clutter_mitigation_decision_status() const; + [[nodiscard]] std::uint16_t rda_scan_and_data_flags() const; + [[nodiscard]] std::uint16_t rda_alarm_summary() const; + [[nodiscard]] std::uint16_t command_acknowledgement() const; + [[nodiscard]] std::uint16_t channel_control_status() const; + [[nodiscard]] std::uint16_t spot_blanking_status() const; + [[nodiscard]] std::uint16_t bypass_map_generation_date() const; + [[nodiscard]] std::uint16_t bypass_map_generation_time() const; + [[nodiscard]] std::uint16_t clutter_filter_map_generation_date() const; + [[nodiscard]] std::uint16_t clutter_filter_map_generation_time() const; + [[nodiscard]] float vertical_reflectivity_calibration_correction() const; + [[nodiscard]] std::uint16_t transition_power_source_status() const; + [[nodiscard]] std::uint16_t rms_control_status() const; + [[nodiscard]] std::uint16_t performance_check_status() const; + [[nodiscard]] std::uint16_t alarm_codes(unsigned i) const; + [[nodiscard]] std::uint16_t signal_processing_options() const; + [[nodiscard]] std::uint16_t downloaded_pattern_number() const; + [[nodiscard]] std::uint16_t status_version() const; - bool Parse(std::istream& is); + bool Parse(std::istream& is) override; static std::shared_ptr Create(Level2MessageHeader&& header, std::istream& is); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; -} // namespace rda -} // namespace wsr88d -} // namespace scwx +} // namespace scwx::wsr88d::rda diff --git a/wxdata/include/scwx/wsr88d/rpg/digital_radial_data_array_packet.hpp b/wxdata/include/scwx/wsr88d/rpg/digital_radial_data_array_packet.hpp index 9b94ba34..135b81ec 100644 --- a/wxdata/include/scwx/wsr88d/rpg/digital_radial_data_array_packet.hpp +++ b/wxdata/include/scwx/wsr88d/rpg/digital_radial_data_array_packet.hpp @@ -29,16 +29,16 @@ public: operator=(DigitalRadialDataArrayPacket&&) noexcept; uint16_t packet_code() const override; - uint16_t index_of_first_range_bin() const; - uint16_t number_of_range_bins() const; - int16_t i_center_of_sweep() const; - int16_t j_center_of_sweep() const; + uint16_t index_of_first_range_bin() const override; + uint16_t number_of_range_bins() const override; + int16_t i_center_of_sweep() const override; + int16_t j_center_of_sweep() const override; float range_scale_factor() const; - uint16_t number_of_radials() const; + uint16_t number_of_radials() const override; - float start_angle(uint16_t r) const; - float delta_angle(uint16_t r) const; - const std::vector& level(uint16_t r) const; + float start_angle(uint16_t r) const override; + float delta_angle(uint16_t r) const override; + const std::vector& level(uint16_t r) const override; size_t data_size() const override; diff --git a/wxdata/include/scwx/wsr88d/rpg/digital_raster_data_array_packet.hpp b/wxdata/include/scwx/wsr88d/rpg/digital_raster_data_array_packet.hpp new file mode 100644 index 00000000..c0309568 --- /dev/null +++ b/wxdata/include/scwx/wsr88d/rpg/digital_raster_data_array_packet.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include +#include + +namespace scwx::wsr88d::rpg +{ + +class DigitalRasterDataArrayPacket : public Packet +{ +public: + explicit DigitalRasterDataArrayPacket(); + ~DigitalRasterDataArrayPacket() override; + + DigitalRasterDataArrayPacket(const DigitalRasterDataArrayPacket&) = delete; + DigitalRasterDataArrayPacket& + operator=(const DigitalRasterDataArrayPacket&) = delete; + + DigitalRasterDataArrayPacket(DigitalRasterDataArrayPacket&&) noexcept; + DigitalRasterDataArrayPacket& + operator=(DigitalRasterDataArrayPacket&&) noexcept; + + [[nodiscard]] std::uint16_t packet_code() const override; + [[nodiscard]] std::uint16_t i_coordinate_start() const; + [[nodiscard]] std::uint16_t j_coordinate_start() const; + [[nodiscard]] std::uint16_t i_scale_factor() const; + [[nodiscard]] std::uint16_t j_scale_factor() const; + [[nodiscard]] std::uint16_t number_of_cells() const; + [[nodiscard]] std::uint16_t number_of_rows() const; + + [[nodiscard]] std::uint16_t number_of_bytes_in_row(std::uint16_t r) const; + [[nodiscard]] const std::vector& level(std::uint16_t r) const; + + [[nodiscard]] std::size_t data_size() const override; + + bool Parse(std::istream& is) override; + + static std::shared_ptr + Create(std::istream& is); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace scwx::wsr88d::rpg diff --git a/wxdata/include/scwx/wsr88d/rpg/graphic_alphanumeric_block.hpp b/wxdata/include/scwx/wsr88d/rpg/graphic_alphanumeric_block.hpp index 9b1f2019..5823d66d 100644 --- a/wxdata/include/scwx/wsr88d/rpg/graphic_alphanumeric_block.hpp +++ b/wxdata/include/scwx/wsr88d/rpg/graphic_alphanumeric_block.hpp @@ -35,7 +35,7 @@ public: const std::vector>>& page_list() const; - bool Parse(std::istream& is); + bool Parse(std::istream& is) override; static constexpr size_t SIZE = 102u; diff --git a/wxdata/include/scwx/wsr88d/rpg/graphic_product_message.hpp b/wxdata/include/scwx/wsr88d/rpg/graphic_product_message.hpp index a14df940..cf52badd 100644 --- a/wxdata/include/scwx/wsr88d/rpg/graphic_product_message.hpp +++ b/wxdata/include/scwx/wsr88d/rpg/graphic_product_message.hpp @@ -30,7 +30,7 @@ public: GraphicProductMessage(GraphicProductMessage&&) noexcept; GraphicProductMessage& operator=(GraphicProductMessage&&) noexcept; - std::shared_ptr description_block() const; + std::shared_ptr description_block() const override; std::shared_ptr symbology_block() const; std::shared_ptr graphic_block() const; std::shared_ptr tabular_block() const; diff --git a/wxdata/include/scwx/wsr88d/rpg/level3_message_factory.hpp b/wxdata/include/scwx/wsr88d/rpg/level3_message_factory.hpp index c2556434..2a5e42ca 100644 --- a/wxdata/include/scwx/wsr88d/rpg/level3_message_factory.hpp +++ b/wxdata/include/scwx/wsr88d/rpg/level3_message_factory.hpp @@ -2,29 +2,22 @@ #include -namespace scwx -{ -namespace wsr88d -{ -namespace rpg +namespace scwx::wsr88d::rpg { class Level3MessageFactory { -private: +public: explicit Level3MessageFactory() = delete; ~Level3MessageFactory() = delete; - Level3MessageFactory(const Level3MessageFactory&) = delete; + Level3MessageFactory(const Level3MessageFactory&) = delete; Level3MessageFactory& operator=(const Level3MessageFactory&) = delete; - Level3MessageFactory(Level3MessageFactory&&) noexcept = delete; + Level3MessageFactory(Level3MessageFactory&&) noexcept = delete; Level3MessageFactory& operator=(Level3MessageFactory&&) noexcept = delete; -public: static std::shared_ptr Create(std::istream& is); }; -} // namespace rpg -} // namespace wsr88d -} // namespace scwx +} // namespace scwx::wsr88d::rpg diff --git a/wxdata/include/scwx/wsr88d/rpg/packet_factory.hpp b/wxdata/include/scwx/wsr88d/rpg/packet_factory.hpp index 889b8c3d..11e794e8 100644 --- a/wxdata/include/scwx/wsr88d/rpg/packet_factory.hpp +++ b/wxdata/include/scwx/wsr88d/rpg/packet_factory.hpp @@ -2,29 +2,22 @@ #include -namespace scwx -{ -namespace wsr88d -{ -namespace rpg +namespace scwx::wsr88d::rpg { class PacketFactory { -private: +public: explicit PacketFactory() = delete; ~PacketFactory() = delete; - PacketFactory(const PacketFactory&) = delete; + PacketFactory(const PacketFactory&) = delete; PacketFactory& operator=(const PacketFactory&) = delete; - PacketFactory(PacketFactory&&) noexcept = delete; + PacketFactory(PacketFactory&&) noexcept = delete; PacketFactory& operator=(PacketFactory&&) noexcept = delete; -public: static std::shared_ptr Create(std::istream& is); }; -} // namespace rpg -} // namespace wsr88d -} // namespace scwx +} // namespace scwx::wsr88d::rpg diff --git a/wxdata/include/scwx/wsr88d/rpg/product_description_block.hpp b/wxdata/include/scwx/wsr88d/rpg/product_description_block.hpp index 1182828a..a306460e 100644 --- a/wxdata/include/scwx/wsr88d/rpg/product_description_block.hpp +++ b/wxdata/include/scwx/wsr88d/rpg/product_description_block.hpp @@ -9,20 +9,14 @@ #include -namespace scwx +namespace scwx::wsr88d::rpg { -namespace wsr88d -{ -namespace rpg -{ - -class ProductDescriptionBlockImpl; class ProductDescriptionBlock : public awips::Message { public: explicit ProductDescriptionBlock(); - ~ProductDescriptionBlock(); + ~ProductDescriptionBlock() override; ProductDescriptionBlock(const ProductDescriptionBlock&) = delete; ProductDescriptionBlock& operator=(const ProductDescriptionBlock&) = delete; @@ -30,66 +24,67 @@ public: ProductDescriptionBlock(ProductDescriptionBlock&&) noexcept; ProductDescriptionBlock& operator=(ProductDescriptionBlock&&) noexcept; - int16_t block_divider() const; - float latitude_of_radar() const; - float longitude_of_radar() const; - int16_t height_of_radar() const; - int16_t product_code() const; - uint16_t operational_mode() const; - uint16_t volume_coverage_pattern() const; - int16_t sequence_number() const; - uint16_t volume_scan_number() const; - uint16_t volume_scan_date() const; - uint32_t volume_scan_start_time() const; - uint16_t generation_date_of_product() const; - uint32_t generation_time_of_product() const; - uint16_t elevation_number() const; - uint16_t data_level_threshold(size_t i) const; - uint8_t version() const; - uint8_t spot_blank() const; - uint32_t offset_to_symbology() const; - uint32_t offset_to_graphic() const; - uint32_t offset_to_tabular() const; + [[nodiscard]] std::int16_t block_divider() const; + [[nodiscard]] float latitude_of_radar() const; + [[nodiscard]] float longitude_of_radar() const; + [[nodiscard]] std::int16_t height_of_radar() const; + [[nodiscard]] std::int16_t product_code() const; + [[nodiscard]] std::uint16_t operational_mode() const; + [[nodiscard]] std::uint16_t volume_coverage_pattern() const; + [[nodiscard]] std::int16_t sequence_number() const; + [[nodiscard]] std::uint16_t volume_scan_number() const; + [[nodiscard]] std::uint16_t volume_scan_date() const; + [[nodiscard]] std::uint32_t volume_scan_start_time() const; + [[nodiscard]] std::uint16_t generation_date_of_product() const; + [[nodiscard]] std::uint32_t generation_time_of_product() const; + [[nodiscard]] std::uint16_t elevation_number() const; + [[nodiscard]] std::uint16_t data_level_threshold(size_t i) const; + [[nodiscard]] std::uint8_t version() const; + [[nodiscard]] std::uint8_t spot_blank() const; + [[nodiscard]] std::uint32_t offset_to_symbology() const; + [[nodiscard]] std::uint32_t offset_to_graphic() const; + [[nodiscard]] std::uint32_t offset_to_tabular() const; - float range() const; - uint16_t range_raw() const; - float x_resolution() const; - uint16_t x_resolution_raw() const; - float y_resolution() const; - uint16_t y_resolution_raw() const; + [[nodiscard]] float range() const; + [[nodiscard]] std::uint16_t range_raw() const; + [[nodiscard]] float x_resolution() const; + [[nodiscard]] std::uint16_t x_resolution_raw() const; + [[nodiscard]] float y_resolution() const; + [[nodiscard]] std::uint16_t y_resolution_raw() const; - uint16_t threshold() const; - float offset() const; - float scale() const; - uint16_t number_of_levels() const; + [[nodiscard]] std::uint16_t threshold() const; + [[nodiscard]] float offset() const; + [[nodiscard]] float scale() const; + [[nodiscard]] std::uint16_t number_of_levels() const; - std::optional data_level_code(std::uint8_t level) const; - std::optional data_value(std::uint8_t level) const; + [[nodiscard]] std::optional + data_level_code(std::uint8_t level) const; + [[nodiscard]] std::optional data_value(std::uint8_t level) const; - std::uint16_t log_start() const; - float log_offset() const; - float log_scale() const; + [[nodiscard]] std::uint16_t log_start() const; + [[nodiscard]] float log_offset() const; + [[nodiscard]] float log_scale() const; - float gr_scale() const; + [[nodiscard]] float gr_scale() const; - std::uint8_t data_mask() const; - std::uint8_t topped_mask() const; + [[nodiscard]] std::uint8_t data_mask() const; + [[nodiscard]] std::uint8_t topped_mask() const; - units::angle::degrees elevation() const; + [[nodiscard]] units::angle::degrees elevation() const; + [[nodiscard]] bool has_elevation() const; - bool IsCompressionEnabled() const; - bool IsDataLevelCoded() const; + [[nodiscard]] bool IsCompressionEnabled() const; + [[nodiscard]] bool IsDataLevelCoded() const; - size_t data_size() const override; + [[nodiscard]] std::size_t data_size() const override; bool Parse(std::istream& is) override; - static constexpr size_t SIZE = 102u; + static constexpr std::size_t SIZE = 102u; private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; -} // namespace rpg -} // namespace wsr88d -} // namespace scwx +} // namespace scwx::wsr88d::rpg diff --git a/wxdata/include/scwx/wsr88d/rpg/product_symbology_block.hpp b/wxdata/include/scwx/wsr88d/rpg/product_symbology_block.hpp index d597532c..69eac4c6 100644 --- a/wxdata/include/scwx/wsr88d/rpg/product_symbology_block.hpp +++ b/wxdata/include/scwx/wsr88d/rpg/product_symbology_block.hpp @@ -34,7 +34,7 @@ public: size_t data_size() const override; - bool Parse(std::istream& is); + bool Parse(std::istream& is) override; static constexpr size_t SIZE = 102u; diff --git a/wxdata/include/scwx/wsr88d/rpg/radar_coded_message.hpp b/wxdata/include/scwx/wsr88d/rpg/radar_coded_message.hpp index f1a22b5e..7d7846f0 100644 --- a/wxdata/include/scwx/wsr88d/rpg/radar_coded_message.hpp +++ b/wxdata/include/scwx/wsr88d/rpg/radar_coded_message.hpp @@ -27,7 +27,7 @@ public: RadarCodedMessage(RadarCodedMessage&&) noexcept; RadarCodedMessage& operator=(RadarCodedMessage&&) noexcept; - std::shared_ptr description_block() const; + std::shared_ptr description_block() const override; bool Parse(std::istream& is) override; diff --git a/wxdata/include/scwx/wsr88d/rpg/radial_data_packet.hpp b/wxdata/include/scwx/wsr88d/rpg/radial_data_packet.hpp index dd6e61ec..34540dc8 100644 --- a/wxdata/include/scwx/wsr88d/rpg/radial_data_packet.hpp +++ b/wxdata/include/scwx/wsr88d/rpg/radial_data_packet.hpp @@ -27,16 +27,16 @@ public: RadialDataPacket& operator=(RadialDataPacket&&) noexcept; uint16_t packet_code() const override; - uint16_t index_of_first_range_bin() const; - uint16_t number_of_range_bins() const; - int16_t i_center_of_sweep() const; - int16_t j_center_of_sweep() const; + uint16_t index_of_first_range_bin() const override; + uint16_t number_of_range_bins() const override; + int16_t i_center_of_sweep() const override; + int16_t j_center_of_sweep() const override; float scale_factor() const; - uint16_t number_of_radials() const; + uint16_t number_of_radials() const override; - float start_angle(uint16_t r) const; - float delta_angle(uint16_t r) const; - const std::vector& level(uint16_t r) const; + float start_angle(uint16_t r) const override; + float delta_angle(uint16_t r) const override; + const std::vector& level(uint16_t r) const override; size_t data_size() const override; diff --git a/wxdata/include/scwx/wsr88d/rpg/tabular_alphanumeric_block.hpp b/wxdata/include/scwx/wsr88d/rpg/tabular_alphanumeric_block.hpp index 0d41fe62..267bd0e5 100644 --- a/wxdata/include/scwx/wsr88d/rpg/tabular_alphanumeric_block.hpp +++ b/wxdata/include/scwx/wsr88d/rpg/tabular_alphanumeric_block.hpp @@ -33,7 +33,7 @@ public: const std::vector>& page_list() const; - bool Parse(std::istream& is); + bool Parse(std::istream& is) override; bool Parse(std::istream& is, bool skipHeader); static constexpr size_t SIZE = 102u; diff --git a/wxdata/include/scwx/wsr88d/rpg/tabular_product_message.hpp b/wxdata/include/scwx/wsr88d/rpg/tabular_product_message.hpp index 006d5e82..ff9231cc 100644 --- a/wxdata/include/scwx/wsr88d/rpg/tabular_product_message.hpp +++ b/wxdata/include/scwx/wsr88d/rpg/tabular_product_message.hpp @@ -28,7 +28,7 @@ public: TabularProductMessage(TabularProductMessage&&) noexcept; TabularProductMessage& operator=(TabularProductMessage&&) noexcept; - std::shared_ptr description_block() const; + std::shared_ptr description_block() const override; std::shared_ptr tabular_block() const; bool Parse(std::istream& is) override; diff --git a/wxdata/source/scwx/awips/coded_time_motion_location.cpp b/wxdata/source/scwx/awips/coded_time_motion_location.cpp index 61e5ba27..569cc513 100644 --- a/wxdata/source/scwx/awips/coded_time_motion_location.cpp +++ b/wxdata/source/scwx/awips/coded_time_motion_location.cpp @@ -13,7 +13,7 @@ #include -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) # include #endif @@ -107,7 +107,7 @@ bool CodedTimeMotionLocation::Parse(const StringRange& lines, { using namespace std::chrono; -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) using namespace date; #endif diff --git a/wxdata/source/scwx/awips/impact_based_warnings.cpp b/wxdata/source/scwx/awips/impact_based_warnings.cpp index 75f04d1e..40a20051 100644 --- a/wxdata/source/scwx/awips/impact_based_warnings.cpp +++ b/wxdata/source/scwx/awips/impact_based_warnings.cpp @@ -4,13 +4,40 @@ #include #include +#include namespace scwx { namespace awips { +namespace ibw +{ -static const std::string logPrefix_ = "scwx::awips::impact_based_warnings"; +static const std::string logPrefix_ = "scwx::awips::ibw::impact_based_warnings"; + +static const boost::unordered_flat_map + impactBasedWarningInfo_ { + {Phenomenon::Marine, + ImpactBasedWarningInfo {.hasTornadoPossibleTag_ = true}}, + {Phenomenon::FlashFlood, + ImpactBasedWarningInfo { + .threatCategories_ {ThreatCategory::Base, + ThreatCategory::Considerable, + ThreatCategory::Catastrophic}}}, + {Phenomenon::SevereThunderstorm, + ImpactBasedWarningInfo { + .hasTornadoPossibleTag_ = true, + .threatCategories_ {ThreatCategory::Base, + ThreatCategory::Considerable, + ThreatCategory::Destructive}}}, + {Phenomenon::SnowSquall, ImpactBasedWarningInfo {}}, + {Phenomenon::Tornado, + ImpactBasedWarningInfo { + .hasObservedTag_ = true, + .threatCategories_ {ThreatCategory::Base, + ThreatCategory::Considerable, + ThreatCategory::Catastrophic}}}, + {Phenomenon::Unknown, ImpactBasedWarningInfo {}}}; static const std::unordered_map threatCategoryName_ {{ThreatCategory::Base, "Base"}, @@ -20,6 +47,16 @@ static const std::unordered_map {ThreatCategory::Catastrophic, "Catastrophic"}, {ThreatCategory::Unknown, "?"}}; +const ImpactBasedWarningInfo& GetImpactBasedWarningInfo(Phenomenon phenomenon) +{ + auto it = impactBasedWarningInfo_.find(phenomenon); + if (it != impactBasedWarningInfo_.cend()) + { + return it->second; + } + return impactBasedWarningInfo_.at(Phenomenon::Unknown); +} + SCWX_GET_ENUM(ThreatCategory, GetThreatCategory, threatCategoryName_) const std::string& GetThreatCategoryName(ThreatCategory threatCategory) @@ -27,5 +64,6 @@ const std::string& GetThreatCategoryName(ThreatCategory threatCategory) return threatCategoryName_.at(threatCategory); } +} // namespace ibw } // namespace awips } // namespace scwx diff --git a/wxdata/source/scwx/awips/message.cpp b/wxdata/source/scwx/awips/message.cpp index 73ae88a2..f4dbe9b7 100644 --- a/wxdata/source/scwx/awips/message.cpp +++ b/wxdata/source/scwx/awips/message.cpp @@ -9,17 +9,22 @@ namespace awips static const std::string logPrefix_ = "scwx::awips::message"; static const auto logger_ = util::Logger::Create(logPrefix_); -class MessageImpl +class Message::Impl { public: - explicit MessageImpl() {}; - ~MessageImpl() = default; + explicit Impl() = default; + ~Impl() = default; + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; }; -Message::Message() : p(std::make_unique()) {} +Message::Message() : p(std::make_unique()) {} Message::~Message() = default; -Message::Message(Message&&) noexcept = default; +Message::Message(Message&&) noexcept = default; Message& Message::operator=(Message&&) noexcept = default; bool Message::ValidateMessage(std::istream& is, size_t bytesRead) const diff --git a/wxdata/source/scwx/awips/phenomenon.cpp b/wxdata/source/scwx/awips/phenomenon.cpp index ee8e549d..c8154509 100644 --- a/wxdata/source/scwx/awips/phenomenon.cpp +++ b/wxdata/source/scwx/awips/phenomenon.cpp @@ -77,64 +77,65 @@ static const PhenomenonCodesBimap phenomenonCodes_ = (Phenomenon::FreezingSpray, "ZY") // (Phenomenon::Unknown, "??"); -static const std::unordered_map phenomenonText_ { - {Phenomenon::AshfallLand, "Ashfall (land)"}, // - {Phenomenon::AirStagnation, "Air Stagnation"}, // - {Phenomenon::BeachHazard, "Beach Hazard"}, // - {Phenomenon::BriskWind, "Brisk Wind"}, // - {Phenomenon::Blizzard, "Blizzard"}, // - {Phenomenon::CoastalFlood, "Coastal Flood"}, // - {Phenomenon::DebrisFlow, "Debris Flow"}, // - {Phenomenon::DustStorm, "Dust Storm"}, // - {Phenomenon::BlowingDust, "Blowing Dust"}, // - {Phenomenon::ExtremeCold, "Extreme Cold"}, // - {Phenomenon::ExcessiveHeat, "Excessive Heat"}, // - {Phenomenon::ExtremeWind, "Extreme Wind"}, // - {Phenomenon::Flood, "Flood"}, // - {Phenomenon::FlashFlood, "Flash Flood"}, // - {Phenomenon::DenseFogLand, "Dense Fog (land)"}, // - {Phenomenon::Flood, "Flood (Forecast Points)"}, // - {Phenomenon::Frost, "Frost"}, // - {Phenomenon::FireWeather, "Fire Weather"}, // - {Phenomenon::Freeze, "Freeze"}, // - {Phenomenon::Gale, "Gale"}, // - {Phenomenon::HurricaneForceWind, "Hurricane Force Wind"}, // - {Phenomenon::Heat, "Heat"}, // - {Phenomenon::Hurricane, "Hurricane"}, // - {Phenomenon::HighWind, "High Wind"}, // - {Phenomenon::Hydrologic, "Hydrologic"}, // - {Phenomenon::HardFreeze, "Hard Freeze"}, // - {Phenomenon::IceStorm, "Ice Storm"}, // - {Phenomenon::LakeEffectSnow, "Lake Effect Snow"}, // - {Phenomenon::LowWater, "Low Water"}, // - {Phenomenon::LakeshoreFlood, "Lakeshore Flood"}, // - {Phenomenon::LakeWind, "Lake Wind"}, // - {Phenomenon::Marine, "Marine"}, // - {Phenomenon::DenseFogMarine, "Dense Fog (marine)"}, // - {Phenomenon::AshfallMarine, "Ashfall (marine)"}, // - {Phenomenon::DenseSmokeMarine, "Dense Smoke (marine)"}, // - {Phenomenon::RipCurrentRisk, "Rip Current Risk"}, // - {Phenomenon::SmallCraft, "Small Craft"}, // - {Phenomenon::HazardousSeas, "Hazardous Seas"}, // - {Phenomenon::DenseSmokeLand, "Dense Smoke (land)"}, // - {Phenomenon::Storm, "Storm"}, // - {Phenomenon::StormSurge, "Storm Surge"}, // - {Phenomenon::SnowSquall, "Snow Squall"}, // - {Phenomenon::HighSurf, "High Surf"}, // - {Phenomenon::SevereThunderstorm, "Severe Thunderstorm"}, // - {Phenomenon::Tornado, "Tornado"}, // - {Phenomenon::TropicalStorm, "Tropical Storm"}, // - {Phenomenon::Tsunami, "Tsunami"}, // - {Phenomenon::Typhoon, "Typhoon"}, // - {Phenomenon::HeavyFreezingSpray, "Heavy Freezing Spray"}, // - {Phenomenon::WindChill, "Wind Chill"}, // - {Phenomenon::Wind, "Wind"}, // - {Phenomenon::WinterStorm, "Winter Storm"}, // - {Phenomenon::WinterWeather, "Winter Weather"}, // - {Phenomenon::FreezingFog, "Freezing Fog"}, // - {Phenomenon::FreezingRain, "Freezing Rain"}, // - {Phenomenon::FreezingSpray, "Freezing Spray"}, // - {Phenomenon::Unknown, "Unknown"}}; +static const PhenomenonCodesBimap phenomenonText_ = + boost::assign::list_of // + (Phenomenon::AshfallLand, "Ashfall (land)") // + (Phenomenon::AirStagnation, "Air Stagnation") // + (Phenomenon::BeachHazard, "Beach Hazard") // + (Phenomenon::BriskWind, "Brisk Wind") // + (Phenomenon::Blizzard, "Blizzard") // + (Phenomenon::CoastalFlood, "Coastal Flood") // + (Phenomenon::DebrisFlow, "Debris Flow") // + (Phenomenon::DustStorm, "Dust Storm") // + (Phenomenon::BlowingDust, "Blowing Dust") // + (Phenomenon::ExtremeCold, "Extreme Cold") // + (Phenomenon::ExcessiveHeat, "Excessive Heat") // + (Phenomenon::ExtremeWind, "Extreme Wind") // + (Phenomenon::Flood, "Flood") // + (Phenomenon::FlashFlood, "Flash Flood") // + (Phenomenon::DenseFogLand, "Dense Fog (land)") // + (Phenomenon::Flood, "Flood (Forecast Points)") // + (Phenomenon::Frost, "Frost") // + (Phenomenon::FireWeather, "Fire Weather") // + (Phenomenon::Freeze, "Freeze") // + (Phenomenon::Gale, "Gale") // + (Phenomenon::HurricaneForceWind, "Hurricane Force Wind") // + (Phenomenon::Heat, "Heat") // + (Phenomenon::Hurricane, "Hurricane") // + (Phenomenon::HighWind, "High Wind") // + (Phenomenon::Hydrologic, "Hydrologic") // + (Phenomenon::HardFreeze, "Hard Freeze") // + (Phenomenon::IceStorm, "Ice Storm") // + (Phenomenon::LakeEffectSnow, "Lake Effect Snow") // + (Phenomenon::LowWater, "Low Water") // + (Phenomenon::LakeshoreFlood, "Lakeshore Flood") // + (Phenomenon::LakeWind, "Lake Wind") // + (Phenomenon::Marine, "Marine") // + (Phenomenon::DenseFogMarine, "Dense Fog (marine)") // + (Phenomenon::AshfallMarine, "Ashfall (marine)") // + (Phenomenon::DenseSmokeMarine, "Dense Smoke (marine)") // + (Phenomenon::RipCurrentRisk, "Rip Current Risk") // + (Phenomenon::SmallCraft, "Small Craft") // + (Phenomenon::HazardousSeas, "Hazardous Seas") // + (Phenomenon::DenseSmokeLand, "Dense Smoke (land)") // + (Phenomenon::Storm, "Storm") // + (Phenomenon::StormSurge, "Storm Surge") // + (Phenomenon::SnowSquall, "Snow Squall") // + (Phenomenon::HighSurf, "High Surf") // + (Phenomenon::SevereThunderstorm, "Severe Thunderstorm") // + (Phenomenon::Tornado, "Tornado") // + (Phenomenon::TropicalStorm, "Tropical Storm") // + (Phenomenon::Tsunami, "Tsunami") // + (Phenomenon::Typhoon, "Typhoon") // + (Phenomenon::HeavyFreezingSpray, "Heavy Freezing Spray") // + (Phenomenon::WindChill, "Wind Chill") // + (Phenomenon::Wind, "Wind") // + (Phenomenon::WinterStorm, "Winter Storm") // + (Phenomenon::WinterWeather, "Winter Weather") // + (Phenomenon::FreezingFog, "Freezing Fog") // + (Phenomenon::FreezingRain, "Freezing Rain") // + (Phenomenon::FreezingSpray, "Freezing Spray") // + (Phenomenon::Unknown, "Unknown"); Phenomenon GetPhenomenon(const std::string& code) { @@ -154,6 +155,24 @@ Phenomenon GetPhenomenon(const std::string& code) return phenomenon; } +Phenomenon GetPhenomenonFromText(const std::string& text) +{ + Phenomenon phenomenon; + + if (phenomenonText_.right.find(text) != phenomenonText_.right.end()) + { + phenomenon = phenomenonText_.right.at(text); + } + else + { + phenomenon = Phenomenon::Unknown; + + logger_->debug("Unrecognized code: \"{}\"", text); + } + + return phenomenon; +} + const std::string& GetPhenomenonCode(Phenomenon phenomenon) { return phenomenonCodes_.left.at(phenomenon); @@ -161,7 +180,7 @@ const std::string& GetPhenomenonCode(Phenomenon phenomenon) const std::string& GetPhenomenonText(Phenomenon phenomenon) { - return phenomenonText_.at(phenomenon); + return phenomenonText_.left.at(phenomenon); } } // namespace awips diff --git a/wxdata/source/scwx/awips/pvtec.cpp b/wxdata/source/scwx/awips/pvtec.cpp index a661a037..b93d2be0 100644 --- a/wxdata/source/scwx/awips/pvtec.cpp +++ b/wxdata/source/scwx/awips/pvtec.cpp @@ -17,7 +17,7 @@ #include #include -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) # include #endif @@ -143,7 +143,7 @@ bool PVtec::Parse(const std::string& s) { using namespace std::chrono; -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) using namespace date; #endif diff --git a/wxdata/source/scwx/awips/text_product_file.cpp b/wxdata/source/scwx/awips/text_product_file.cpp index 3edc7b2d..96d5503f 100644 --- a/wxdata/source/scwx/awips/text_product_file.cpp +++ b/wxdata/source/scwx/awips/text_product_file.cpp @@ -3,24 +3,31 @@ #include -namespace scwx -{ -namespace awips +#include + +namespace scwx::awips { static const std::string logPrefix_ = "scwx::awips::text_product_file"; static const auto logger_ = util::Logger::Create(logPrefix_); -class TextProductFileImpl +class TextProductFile::Impl { public: - explicit TextProductFileImpl() : messages_ {} {}; - ~TextProductFileImpl() = default; + explicit Impl() : messages_ {} {}; + ~Impl() = default; + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + + Impl(Impl&&) = delete; + Impl& operator=(Impl&&) = delete; std::vector> messages_; }; -TextProductFile::TextProductFile() : p(std::make_unique()) +TextProductFile::TextProductFile() : + p(std::make_unique()) { } TextProductFile::~TextProductFile() = default; @@ -59,16 +66,34 @@ bool TextProductFile::LoadFile(const std::string& filename) if (fileValid) { - fileValid = LoadData(f); + fileValid = LoadData(filename, f); } return fileValid; } -bool TextProductFile::LoadData(std::istream& is) +bool TextProductFile::LoadData(const std::string& filename, std::istream& is) { + static constexpr LazyRE2 kDateTimePattern_ = { + R"(((?:19|20)\d{2}))" // Year (YYYY) + R"((0[1-9]|1[0-2]))" // Month (MM) + R"((0[1-9]|[12]\d|3[01]))" // Day (DD) + R"(_?)" // Optional separator (not captured) + R"(([01]\d|2[0-3]))" // Hour (HH) + }; + logger_->trace("Loading Data"); + // Attempt to parse the date from the filename + std::optional yearMonth; + int year {}; + unsigned int month {}; + + if (RE2::PartialMatch(filename, *kDateTimePattern_, &year, &month)) + { + yearMonth = std::chrono::year {year} / std::chrono::month {month}; + } + while (!is.eof()) { std::shared_ptr message = @@ -77,7 +102,7 @@ bool TextProductFile::LoadData(std::istream& is) if (message != nullptr) { - for (auto m : p->messages_) + for (const auto& m : p->messages_) { if (*m->wmo_header().get() == *message->wmo_header().get()) { @@ -88,6 +113,11 @@ bool TextProductFile::LoadData(std::istream& is) if (!duplicate) { + if (yearMonth.has_value()) + { + message->wmo_header()->SetDateHint(yearMonth.value()); + } + p->messages_.push_back(message); } } @@ -100,5 +130,4 @@ bool TextProductFile::LoadData(std::istream& is) return !p->messages_.empty(); } -} // namespace awips -} // namespace scwx +} // namespace scwx::awips diff --git a/wxdata/source/scwx/awips/text_product_message.cpp b/wxdata/source/scwx/awips/text_product_message.cpp index 5128aee8..53ec502d 100644 --- a/wxdata/source/scwx/awips/text_product_message.cpp +++ b/wxdata/source/scwx/awips/text_product_message.cpp @@ -9,11 +9,10 @@ #include #include +#include #include -namespace scwx -{ -namespace awips +namespace scwx::awips { static const std::string logPrefix_ = "scwx::awips::text_product_message"; @@ -27,8 +26,8 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); // Look for hhmm (xM|UTC) to key the date/time string static constexpr LazyRE2 reDateTimeString = {"^[0-9]{3,4} ([AP]M|UTC)"}; -static void ParseCodedInformation(std::shared_ptr segment, - const std::string& wfo); +static void ParseCodedInformation(const std::shared_ptr& segment, + const std::string& wfo); static std::vector ParseProductContent(std::istream& is); static void SkipBlankLines(std::istream& is); static bool TryParseEndOfProduct(std::istream& is); @@ -50,6 +49,13 @@ public: } ~TextProductMessageImpl() = default; + TextProductMessageImpl(const TextProductMessageImpl&) = delete; + TextProductMessageImpl& operator=(const TextProductMessageImpl&) = delete; + TextProductMessageImpl(const TextProductMessageImpl&&) = delete; + TextProductMessageImpl& operator=(const TextProductMessageImpl&&) = delete; + + boost::uuids::uuid uuid_ {boost::uuids::random_generator()()}; + std::string messageContent_; std::shared_ptr wmoHeader_; std::vector mndHeader_; @@ -67,6 +73,11 @@ TextProductMessage::TextProductMessage(TextProductMessage&&) noexcept = default; TextProductMessage& TextProductMessage::operator=(TextProductMessage&&) noexcept = default; +boost::uuids::uuid TextProductMessage::uuid() const +{ + return p->uuid_; +} + std::string TextProductMessage::message_content() const { return p->messageContent_; @@ -116,71 +127,11 @@ std::chrono::system_clock::time_point Segment::event_begin() const // If event begin is 000000T0000Z if (eventBegin == std::chrono::system_clock::time_point {}) { - using namespace std::chrono; - // Determine event end from P-VTEC string - system_clock::time_point eventEnd = + std::chrono::system_clock::time_point eventEnd = header_->vtecString_[0].pVtec_.event_end(); - auto endDays = floor(eventEnd); - year_month_day endDate {endDays}; - - // Determine WMO date/time - std::string wmoDateTime = wmoHeader_->date_time(); - - bool wmoDateTimeValid = false; - unsigned int dayOfMonth = 0; - unsigned long beginHour = 0; - unsigned long beginMinute = 0; - - try - { - // WMO date time is in the format DDHHMM - dayOfMonth = - static_cast(std::stoul(wmoDateTime.substr(0, 2))); - beginHour = std::stoul(wmoDateTime.substr(2, 2)); - beginMinute = std::stoul(wmoDateTime.substr(4, 2)); - wmoDateTimeValid = true; - } - catch (const std::exception&) - { - logger_->warn("Malformed WMO date/time: {}", wmoDateTime); - } - - if (wmoDateTimeValid) - { - // Combine end date year and month with WMO date time - eventBegin = - sys_days {endDate.year() / endDate.month() / day {dayOfMonth}} + - hours {beginHour} + minutes {beginMinute}; - - // If the begin date is after the end date, assume the start time - // was the previous month (give a 1 day grace period for expiring - // events in the past) - if (eventBegin > eventEnd + 24h) - { - // If the current end month is January - if (endDate.month() == January) - { - // The begin month must be December of last year - eventBegin = - sys_days { - year {static_cast((endDate.year() - 1y).count())} / - December / day {dayOfMonth}} + - hours {beginHour} + minutes {beginMinute}; - } - else - { - // Back up one month - eventBegin = - sys_days {endDate.year() / - month {static_cast( - (endDate.month() - month {1}).count())} / - day {dayOfMonth}} + - hours {beginHour} + minutes {beginMinute}; - } - } - } + eventBegin = wmoHeader_->GetDateTime(eventEnd); } } @@ -232,7 +183,7 @@ bool TextProductMessage::Parse(std::istream& is) if (i == 0) { - if (is.peek() != '\r') + if (is.peek() != '\r' && is.peek() != '\n') { segment->header_ = TryParseSegmentHeader(is); } @@ -318,8 +269,8 @@ bool TextProductMessage::Parse(std::istream& is) return dataValid; } -void ParseCodedInformation(std::shared_ptr segment, - const std::string& wfo) +void ParseCodedInformation(const std::shared_ptr& segment, + const std::string& wfo) { typedef std::vector::const_iterator StringIterator; @@ -352,8 +303,8 @@ void ParseCodedInformation(std::shared_ptr segment, codedLocationEnd = it; } - else if (codedMotionBegin == productContent.cend() && - it->starts_with("TIME...MOT...LOC")) + if (codedMotionBegin == productContent.cend() && + it->starts_with("TIME...MOT...LOC")) { codedMotionBegin = it; } @@ -366,8 +317,7 @@ void ParseCodedInformation(std::shared_ptr segment, codedMotionEnd = it; } - else if (!segment->observed_ && - it->find("...OBSERVED") != std::string::npos) + if (!segment->observed_ && it->find("...OBSERVED") != std::string::npos) { segment->observed_ = true; } @@ -378,21 +328,37 @@ void ParseCodedInformation(std::shared_ptr segment, segment->tornadoPossible_ = true; } - else if (segment->threatCategory_ == ThreatCategory::Base && + // Assignment of an iterator permitted + // NOLINTBEGIN(bugprone-assignment-in-if-condition) + else if (segment->threatCategory_ == ibw::ThreatCategory::Base && (threatTagIt = std::find_if(kThreatCategoryTags.cbegin(), kThreatCategoryTags.cend(), [&it](const std::string& tag) { return it->starts_with(tag); })) != kThreatCategoryTags.cend() && it->length() > threatTagIt->length()) + // NOLINTEND(bugprone-assignment-in-if-condition) { const std::string threatCategoryName = it->substr(threatTagIt->length()); - ThreatCategory threatCategory = GetThreatCategory(threatCategoryName); - if (threatCategory == ThreatCategory::Unknown) + ibw::ThreatCategory threatCategory = + ibw::GetThreatCategory(threatCategoryName); + + switch (threatCategory) { - threatCategory = ThreatCategory::Base; + case ibw::ThreatCategory::Significant: + // "Significant" is no longer an official tag, and has largely been + // replaced with "Considerable". + threatCategory = ibw::ThreatCategory::Considerable; + break; + + case ibw::ThreatCategory::Unknown: + threatCategory = ibw::ThreatCategory::Base; + break; + + default: + break; } segment->threatCategory_ = threatCategory; @@ -445,7 +411,7 @@ void SkipBlankLines(std::istream& is) { std::string line; - while (is.peek() == '\r') + while (is.peek() == '\r' || is.peek() == '\n') { util::getline(is, line); } @@ -500,7 +466,7 @@ std::vector TryParseMndHeader(std::istream& is) std::string line; std::streampos isBegin = is.tellg(); - while (!is.eof() && is.peek() != '\r') + while (!is.eof() && is.peek() != '\r' && is.peek() != '\n') { util::getline(is, line); mndHeader.push_back(line); @@ -533,7 +499,7 @@ std::vector TryParseOverviewBlock(std::istream& is) if (is.peek() == '.') { - while (!is.eof() && is.peek() != '\r') + while (!is.eof() && is.peek() != '\r' && is.peek() != '\n') { util::getline(is, line); overviewBlock.push_back(line); @@ -563,7 +529,7 @@ std::optional TryParseSegmentHeader(std::istream& is) header->ugcString_.push_back(line); // If UGC is multi-line, continue parsing - while (!is.eof() && is.peek() != '\r' && + while (!is.eof() && is.peek() != '\r' && is.peek() != '\n' && !RE2::PartialMatch(line, *reUgcExpiration)) { util::getline(is, line); @@ -582,7 +548,7 @@ std::optional TryParseSegmentHeader(std::istream& is) header->vtecString_.push_back(std::move(*vtec)); } - while (!is.eof() && is.peek() != '\r') + while (!is.eof() && is.peek() != '\r' && is.peek() != '\n') { util::getline(is, line); if (!RE2::PartialMatch(line, *reDateTimeString)) @@ -627,10 +593,8 @@ std::optional TryParseVtecString(std::istream& is) if (RE2::PartialMatch(line, *rePVtecString)) { - bool vtecValid; - - vtec = Vtec(); - vtecValid = vtec->pVtec_.Parse(line); + vtec = Vtec(); + const bool vtecValid = vtec->pVtec_.Parse(line); isBegin = is.tellg(); @@ -674,5 +638,4 @@ std::shared_ptr TextProductMessage::Create(std::istream& is) return message; } -} // namespace awips -} // namespace scwx +} // namespace scwx::awips diff --git a/wxdata/source/scwx/awips/wmo_header.cpp b/wxdata/source/scwx/awips/wmo_header.cpp index eb4501e7..f89db609 100644 --- a/wxdata/source/scwx/awips/wmo_header.cpp +++ b/wxdata/source/scwx/awips/wmo_header.cpp @@ -12,14 +12,19 @@ # include #endif -namespace scwx -{ -namespace awips +namespace scwx::awips { static const std::string logPrefix_ = "scwx::awips::wmo_header"; static const auto logger_ = util::Logger::Create(logPrefix_); +static constexpr std::size_t kWmoHeaderMinLineLength_ = 18; +static constexpr std::size_t kWmoIdentifierLengthMin_ = 5; +static constexpr std::size_t kWmoIdentifierLengthMax_ = 6; +static constexpr std::size_t kIcaoLength_ = 4; +static constexpr std::size_t kDateTimeLength_ = 6; +static constexpr std::size_t kAwipsIdentifierLineLength_ = 6; + class WmoHeaderImpl { public: @@ -37,17 +42,31 @@ public: } ~WmoHeaderImpl() = default; + WmoHeaderImpl(const WmoHeaderImpl&) = delete; + WmoHeaderImpl& operator=(const WmoHeaderImpl&) = delete; + WmoHeaderImpl(const WmoHeaderImpl&&) = delete; + WmoHeaderImpl& operator=(const WmoHeaderImpl&&) = delete; + + void CalculateAbsoluteDateTime(); + bool ParseDateTime(unsigned int& dayOfMonth, + unsigned long& hour, + unsigned long& minute); + bool operator==(const WmoHeaderImpl& o) const; - std::string sequenceNumber_; - std::string dataType_; - std::string geographicDesignator_; - std::string bulletinId_; - std::string icao_; - std::string dateTime_; - std::string bbbIndicator_; - std::string productCategory_; - std::string productDesignator_; + std::string sequenceNumber_ {}; + std::string dataType_ {}; + std::string geographicDesignator_ {}; + std::string bulletinId_ {}; + std::string icao_ {}; + std::string dateTime_ {}; + std::string bbbIndicator_ {}; + std::string productCategory_ {}; + std::string productDesignator_ {}; + + std::optional dateHint_ {}; + std::optional> + absoluteDateTime_ {}; }; WmoHeader::WmoHeader() : p(std::make_unique()) {} @@ -119,6 +138,71 @@ std::string WmoHeader::product_designator() const return p->productDesignator_; } +std::chrono::sys_time WmoHeader::GetDateTime( + std::optional endTimeHint) +{ + std::chrono::sys_time wmoDateTime {}; + + const auto absoluteDateTime = p->absoluteDateTime_; + + if (absoluteDateTime.has_value()) + { + wmoDateTime = absoluteDateTime.value(); + } + else if (endTimeHint.has_value()) + { + bool dateTimeValid = false; + unsigned int dayOfMonth = 0; + unsigned long hour = 0; + unsigned long minute = 0; + + dateTimeValid = p->ParseDateTime(dayOfMonth, hour, minute); + + if (dateTimeValid) + { + using namespace std::chrono; + + const auto endDays = floor(endTimeHint.value()); + const year_month_day endDate {endDays}; + + // Combine end date year and month with WMO date time + wmoDateTime = + sys_days {endDate.year() / endDate.month() / day {dayOfMonth}} + + hours {hour} + minutes {minute}; + + // If the begin date is after the end date, assume the start time + // was the previous month (give a 1 day grace period for expiring + // events in the past) + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) + if (wmoDateTime > endTimeHint.value() + 24h) + { + // If the current end month is January + if (endDate.month() == January) + { + // The begin month must be December of last year + wmoDateTime = + sys_days { + year {static_cast((endDate.year() - 1y).count())} / + December / day {dayOfMonth}} + + hours {hour} + minutes {minute}; + } + else + { + // Back up one month + wmoDateTime = + sys_days {endDate.year() / + month {static_cast( + (endDate.month() - month {1}).count())} / + day {dayOfMonth}} + + hours {hour} + minutes {minute}; + } + } + } + } + + return wmoDateTime; +} + bool WmoHeader::Parse(std::istream& is) { bool headerValid = true; @@ -132,9 +216,21 @@ bool WmoHeader::Parse(std::istream& is) { util::getline(is, sohLine); util::getline(is, sequenceLine); + util::getline(is, wmoLine); + } + else + { + // The next line could be the WMO line or the sequence line + util::getline(is, wmoLine); + if (wmoLine.length() < kWmoHeaderMinLineLength_) + { + // This is likely the sequence line instead + sequenceLine.swap(wmoLine); + util::getline(is, wmoLine); + } } - util::getline(is, wmoLine); + auto awipsLinePos = is.tellg(); util::getline(is, awipsLine); if (is.eof()) @@ -179,17 +275,18 @@ bool WmoHeader::Parse(std::istream& is) logger_->warn("Invalid number of WMO tokens"); headerValid = false; } - else if (wmoTokenList[0].size() != 6) + else if (wmoTokenList[0].size() < kWmoIdentifierLengthMin_ || + wmoTokenList[0].size() > kWmoIdentifierLengthMax_) { logger_->warn("WMO identifier malformed"); headerValid = false; } - else if (wmoTokenList[1].size() != 4) + else if (wmoTokenList[1].size() != kIcaoLength_) { logger_->warn("ICAO malformed"); headerValid = false; } - else if (wmoTokenList[2].size() != 6) + else if (wmoTokenList[2].size() != kDateTimeLength_) { logger_->warn("Date/time malformed"); headerValid = false; @@ -204,9 +301,11 @@ bool WmoHeader::Parse(std::istream& is) { p->dataType_ = wmoTokenList[0].substr(0, 2); p->geographicDesignator_ = wmoTokenList[0].substr(2, 2); - p->bulletinId_ = wmoTokenList[0].substr(4, 2); - p->icao_ = wmoTokenList[1]; - p->dateTime_ = wmoTokenList[2]; + p->bulletinId_ = wmoTokenList[0].substr(4, wmoTokenList[0].size() - 4); + p->icao_ = wmoTokenList[1]; + p->dateTime_ = wmoTokenList[2]; + + p->CalculateAbsoluteDateTime(); if (wmoTokenList.size() == 4) { @@ -224,10 +323,14 @@ bool WmoHeader::Parse(std::istream& is) if (headerValid) { - if (awipsLine.size() != 6) + if (awipsLine.size() != kAwipsIdentifierLineLength_) { - logger_->warn("AWIPS Identifier Line bad size"); - headerValid = false; + // Older products may be missing an AWIPS Identifier Line + logger_->trace("AWIPS Identifier Line bad size"); + + is.seekg(awipsLinePos); + p->productCategory_ = ""; + p->productDesignator_ = ""; } else { @@ -239,5 +342,60 @@ bool WmoHeader::Parse(std::istream& is) return headerValid; } -} // namespace awips -} // namespace scwx +void WmoHeader::SetDateHint(std::chrono::year_month dateHint) +{ + p->dateHint_ = dateHint; + p->CalculateAbsoluteDateTime(); +} + +bool WmoHeaderImpl::ParseDateTime(unsigned int& dayOfMonth, + unsigned long& hour, + unsigned long& minute) +{ + bool dateTimeValid = false; + + try + { + // WMO date time is in the format DDHHMM + dayOfMonth = + static_cast(std::stoul(dateTime_.substr(0, 2))); + hour = std::stoul(dateTime_.substr(2, 2)); + minute = std::stoul(dateTime_.substr(4, 2)); + dateTimeValid = true; + } + catch (const std::exception&) + { + logger_->warn("Malformed WMO date/time: {}", dateTime_); + } + + return dateTimeValid; +} + +void WmoHeaderImpl::CalculateAbsoluteDateTime() +{ + bool dateTimeValid = false; + + if (dateHint_.has_value() && !dateTime_.empty()) + { + unsigned int dayOfMonth = 0; + unsigned long hour = 0; + unsigned long minute = 0; + + dateTimeValid = ParseDateTime(dayOfMonth, hour, minute); + + if (dateTimeValid) + { + using namespace std::chrono; + absoluteDateTime_ = sys_days {dateHint_->year() / dateHint_->month() / + day {dayOfMonth}} + + hours {hour} + minutes {minute}; + } + } + + if (!dateTimeValid) + { + absoluteDateTime_.reset(); + } +} + +} // namespace scwx::awips diff --git a/wxdata/source/scwx/common/color_table.cpp b/wxdata/source/scwx/common/color_table.cpp index aff48f3d..32a4684f 100644 --- a/wxdata/source/scwx/common/color_table.cpp +++ b/wxdata/source/scwx/common/color_table.cpp @@ -10,8 +10,18 @@ #include #include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-parameter" +#endif + #include +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + #include namespace scwx diff --git a/wxdata/source/scwx/common/geographic.cpp b/wxdata/source/scwx/common/geographic.cpp index bf7d1a2c..e9a494d3 100644 --- a/wxdata/source/scwx/common/geographic.cpp +++ b/wxdata/source/scwx/common/geographic.cpp @@ -14,6 +14,34 @@ static std::string GetDegreeString(double degrees, DegreeStringType type, const std::string& suffix); +units::degrees GetAngleDelta(units::degrees angle1, + units::degrees angle2) +{ + // Normalize angles to [0, 360) + while (angle1.value() < 0.0f) + { + angle1 += units::degrees {360.0f}; + } + while (angle2.value() < 0.0f) + { + angle2 += units::degrees {360.0f}; + } + angle1 = units::degrees {std::fmod(angle1.value(), 360.f)}; + angle2 = units::degrees {std::fmod(angle2.value(), 360.f)}; + + // Calculate the absolute difference + auto delta = angle1 - angle2; + if (delta < units::degrees {0.0f}) + { + delta *= -1.0f; + } + + // Account for wrapping + delta = std::min(delta, units::degrees {360.0f} - delta); + + return delta; +} + Coordinate GetCentroid(const std::vector& coordinates) { double x = 0.0; diff --git a/wxdata/source/scwx/common/products.cpp b/wxdata/source/scwx/common/products.cpp index f3ecd1e8..48969e13 100644 --- a/wxdata/source/scwx/common/products.cpp +++ b/wxdata/source/scwx/common/products.cpp @@ -49,7 +49,7 @@ static const std::unordered_map level3ProductCodeMap_ { {153, "SDR"}, {154, "SDV"}, {159, "DZD"}, {161, "DCC"}, {163, "DKD"}, {165, "DHC"}, {166, "ML"}, {169, "OHA"}, {170, "DAA"}, {172, "DTA"}, {173, "DUA"}, {174, "DOD"}, {175, "DSD"}, {177, "HHC"}, {180, "TDR"}, - {182, "TDV"}}; + {182, "TDV"}, {186, "TZL"}}; static const std::unordered_map level3ProductDescription_ { @@ -84,6 +84,7 @@ static const std::unordered_map {"HHC", "Hybrid Hydrometeor Classification"}, {"TDR", "Digital Reflectivity"}, {"TDV", "Digital Velocity"}, + {"TZL", "Long Range Reflectivity"}, {"?", "Unknown"}}; static const std::unordered_map> @@ -91,6 +92,7 @@ static const std::unordered_map> // Reflectivity {"SDR", {"NXB", "NYB", "NZB", "N0B", "NAB", "N1B", "NBB", "N2B", "N3B"}}, {"DR", {"NXQ", "NYQ", "NZQ", "N0Q", "NAQ", "N1Q", "NBQ", "N2Q", "N3Q"}}, + {"TZL", {"TZL"}}, {"TDR", {"TZ0", "TZ1", "TZ2"}}, {"NCR", {"NCR"}}, @@ -184,7 +186,7 @@ static const std::unordered_map static const std::unordered_map> level3CategoryProductList_ { - {Level3ProductCategory::Reflectivity, {"SDR", "DR", "TDR", "NCR"}}, + {Level3ProductCategory::Reflectivity, {"SDR", "DR", "TZL", "TDR", "NCR"}}, {Level3ProductCategory::Velocity, {"SDV", "DV", "TDV"}}, {Level3ProductCategory::StormRelativeVelocity, {"SRM"}}, {Level3ProductCategory::SpectrumWidth, {"SW"}}, @@ -296,9 +298,27 @@ const std::string& GetLevel3CategoryDescription(Level3ProductCategory category) return level3CategoryDescription_.at(category); } -const std::string& -GetLevel3CategoryDefaultProduct(Level3ProductCategory category) +std::string +GetLevel3CategoryDefaultProduct(Level3ProductCategory category, + const Level3ProductCategoryMap& categoryMap) { + const auto& productsIt = categoryMap.find(category); + if (productsIt == categoryMap.cend()) + { + return level3CategoryDefaultAwipsId_.at(category); + } + + const auto& productsSiteHas = productsIt->second; + const auto& productList = level3CategoryProductList_.at(category); + for (auto& product : productList) + { + const auto& tiltsIt = productsSiteHas.find(product); + if (tiltsIt != productsSiteHas.cend() && tiltsIt->second.size() > 0) + { + return tiltsIt->second[0]; + } + } + return level3CategoryDefaultAwipsId_.at(category); } diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp index 1806cdfe..808ce19c 100644 --- a/wxdata/source/scwx/gr/placefile.cpp +++ b/wxdata/source/scwx/gr/placefile.cpp @@ -20,7 +20,7 @@ #include -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) # include #endif @@ -154,9 +154,17 @@ std::shared_ptr Placefile::font(std::size_t i) std::shared_ptr Placefile::Load(const std::string& filename) { + std::shared_ptr placefile = nullptr; + logger_->debug("Loading placefile: {}", filename); std::ifstream f(filename, std::ios_base::in); - return Load(filename, f); + + if (f.is_open()) + { + placefile = Load(filename, f); + } + + return placefile; } std::shared_ptr Placefile::Load(const std::string& name, @@ -284,7 +292,7 @@ void Placefile::Impl::ProcessLine(const std::string& line) { using namespace std::chrono; -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) using namespace date; #endif diff --git a/wxdata/source/scwx/network/cpr.cpp b/wxdata/source/scwx/network/cpr.cpp index 81dea5ad..0f8fb956 100644 --- a/wxdata/source/scwx/network/cpr.cpp +++ b/wxdata/source/scwx/network/cpr.cpp @@ -7,8 +7,6 @@ namespace network namespace cpr { -static const std::string logPrefix_ = "scwx::network::cpr"; - static ::cpr::Header header_ {}; ::cpr::Header GetHeader() diff --git a/wxdata/source/scwx/network/dir_list.cpp b/wxdata/source/scwx/network/dir_list.cpp index 3c6dabd4..f1be20b9 100644 --- a/wxdata/source/scwx/network/dir_list.cpp +++ b/wxdata/source/scwx/network/dir_list.cpp @@ -11,7 +11,7 @@ #include #include -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) # include #endif @@ -200,7 +200,7 @@ void DirListSAXHandler::Characters(void* userData, const xmlChar* ch, int len) { using namespace std::chrono; -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) using namespace date; #endif diff --git a/wxdata/source/scwx/network/ntp_client.cpp b/wxdata/source/scwx/network/ntp_client.cpp new file mode 100644 index 00000000..494aadde --- /dev/null +++ b/wxdata/source/scwx/network/ntp_client.cpp @@ -0,0 +1,571 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace scwx::network +{ + +static const std::string logPrefix_ = "scwx::network::ntp_client"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static constexpr std::size_t kReceiveBufferSize_ {48u}; + +// Reasonable min/max values for polling intervals. We don't want to poll too +// quickly and upset the server, but we don't want to poll too slowly in the +// event of a time jump. +static constexpr std::uint32_t kMinPollInterval_ = 6u; // 2^6 = 64 seconds +static constexpr std::uint32_t kMaxPollInterval_ = 9u; // 2^9 = 512 seconds + +class NtpTimestamp +{ +public: + // NTP epoch: January 1, 1900 + // Unix epoch: January 1, 1970 + // Difference = 70 years = 2,208,988,800 seconds + static constexpr std::uint32_t kNtpToUnixOffset_ = 2208988800UL; + + // NTP fractional part represents 1/2^32 of a second + static constexpr std::uint64_t kFractionalMultiplier_ = 0x100000000ULL; + + static constexpr std::uint64_t _1e9 = 1000000000ULL; + + std::uint32_t seconds_ {0}; + std::uint32_t fraction_ {0}; + + explicit NtpTimestamp() = default; + explicit NtpTimestamp(std::uint32_t seconds, std::uint32_t fraction) : + seconds_ {seconds}, fraction_ {fraction} + { + } + ~NtpTimestamp() = default; + + NtpTimestamp(const NtpTimestamp&) = default; + NtpTimestamp& operator=(const NtpTimestamp&) = default; + NtpTimestamp(NtpTimestamp&&) = default; + NtpTimestamp& operator=(NtpTimestamp&&) = default; + + template + [[nodiscard]] std::chrono::time_point ToTimePoint() const + { + // Convert NTP seconds to Unix seconds + // Don't cast to a larger type to account for rollover, and this should + // work until 2106 + const std::uint32_t unixSeconds = seconds_ - kNtpToUnixOffset_; + + // Convert NTP fraction to nanoseconds + const auto nanoseconds = + static_cast(fraction_) * _1e9 / kFractionalMultiplier_; + + return std::chrono::time_point( + std::chrono::duration_cast( + std::chrono::seconds {unixSeconds} + + std::chrono::nanoseconds {nanoseconds})); + } + + template + static NtpTimestamp FromTimePoint(std::chrono::time_point timePoint) + { + // Convert to duration since Unix epoch + const auto unixDuration = timePoint.time_since_epoch(); + + // Extract seconds and nanoseconds + const auto unixSeconds = + std::chrono::duration_cast(unixDuration); + const auto nanoseconds = + std::chrono::duration_cast(unixDuration - + unixSeconds); + + // Convert Unix seconds to NTP seconds + const auto ntpSeconds = + static_cast(unixSeconds.count() + kNtpToUnixOffset_); + + // Convert nanoseconds to NTP fractional seconds + const auto ntpFraction = static_cast( + nanoseconds.count() * kFractionalMultiplier_ / _1e9); + + return NtpTimestamp(ntpSeconds, ntpFraction); + } +}; + +class NtpClient::Impl +{ +public: + explicit Impl(); + ~Impl(); + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(Impl&&) = delete; + Impl& operator=(Impl&&) = delete; + + void Open(std::string_view host, std::string_view service); + void OpenCurrentServer(); + void Poll(); + void ReceivePacket(std::size_t length); + std::string RotateServer(); + void Run(); + void RunOnce(); + + void FinishInitialization(); + + boost::asio::thread_pool threadPool_ {2u}; + + boost::asio::steady_timer pollTimer_ {threadPool_}; + std::uint32_t pollInterval_ {kMinPollInterval_}; + + bool enabled_ {true}; + bool error_ {false}; + bool disableServer_ {false}; + bool rotateServer_ {false}; + + std::mutex initializationMutex_ {}; + std::condition_variable initializationCondition_ {}; + std::atomic initialized_ {false}; + + types::ntp::NtpPacket transmitPacket_ {}; + + boost::asio::ip::udp::socket socket_ {threadPool_}; + std::optional serverEndpoint_ {}; + std::array receiveBuffer_ {}; + + std::chrono::system_clock::duration timeOffset_ {}; + + const std::vector serverList_ {"time.nist.gov", + "time.cloudflare.com", + "pool.ntp.org", + "time.aws.com", + "time.windows.com", + "time.apple.com"}; + std::vector disabledServers_ {}; + + std::vector::const_iterator currentServer_ = + serverList_.begin(); +}; + +NtpClient::NtpClient() : p(std::make_unique()) {} +NtpClient::~NtpClient() = default; + +NtpClient::NtpClient(NtpClient&&) noexcept = default; +NtpClient& NtpClient::operator=(NtpClient&&) noexcept = default; + +NtpClient::Impl::Impl() +{ + using namespace std::chrono_literals; + + const auto now = + std::chrono::floor(std::chrono::system_clock::now()); + + // The NTP timestamp will overflow in 2036. Overflow is handled in such a way + // that dates prior to 1970 result in a Unix timestamp after 2036. Additional + // handling for the year 2106 and subsequent eras is required. + static constexpr auto kMaxYear_ = 2106y; + + enabled_ = now < kMaxYear_ / 1 / 1; + + transmitPacket_.fields.vn = 3; // Version + transmitPacket_.fields.mode = 3; // Client (3) + + // If the NTP client is enabled, wait until the first refresh to consider + // "initialized". Otherwise, mark as initialized immediately to prevent a + // deadlock. + if (!enabled_) + { + initialized_ = true; + } +} + +NtpClient::Impl::~Impl() +{ + threadPool_.join(); +} + +bool NtpClient::error() +{ + const bool returnValue = p->error_; + p->error_ = false; + return returnValue; +} + +std::chrono::system_clock::duration NtpClient::time_offset() const +{ + return p->timeOffset_; +} + +void NtpClient::Start() +{ + if (p->enabled_) + { + boost::asio::post(p->threadPool_, [this]() { p->Run(); }); + } +} + +void NtpClient::Stop() +{ + p->enabled_ = false; + p->socket_.cancel(); + p->pollTimer_.cancel(); + p->threadPool_.join(); +} + +void NtpClient::Open(std::string_view host, std::string_view service) +{ + p->Open(host, service); +} + +void NtpClient::OpenCurrentServer() +{ + p->OpenCurrentServer(); +} + +void NtpClient::Poll() +{ + p->Poll(); +} + +std::string NtpClient::RotateServer() +{ + return p->RotateServer(); +} + +void NtpClient::RunOnce() +{ + p->RunOnce(); +} + +void NtpClient::Impl::Open(std::string_view host, std::string_view service) +{ + boost::asio::ip::udp::resolver resolver(threadPool_); + boost::system::error_code ec; + + auto results = resolver.resolve(host, service, ec); + if (ec.value() == boost::system::errc::success && !results.empty()) + { + logger_->info("Using NTP server: {}", host); + serverEndpoint_ = *results.begin(); + socket_.open(serverEndpoint_->protocol()); + } + else + { + serverEndpoint_ = std::nullopt; + logger_->warn("Could not resolve host {}: {}", host, ec.message()); + rotateServer_ = true; + } +} + +void NtpClient::Impl::OpenCurrentServer() +{ + Open(*currentServer_, "123"); +} + +void NtpClient::Impl::Poll() +{ + using namespace std::chrono_literals; + + static constexpr auto kTimeout_ = 5s; + + if (!serverEndpoint_.has_value()) + { + logger_->error("Server endpoint not set"); + error_ = true; + return; + } + + try + { + const auto originTimestamp = + NtpTimestamp::FromTimePoint(std::chrono::system_clock::now()); + transmitPacket_.txTm_s = ntohl(originTimestamp.seconds_); + transmitPacket_.txTm_f = ntohl(originTimestamp.fraction_); + + const std::size_t transmitPacketSize = sizeof(transmitPacket_); + // Send NTP request + socket_.send_to(boost::asio::buffer(&transmitPacket_, transmitPacketSize), + *serverEndpoint_); + + // Receive NTP response + auto future = + socket_.async_receive_from(boost::asio::buffer(receiveBuffer_), + *serverEndpoint_, + boost::asio::use_future); + std::size_t bytesReceived = 0; + + switch (future.wait_for(kTimeout_)) + { + case std::future_status::ready: + bytesReceived = future.get(); + ReceivePacket(bytesReceived); + break; + + case std::future_status::timeout: + case std::future_status::deferred: + logger_->warn("Timeout waiting for NTP response"); + socket_.cancel(); + error_ = true; + break; + } + } + catch (const std::exception& ex) + { + logger_->error("Error polling: {}", ex.what()); + error_ = true; + } +} + +void NtpClient::Impl::ReceivePacket(std::size_t length) +{ + if (length >= sizeof(types::ntp::NtpPacket)) + { + const auto destinationTime = std::chrono::system_clock::now(); + + const auto packet = types::ntp::NtpPacket::Parse(receiveBuffer_); + + if (packet.stratum == 0) + { + const std::uint32_t refId = ntohl(packet.refId); + const std::string kod = + std::string(reinterpret_cast(&refId), 4); + + logger_->warn("KoD packet received: {}", kod); + + if (kod == "DENY" || kod == "RSTR") + { + // The client MUST demobilize any associations to that server and + // stop sending packets to that server + disableServer_ = true; + } + else if (kod == "RATE") + { + // The client MUST immediately reduce its polling interval to that + // server and continue to reduce it each time it receives a RATE + // kiss code + if (pollInterval_ < kMaxPollInterval_) + { + ++pollInterval_; + } + else + { + // The server wants us to reduce the polling interval lower than + // what we deem useful. Move to the next server. + rotateServer_ = true; + } + } + + // Consider a KoD packet an error + error_ = true; + } + else + { + const auto originTimestamp = + NtpTimestamp(packet.origTm_s, packet.origTm_f); + const auto receiveTimestamp = + NtpTimestamp(packet.rxTm_s, packet.rxTm_f); + const auto transmitTimestamp = + NtpTimestamp(packet.txTm_s, packet.txTm_f); + + const auto originTime = originTimestamp.ToTimePoint(); + const auto receiveTime = receiveTimestamp.ToTimePoint(); + const auto transmitTime = transmitTimestamp.ToTimePoint(); + + const auto& t0 = originTime; + const auto& t1 = receiveTime; + const auto& t2 = transmitTime; + const auto& t3 = destinationTime; + + // Update time offset + timeOffset_ = ((t1 - t0) + (t2 - t3)) / 2; + + logger_->debug("Time offset updated: {:%jd %T}", timeOffset_); + } + } + else + { + logger_->warn("Received too few bytes: {}", length); + error_ = true; + } +} + +std::string NtpClient::Impl::RotateServer() +{ + socket_.close(); + + bool newServerFound = false; + + // Save the current server + const auto oldServer = currentServer_; + + while (!newServerFound) + { + // Increment the current server + ++currentServer_; + + // If we are at the end of the list, start over at the beginning + if (currentServer_ == serverList_.end()) + { + currentServer_ = serverList_.begin(); + } + + // If we have reached the end of the list, give up + if (currentServer_ == oldServer) + { + enabled_ = false; + break; + } + + // If the current server is disabled, continue searching + while (std::find(disabledServers_.cbegin(), + disabledServers_.cend(), + *currentServer_) != disabledServers_.cend()) + { + continue; + } + + // A new server has been found + newServerFound = true; + } + + pollInterval_ = kMinPollInterval_; + rotateServer_ = false; + + return *currentServer_; +} + +void NtpClient::Impl::Run() +{ + RunOnce(); + + if (enabled_) + { + const std::chrono::seconds pollIntervalSeconds {1u << pollInterval_}; + pollTimer_.expires_after(pollIntervalSeconds); + pollTimer_.async_wait( + [this](const boost::system::error_code& e) + { + if (e == boost::asio::error::operation_aborted) + { + logger_->debug("Poll timer cancelled"); + } + else if (e != boost::system::errc::success) + { + logger_->warn("Poll timer error: {}", e.message()); + } + else + { + try + { + Run(); + } + catch (const std::exception& ex) + { + logger_->error(ex.what()); + } + } + }); + } +} + +void NtpClient::Impl::RunOnce() +{ + if (disableServer_) + { + // Disable the current server + disabledServers_.push_back(*currentServer_); + + // Disable the NTP client if all servers are disabled + enabled_ = disabledServers_.size() == serverList_.size(); + + if (!enabled_) + { + error_ = true; + } + + disableServer_ = false; + rotateServer_ = enabled_; + } + + if (!enabled_ && socket_.is_open()) + { + // Sockets should be closed if the client is disabled + socket_.close(); + } + + if (rotateServer_) + { + // Rotate the server if requested + RotateServer(); + } + + if (enabled_ && !socket_.is_open()) + { + // Open the current server if it is not open + OpenCurrentServer(); + } + + if (socket_.is_open()) + { + // Send an NTP message to determine the current time offset + Poll(); + } + else if (enabled_) + { + // Did not poll this frame + error_ = true; + } + + FinishInitialization(); +} + +void NtpClient::Impl::FinishInitialization() +{ + if (!initialized_) + { + // Set initialized to true + std::unique_lock lock(initializationMutex_); + initialized_ = true; + lock.unlock(); + + // Notify any threads waiting for initialization + initializationCondition_.notify_all(); + } +} + +void NtpClient::WaitForInitialOffset() +{ + std::unique_lock lock(p->initializationMutex_); + + // While not yet initialized + while (!p->initialized_) + { + // Wait for initialization + p->initializationCondition_.wait(lock); + } +} + +std::shared_ptr NtpClient::Instance() +{ + static std::weak_ptr ntpClientReference_ {}; + static std::mutex instanceMutex_ {}; + + const std::unique_lock lock(instanceMutex_); + + std::shared_ptr ntpClient = ntpClientReference_.lock(); + + if (ntpClient == nullptr) + { + ntpClient = std::make_shared(); + ntpClientReference_ = ntpClient; + } + + return ntpClient; +} + +} // namespace scwx::network diff --git a/wxdata/source/scwx/provider/aws_level2_chunks_data_provider.cpp b/wxdata/source/scwx/provider/aws_level2_chunks_data_provider.cpp new file mode 100644 index 00000000..f7de36ca --- /dev/null +++ b/wxdata/source/scwx/provider/aws_level2_chunks_data_provider.cpp @@ -0,0 +1,782 @@ +#include "scwx/wsr88d/rda/digital_radar_data.hpp" +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +// Avoid circular refrence errors in boost +// NOLINTBEGIN(misc-header-include-cycle) +#if defined(_MSC_VER) +# pragma warning(push, 0) +#endif + +#include +#include +#include + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif +// NOLINTEND(misc-header-include-cycle) + +#if (__cpp_lib_chrono < 201907L) +# include +#endif + +namespace scwx::provider +{ +static const std::string logPrefix_ = + "scwx::provider::aws_level2_chunks_data_provider"; +static const auto logger_ = util::Logger::Create(logPrefix_); + +static const std::string kDefaultBucketName_ = "unidata-nexrad-level2-chunks"; +static const std::string kDefaultRegion_ = "us-east-1"; + +class AwsLevel2ChunksDataProvider::Impl +{ +public: + struct ScanRecord + { + explicit ScanRecord(std::string prefix, bool valid = true) : + valid_ {valid}, + prefix_ {std::move(prefix)}, + nexradFile_ {}, + lastModified_ {}, + lastKey_ {""} + { + } + ~ScanRecord() = default; + ScanRecord(const ScanRecord&) = default; + ScanRecord(ScanRecord&&) = default; + ScanRecord& operator=(const ScanRecord&) = default; + ScanRecord& operator=(ScanRecord&&) = default; + + bool valid_; + std::string prefix_; + std::shared_ptr nexradFile_; + std::chrono::system_clock::time_point time_; + std::chrono::system_clock::time_point lastModified_; + std::chrono::system_clock::time_point secondLastModified_; + std::string lastKey_; + int nextFile_ {1}; + bool hasAllFiles_ {false}; + }; + + explicit Impl(AwsLevel2ChunksDataProvider* self, + std::string radarSite, + std::string bucketName, + std::string region) : + radarSite_ {std::move(radarSite)}, + bucketName_ {std::move(bucketName)}, + region_ {std::move(region)}, + client_ {nullptr}, + scanTimes_ {}, + lastScan_ {"", false}, + currentScan_ {"", false}, + scansMutex_ {}, + lastTimeListed_ {}, + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) about average + updatePeriod_ {7}, + level2DataProvider_ {}, + self_ {self} + { + // Disable HTTP request for region + util::SetEnvironment("AWS_EC2_METADATA_DISABLED", "true"); + + // Use anonymous credentials + const Aws::Auth::AWSCredentials credentials {}; + + Aws::Client::ClientConfiguration config; + config.region = region_; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) arbitrary + config.connectTimeoutMs = 10000; + + client_ = std::make_shared( + credentials, + Aws::MakeShared( + Aws::S3::S3Client::GetAllocationTag()), + config); + } + ~Impl() = default; + Impl(const Impl&) = delete; + Impl(Impl&&) = delete; + Impl& operator=(const Impl&) = delete; + Impl& operator=(Impl&&) = delete; + + std::chrono::system_clock::time_point GetScanTime(const std::string& prefix); + int GetScanNumber(const std::string& prefix); + + bool LoadScan(Impl::ScanRecord& scanRecord); + std::tuple ListObjects(); + + std::string radarSite_; + std::string bucketName_; + std::string region_; + std::shared_ptr client_; + + std::mutex refreshMutex_; + + std::unordered_map + scanTimes_; + ScanRecord lastScan_; + ScanRecord currentScan_; + std::shared_mutex scansMutex_; + std::chrono::system_clock::time_point lastTimeListed_; + + std::chrono::seconds updatePeriod_; + + std::weak_ptr level2DataProvider_; + + AwsLevel2ChunksDataProvider* self_; +}; + +AwsLevel2ChunksDataProvider::AwsLevel2ChunksDataProvider( + const std::string& radarSite) : + AwsLevel2ChunksDataProvider(radarSite, kDefaultBucketName_, kDefaultRegion_) +{ +} + +AwsLevel2ChunksDataProvider::AwsLevel2ChunksDataProvider( + const std::string& radarSite, + const std::string& bucketName, + const std::string& region) : + p(std::make_unique(this, radarSite, bucketName, region)) +{ +} + +AwsLevel2ChunksDataProvider::~AwsLevel2ChunksDataProvider() = default; + +std::chrono::system_clock::time_point +AwsLevel2ChunksDataProvider::GetTimePointByKey(const std::string& key) const +{ + std::chrono::system_clock::time_point time {}; + + const size_t lastSeparator = key.rfind('/'); + const size_t offset = + (lastSeparator == std::string::npos) ? 0 : lastSeparator + 1; + + // Filename format is YYYYMMDD-TTTTTT-AAA-B + static const size_t formatSize = std::string("YYYYMMDD-TTTTTT").size(); + + if (key.size() >= offset + formatSize) + { + using namespace std::chrono; + +#if (__cpp_lib_chrono < 201907L) + using namespace date; +#endif + + static const std::string timeFormat {"%Y%m%d-%H%M%S"}; + + std::string timeStr {key.substr(offset, formatSize)}; + std::istringstream in {timeStr}; + in >> parse(timeFormat, time); + + if (in.fail()) + { + logger_->warn("Invalid time: \"{}\"", timeStr); + } + } + else + { + logger_->warn("Time not parsable from key: \"{}\"", key); + } + + return time; +} + +size_t AwsLevel2ChunksDataProvider::cache_size() const +{ + return 2; +} + +std::chrono::system_clock::time_point +AwsLevel2ChunksDataProvider::last_modified() const +{ + // There is a slight delay between the "modified time" and when it is + // actually available. Radar product manager uses this as available time + static const auto extra = std::chrono::seconds(2); + + const std::shared_lock lock(p->scansMutex_); + if (p->currentScan_.valid_ && p->currentScan_.lastModified_ != + std::chrono::system_clock::time_point {}) + { + return p->currentScan_.lastModified_ + extra; + } + else if (p->lastScan_.valid_ && p->lastScan_.lastModified_ != + std::chrono::system_clock::time_point {}) + { + return p->lastScan_.lastModified_ + extra; + } + else + { + return {}; + } +} +std::chrono::seconds AwsLevel2ChunksDataProvider::update_period() const +{ + const std::shared_lock lock(p->scansMutex_); + // get update period from time between chunks + if (p->currentScan_.valid_ && p->currentScan_.nextFile_ > 2) + { + auto delta = + p->currentScan_.lastModified_ - p->currentScan_.secondLastModified_; + return std::chrono::duration_cast(delta); + } + else if (p->lastScan_.valid_ && p->lastScan_.nextFile_ > 2) + { + auto delta = + p->lastScan_.lastModified_ - p->lastScan_.secondLastModified_; + return std::chrono::duration_cast(delta); + } + + // default to a set update period + return p->updatePeriod_; +} + +std::string +AwsLevel2ChunksDataProvider::FindKey(std::chrono::system_clock::time_point time) +{ + logger_->debug("FindKey: {}", util::TimeString(time)); + + const std::shared_lock lock(p->scansMutex_); + if (p->currentScan_.valid_ && time >= p->currentScan_.time_) + { + return p->currentScan_.prefix_; + } + else if (p->lastScan_.valid_ && time >= p->lastScan_.time_) + { + return p->lastScan_.prefix_; + } + + return {}; +} + +std::string AwsLevel2ChunksDataProvider::FindLatestKey() +{ + const std::shared_lock lock(p->scansMutex_); + if (!p->currentScan_.valid_) + { + return ""; + } + + return p->currentScan_.prefix_; +} + +std::chrono::system_clock::time_point +AwsLevel2ChunksDataProvider::FindLatestTime() +{ + const std::shared_lock lock(p->scansMutex_); + if (!p->currentScan_.valid_) + { + return {}; + } + + return p->currentScan_.time_; +} + +std::vector +AwsLevel2ChunksDataProvider::GetTimePointsByDate( + std::chrono::system_clock::time_point /*date*/) +{ + return {}; +} + +std::chrono::system_clock::time_point +AwsLevel2ChunksDataProvider::Impl::GetScanTime(const std::string& prefix) +{ + using namespace std::chrono; + const auto& scanTimeIt = scanTimes_.find(prefix); // O(log(n)) + if (scanTimeIt != scanTimes_.cend()) + { + // If the time is greater than 2 hours ago, it may be a new scan + auto replaceBy = util::time::now() - hours {2}; + if (scanTimeIt->second > replaceBy) + { + return scanTimeIt->second; + } + } + + Aws::S3::Model::ListObjectsV2Request request; + request.SetBucket(bucketName_); + request.SetPrefix(prefix); + request.SetDelimiter("/"); + request.SetMaxKeys(1); + + auto outcome = client_->ListObjectsV2(request); + if (outcome.IsSuccess()) + { + auto timePoint = self_->GetTimePointByKey( + outcome.GetResult().GetContents().at(0).GetKey()); + scanTimes_.insert_or_assign(prefix, timePoint); + return timePoint; + } + + return {}; +} + +std::tuple +AwsLevel2ChunksDataProvider::Impl::ListObjects() +{ + size_t newObjects = 0; + const size_t totalObjects = 0; + + const std::chrono::system_clock::time_point now = util::time::now(); + + if (currentScan_.valid_ && !currentScan_.hasAllFiles_ && + lastTimeListed_ + std::chrono::minutes(2) > now) + { + return {true, newObjects, totalObjects}; + } + logger_->trace("ListObjects"); + lastTimeListed_ = now; + + const std::string prefix = radarSite_ + "/"; + + Aws::S3::Model::ListObjectsV2Request request; + request.SetBucket(bucketName_); + request.SetPrefix(prefix); + request.SetDelimiter("/"); + + auto outcome = client_->ListObjectsV2(request); + + if (outcome.IsSuccess()) + { + auto& scans = outcome.GetResult().GetCommonPrefixes(); + logger_->trace("Found {} scans", scans.size()); + + if (scans.size() > 0) + { + // find latest scan + auto scanNumberMap = std::map(); + + for (auto& scan : scans) // O(n log(n)) n <= 999 + { + const std::string& scanPrefix = scan.GetPrefix(); + scanNumberMap.insert_or_assign(GetScanNumber(scanPrefix), + scanPrefix); + } + + // Start with last scan + int previousScanNumber = scanNumberMap.crbegin()->first; + const int firstScanNumber = scanNumberMap.cbegin()->first; + + // Look for a gap in scan numbers. This indicates that is the latest + // scan. + + auto possibleLastNumbers = std::unordered_set(); + // This indicates that highest number scan may be the last scan + // (including if there is only 1 scan) + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) + if (previousScanNumber != 999 || firstScanNumber != 1) + { + possibleLastNumbers.emplace(previousScanNumber); + } + // Have already checked scan with highest number, so skip first + previousScanNumber = firstScanNumber; + bool first = true; + for (const auto& scan : scanNumberMap) + { + if (first) + { + first = false; + continue; + } + if (scan.first != previousScanNumber + 1) + { + possibleLastNumbers.emplace(previousScanNumber); + } + previousScanNumber = scan.first; + } + + if (possibleLastNumbers.empty()) + { + logger_->warn("Could not find last scan"); + return {false, 0, 0}; + } + + int lastScanNumber = -1; + std::chrono::system_clock::time_point lastScanTime = {}; + std::string lastScanPrefix; + + for (const int scanNumber : possibleLastNumbers) + { + const std::string& scanPrefix = scanNumberMap.at(scanNumber); + auto scanTime = GetScanTime(scanPrefix); + if (scanTime > lastScanTime) + { + lastScanTime = scanTime; + lastScanPrefix = scanPrefix; + lastScanNumber = scanNumber; + } + } + + const int secondLastScanNumber = + // 999 is the last file possible + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) + lastScanNumber == 1 ? 999 : lastScanNumber - 1; + + const auto& secondLastScanPrefix = + scanNumberMap.find(secondLastScanNumber); + + if (!currentScan_.valid_ || currentScan_.prefix_ != lastScanPrefix) + { + if (currentScan_.valid_ && + (secondLastScanPrefix == scanNumberMap.cend() || + currentScan_.prefix_ == secondLastScanPrefix->second)) + { + lastScan_ = currentScan_; + } + else if (secondLastScanPrefix != scanNumberMap.cend()) + { + lastScan_.valid_ = true; + lastScan_.prefix_ = secondLastScanPrefix->second; + lastScan_.nexradFile_ = nullptr; + lastScan_.time_ = GetScanTime(secondLastScanPrefix->second); + lastScan_.lastModified_ = {}; + lastScan_.secondLastModified_ = {}; + lastScan_.lastKey_ = ""; + lastScan_.nextFile_ = 1; + lastScan_.hasAllFiles_ = false; + newObjects += 1; + } + + currentScan_.valid_ = true; + currentScan_.prefix_ = lastScanPrefix; + currentScan_.nexradFile_ = nullptr; + currentScan_.time_ = lastScanTime; + currentScan_.lastModified_ = {}; + currentScan_.secondLastModified_ = {}; + currentScan_.lastKey_ = ""; + currentScan_.nextFile_ = 1; + currentScan_.hasAllFiles_ = false; + newObjects += 1; + } + } + } + + return {true, newObjects, totalObjects}; +} + +std::tuple +AwsLevel2ChunksDataProvider::ListObjects(std::chrono::system_clock::time_point) +{ + return {true, 0, 0}; +} + +std::shared_ptr +AwsLevel2ChunksDataProvider::LoadObjectByKey(const std::string& /*prefix*/) +{ + return nullptr; +} + +bool AwsLevel2ChunksDataProvider::Impl::LoadScan(Impl::ScanRecord& scanRecord) +{ + if (!scanRecord.valid_) + { + logger_->warn("Tried to load scan which was not listed yet"); + return false; + } + else if (scanRecord.hasAllFiles_) + { + return false; + } + + Aws::S3::Model::ListObjectsV2Request listRequest; + listRequest.SetBucket(bucketName_); + listRequest.SetPrefix(scanRecord.prefix_); + listRequest.SetDelimiter("/"); + if (!scanRecord.lastKey_.empty()) + { + listRequest.SetStartAfter(scanRecord.lastKey_); + } + + auto listOutcome = client_->ListObjectsV2(listRequest); + if (!listOutcome.IsSuccess()) + { + logger_->warn("Could not find scan at {}", scanRecord.prefix_); + return false; + } + + bool hasNew = false; + auto& chunks = listOutcome.GetResult().GetContents(); + logger_->trace("Found {} new chunks.", chunks.size()); + for (const auto& chunk : chunks) + { + const std::string& key = chunk.GetKey(); + + // KIND/585/20250324-134727-001-S + // KIND/5/20250324-134727-001-S + static const size_t firstSlash = std::string("KIND/").size(); + const size_t secondSlash = key.find('/', firstSlash); + static const size_t startNumberPosOffset = + std::string("/20250324-134727-").size(); + const size_t startNumberPos = secondSlash + startNumberPosOffset; + const std::string& keyNumberStr = key.substr(startNumberPos, 3); + const int keyNumber = std::stoi(keyNumberStr); + // As far as order goes, only the first one matters. This may cause some + // issues if keys come in out of order, but usually they just skip chunks + if (scanRecord.nextFile_ == 1 && keyNumber != scanRecord.nextFile_) + { + logger_->warn("Chunk found that was not in order {} {}", + scanRecord.lastKey_, + key); + continue; + } + + // Now we want the ending char + // KIND/585/20250324-134727-001-S + static const size_t charPos = std::string("/20250324-134727-001-").size(); + if (secondSlash + charPos >= key.size()) + { + logger_->warn("Chunk key was not long enough"); + continue; + } + const char keyChar = key[secondSlash + charPos]; + + Aws::S3::Model::GetObjectRequest objectRequest; + objectRequest.SetBucket(bucketName_); + objectRequest.SetKey(key); + + auto outcome = client_->GetObject(objectRequest); + + if (!outcome.IsSuccess()) + { + logger_->warn("Could not get object: {}", + outcome.GetError().GetMessage()); + return hasNew; + } + + auto& body = outcome.GetResultWithOwnership().GetBody(); + + switch (keyChar) + { + case 'S': + { // First chunk + scanRecord.nexradFile_ = std::make_shared(); + if (!scanRecord.nexradFile_->LoadData(body)) + { + logger_->warn("Failed to load first chunk"); + return hasNew; + } + break; + } + case 'I': + { // Middle chunk + if (!scanRecord.nexradFile_->LoadLDMRecords(body)) + { + logger_->warn("Failed to load middle chunk"); + return hasNew; + } + break; + } + case 'E': + { // Last chunk + if (!scanRecord.nexradFile_->LoadLDMRecords(body)) + { + logger_->warn("Failed to load last chunk"); + return hasNew; + } + scanRecord.hasAllFiles_ = true; + break; + } + default: + logger_->warn("Could not load chunk with unknown char"); + return hasNew; + } + hasNew = true; + + const std::chrono::seconds lastModifiedSeconds { + outcome.GetResult().GetLastModified().Seconds()}; + const std::chrono::system_clock::time_point lastModified { + lastModifiedSeconds}; + + scanRecord.secondLastModified_ = scanRecord.lastModified_; + scanRecord.lastModified_ = lastModified; + + scanRecord.nextFile_ = keyNumber + 1; + scanRecord.lastKey_ = key; + } + + if (scanRecord.nexradFile_ == nullptr) + { + logger_->warn("Could not load file"); + } + else if (hasNew) + { + scanRecord.nexradFile_->IndexFile(); + } + + return hasNew; +} + +std::shared_ptr +AwsLevel2ChunksDataProvider::LoadObjectByTime( + std::chrono::system_clock::time_point time) +{ + const std::unique_lock lock(p->scansMutex_); + static const std::chrono::system_clock::time_point epoch {}; + + if (p->currentScan_.valid_ && + (time == epoch || time >= p->currentScan_.time_)) + { + return std::make_shared(p->currentScan_.nexradFile_, + p->lastScan_.nexradFile_); + } + else if (p->lastScan_.valid_ && time >= p->lastScan_.time_) + { + return p->lastScan_.nexradFile_; + } + else + { + return nullptr; + } +} + +int AwsLevel2ChunksDataProvider::Impl::GetScanNumber(const std::string& prefix) +{ + // KIND/585/20250324-134727-001-S + static const size_t firstSlash = std::string("KIND/").size(); + const std::string& prefixNumberStr = prefix.substr(firstSlash, 3); + return std::stoi(prefixNumberStr); +} + +std::pair AwsLevel2ChunksDataProvider::Refresh() +{ + using namespace std::chrono; + + boost::timer::cpu_timer timer {}; + timer.start(); + + const std::unique_lock lock(p->refreshMutex_); + const std::unique_lock scanLock(p->scansMutex_); + + auto [success, newObjects, totalObjects] = p->ListObjects(); + + auto threadPool = boost::asio::thread_pool(3); + bool newCurrent = false; + bool newLast = false; + if (p->currentScan_.valid_) + { + boost::asio::post(threadPool, + [this, &newCurrent]() + { newCurrent = p->LoadScan(p->currentScan_); }); + totalObjects += 1; + } + + if (p->lastScan_.valid_) + { + totalObjects += 1; + boost::asio::post( + threadPool, + [this, &newLast]() + { + if (!p->lastScan_.hasAllFiles_) + { + // If we have chunks, use chunks + if (p->lastScan_.nextFile_ != 1) + { + newLast = p->LoadScan(p->lastScan_); + } + else + { + auto level2DataProvider = p->level2DataProvider_.lock(); + if (level2DataProvider != nullptr) + { + level2DataProvider->ListObjects(p->lastScan_.time_); + p->lastScan_.nexradFile_ = + std::dynamic_pointer_cast( + level2DataProvider->LoadObjectByTime( + p->lastScan_.time_)); + if (p->lastScan_.nexradFile_ != nullptr) + { + p->lastScan_.hasAllFiles_ = true; + } + } + // Fall back to chunks if files did not load + newLast = p->lastScan_.nexradFile_ != nullptr || + p->LoadScan(p->lastScan_); + } + } + }); + } + + threadPool.join(); + if (newCurrent) + { + newObjects += 1; + } + if (newLast) + { + newObjects += 1; + } + + timer.stop(); + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) format to 6 digits + logger_->debug("Refresh() in {}", timer.format(6, "%ws")); + return std::make_pair(newObjects, totalObjects); +} + +void AwsLevel2ChunksDataProvider::RequestAvailableProducts() {} +std::vector AwsLevel2ChunksDataProvider::GetAvailableProducts() +{ + return {}; +} + +AwsLevel2ChunksDataProvider::AwsLevel2ChunksDataProvider( + AwsLevel2ChunksDataProvider&&) noexcept = default; +AwsLevel2ChunksDataProvider& AwsLevel2ChunksDataProvider::operator=( + AwsLevel2ChunksDataProvider&&) noexcept = default; + +std::optional AwsLevel2ChunksDataProvider::GetCurrentElevation() +{ + if (!p->currentScan_.valid_ || p->currentScan_.nexradFile_ == nullptr) + { + // Does not have any scan elevation. + return {}; + } + + auto vcpData = p->currentScan_.nexradFile_->vcp_data(); + auto radarData = p->currentScan_.nexradFile_->radar_data(); + if (radarData.size() == 0) + { + // Does not have any scan elevation. + return {}; + } + + const auto& lastElevation = radarData.crbegin(); + const std::shared_ptr digitalRadarData0 = + std::dynamic_pointer_cast( + lastElevation->second->cbegin()->second); + + if (vcpData != nullptr) + { + return static_cast(vcpData->elevation_angle(lastElevation->first)); + } + else if (digitalRadarData0 != nullptr) + { + return digitalRadarData0->elevation_angle().value(); + } + + return {}; +} + +void AwsLevel2ChunksDataProvider::SetLevel2DataProvider( + const std::shared_ptr& provider) +{ + p->level2DataProvider_ = provider; +} + +} // namespace scwx::provider diff --git a/wxdata/source/scwx/provider/aws_level2_data_provider.cpp b/wxdata/source/scwx/provider/aws_level2_data_provider.cpp index 6ac939c0..c2910773 100644 --- a/wxdata/source/scwx/provider/aws_level2_data_provider.cpp +++ b/wxdata/source/scwx/provider/aws_level2_data_provider.cpp @@ -5,7 +5,7 @@ #include #include -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) # include #endif @@ -82,7 +82,7 @@ AwsLevel2DataProvider::GetTimePointFromKey(const std::string& key) { using namespace std::chrono; -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) using namespace date; #endif diff --git a/wxdata/source/scwx/provider/aws_level3_data_provider.cpp b/wxdata/source/scwx/provider/aws_level3_data_provider.cpp index 901eb41d..5bae6843 100644 --- a/wxdata/source/scwx/provider/aws_level3_data_provider.cpp +++ b/wxdata/source/scwx/provider/aws_level3_data_provider.cpp @@ -12,7 +12,7 @@ #include #include -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) # include #endif @@ -110,7 +110,7 @@ AwsLevel3DataProvider::GetTimePointFromKey(const std::string& key) { using namespace std::chrono; -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) using namespace date; #endif diff --git a/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp b/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp index 4e139bac..97528a9e 100644 --- a/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp +++ b/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp @@ -170,6 +170,11 @@ std::string AwsNexradDataProvider::FindLatestKey() return key; } +std::chrono::system_clock::time_point AwsNexradDataProvider::FindLatestTime() +{ + return GetTimePointByKey(FindLatestKey()); +} + std::vector AwsNexradDataProvider::GetTimePointsByDate( std::chrono::system_clock::time_point date) @@ -183,6 +188,7 @@ AwsNexradDataProvider::GetTimePointsByDate( std::shared_lock lock(p->objectsMutex_); // Is the date present in the date list? + bool currentDatePresent; auto currentDateIterator = std::find(p->objectDates_.cbegin(), p->objectDates_.cend(), day); if (currentDateIterator == p->objectDates_.cend()) @@ -199,6 +205,12 @@ AwsNexradDataProvider::GetTimePointsByDate( // Re-lock mutex lock.lock(); + + currentDatePresent = false; + } + else + { + currentDatePresent = true; } // Determine objects to retrieve @@ -216,7 +228,7 @@ AwsNexradDataProvider::GetTimePointsByDate( // If we haven't updated the most recently queried dates yet, because the // date was already cached, update - if (currentDateIterator != p->objectDates_.cend()) + if (currentDatePresent) { p->UpdateObjectDates(date); } @@ -320,13 +332,27 @@ AwsNexradDataProvider::LoadObjectByKey(const std::string& key) return nexradFile; } +std::shared_ptr AwsNexradDataProvider::LoadObjectByTime( + std::chrono::system_clock::time_point time) +{ + const std::string key = FindKey(time); + if (key.empty()) + { + return nullptr; + } + else + { + return LoadObjectByKey(key); + } +} + std::pair AwsNexradDataProvider::Refresh() { using namespace std::chrono; logger_->debug("Refresh()"); - auto today = floor(system_clock::now()); + auto today = floor(util::time::now()); auto yesterday = today - days {1}; std::unique_lock lock(p->refreshMutex_); @@ -362,7 +388,7 @@ void AwsNexradDataProvider::Impl::PruneObjects() { using namespace std::chrono; - auto today = floor(system_clock::now()); + auto today = floor(util::time::now()); auto yesterday = today - days {1}; std::unique_lock lock(objectsMutex_); diff --git a/wxdata/source/scwx/provider/iem_api_provider.cpp b/wxdata/source/scwx/provider/iem_api_provider.cpp new file mode 100644 index 00000000..ef0576e7 --- /dev/null +++ b/wxdata/source/scwx/provider/iem_api_provider.cpp @@ -0,0 +1,185 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#if (__cpp_lib_chrono < 201907L) +# include +#endif + +namespace scwx::provider +{ + +static const std::string logPrefix_ = "scwx::provider::iem_api_provider"; + +const std::shared_ptr IemApiProvider::logger_ = + util::Logger::Create(logPrefix_); + +const std::string IemApiProvider::kBaseUrl_ = + "https://mesonet.agron.iastate.edu/api/1"; + +const std::string IemApiProvider::kListNwsTextProductsEndpoint_ = + "/nws/afos/list.json"; +const std::string IemApiProvider::kNwsTextProductEndpoint_ = "/nwstext/"; + +class IemApiProvider::Impl +{ +public: + explicit Impl() = default; + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; +}; + +IemApiProvider::IemApiProvider() : p(std::make_unique()) {} +IemApiProvider::~IemApiProvider() = default; + +IemApiProvider::IemApiProvider(IemApiProvider&&) noexcept = default; +IemApiProvider& IemApiProvider::operator=(IemApiProvider&&) noexcept = default; + +boost::outcome_v2::result> +IemApiProvider::ListTextProducts(std::chrono::sys_days date, + std::optional optionalCccc, + std::optional optionalPil) +{ + const std::string_view cccc = + optionalCccc.has_value() ? optionalCccc.value() : std::string_view {}; + const std::string_view pil = + optionalPil.has_value() ? optionalPil.value() : std::string_view {}; + + const auto dateArray = std::array {date}; + const auto ccccArray = std::array {cccc}; + const auto pilArray = std::array {pil}; + + return ListTextProducts(dateArray, ccccArray, pilArray); +} + +boost::outcome_v2::result> +IemApiProvider::ProcessTextProductLists( + std::vector& asyncResponses) +{ + std::vector textProducts {}; + + for (auto& asyncResponse : asyncResponses) + { + auto response = asyncResponse.get(); + + const boost::json::value json = util::json::ReadJsonString(response.text); + + if (response.status_code == cpr::status::HTTP_OK) + { + try + { + // Get AFOS list from response + auto entries = boost::json::value_to(json); + textProducts.insert(textProducts.end(), + std::make_move_iterator(entries.data_.begin()), + std::make_move_iterator(entries.data_.end())); + } + catch (const std::exception& ex) + { + // Unexpected bad response + logger_->warn("Error parsing JSON: {}", ex.what()); + return boost::system::errc::make_error_code( + boost::system::errc::bad_message); + } + } + else if (response.status_code == cpr::status::HTTP_BAD_REQUEST && + json != nullptr) + { + try + { + // Log bad request details + auto badRequest = + boost::json::value_to(json); + logger_->warn("ListTextProducts bad request: {}", + badRequest.detail_); + } + catch (const std::exception& ex) + { + // Unexpected bad response + logger_->warn("Error parsing bad response: {}", ex.what()); + } + + return boost::system::errc::make_error_code( + boost::system::errc::invalid_argument); + } + else if (response.status_code == cpr::status::HTTP_UNPROCESSABLE_ENTITY && + json != nullptr) + { + try + { + // Log validation error details + auto error = + boost::json::value_to(json); + logger_->warn("ListTextProducts validation error: {}", + error.detail_.at(0).msg_); + } + catch (const std::exception& ex) + { + // Unexpected bad response + logger_->warn("Error parsing validation error: {}", ex.what()); + } + + return boost::system::errc::make_error_code( + boost::system::errc::no_message_available); + } + else + { + logger_->warn("Could not list text products: {}", + response.status_line); + + return boost::system::errc::make_error_code( + boost::system::errc::no_message); + } + } + + logger_->debug("Found {} products", textProducts.size()); + + return textProducts; +} + +std::vector> +IemApiProvider::ProcessTextProductFiles( + std::vector>& asyncResponses) +{ + std::vector> textProductFiles; + + for (auto& asyncResponse : asyncResponses) + { + auto response = asyncResponse.second.get(); + + if (response.status_code == cpr::status::HTTP_OK) + { + // Load file + auto& productId = asyncResponse.first; + const std::shared_ptr textProductFile { + std::make_shared()}; + std::istringstream responseBody {response.text}; + if (textProductFile->LoadData(productId, responseBody)) + { + textProductFiles.push_back(textProductFile); + } + } + else + { + logger_->warn("Could not load text product: {} ({})", + asyncResponse.first, + response.status_line); + } + } + + logger_->debug("Loaded {} text products", textProductFiles.size()); + + return textProductFiles; +} + +} // namespace scwx::provider diff --git a/wxdata/source/scwx/provider/nexrad_data_provider_factory.cpp b/wxdata/source/scwx/provider/nexrad_data_provider_factory.cpp index 5e75fd96..dcaf7c9f 100644 --- a/wxdata/source/scwx/provider/nexrad_data_provider_factory.cpp +++ b/wxdata/source/scwx/provider/nexrad_data_provider_factory.cpp @@ -1,5 +1,6 @@ #include #include +#include #include namespace scwx @@ -17,6 +18,13 @@ NexradDataProviderFactory::CreateLevel2DataProvider( return std::make_unique(radarSite); } +std::shared_ptr +NexradDataProviderFactory::CreateLevel2ChunksDataProvider( + const std::string& radarSite) +{ + return std::make_unique(radarSite); +} + std::shared_ptr NexradDataProviderFactory::CreateLevel3DataProvider( const std::string& radarSite, const std::string& product) diff --git a/wxdata/source/scwx/provider/warnings_provider.cpp b/wxdata/source/scwx/provider/warnings_provider.cpp index d187763a..a762cd99 100644 --- a/wxdata/source/scwx/provider/warnings_provider.cpp +++ b/wxdata/source/scwx/provider/warnings_provider.cpp @@ -1,9 +1,19 @@ -#include -#include -#include +// Prevent redefinition of __cpp_lib_format +#if defined(_MSC_VER) +# include +#endif -#include -#include +// Enable chrono formatters +#ifndef __cpp_lib_format +// NOLINTNEXTLINE(bugprone-reserved-identifier, cppcoreguidelines-macro-usage) +# define __cpp_lib_format 202110L +#endif + +#include +#include +#include + +#include #if defined(_MSC_VER) # pragma warning(push, 0) @@ -11,10 +21,8 @@ #define LIBXML_HTML_ENABLED #include -#include -#include -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) # include #endif @@ -22,40 +30,47 @@ # pragma warning(pop) #endif -namespace scwx -{ -namespace provider +namespace scwx::provider { static const std::string logPrefix_ = "scwx::provider::warnings_provider"; static const auto logger_ = util::Logger::Create(logPrefix_); -static constexpr std::chrono::seconds kUpdatePeriod_ {15}; - class WarningsProvider::Impl { public: struct FileInfoRecord { - std::chrono::system_clock::time_point startTime_ {}; - std::chrono::system_clock::time_point lastModified_ {}; - size_t size_ {}; - bool updated_ {}; + FileInfoRecord(std::string contentLength, std::string lastModified) : + contentLengthStr_ {std::move(contentLength)}, + lastModifiedStr_ {std::move(lastModified)} + { + } + + std::string contentLengthStr_ {}; + std::string lastModifiedStr_ {}; }; - typedef std::map WarningFileMap; + using WarningFileMap = std::map; - explicit Impl(const std::string& baseUrl) : - baseUrl_ {baseUrl}, files_ {}, filesMutex_ {} + explicit Impl(std::string baseUrl) : + baseUrl_ {std::move(baseUrl)}, files_ {}, filesMutex_ {} { } - ~Impl() {} + ~Impl() = default; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + + bool UpdateFileRecord(const cpr::Response& response, + const std::string& filename); std::string baseUrl_; - WarningFileMap files_; - std::shared_mutex filesMutex_; + WarningFileMap files_; + std::mutex filesMutex_; }; WarningsProvider::WarningsProvider(const std::string& baseUrl) : @@ -68,145 +83,177 @@ WarningsProvider::WarningsProvider(WarningsProvider&&) noexcept = default; WarningsProvider& WarningsProvider::operator=(WarningsProvider&&) noexcept = default; -std::pair -WarningsProvider::ListFiles(std::chrono::system_clock::time_point newerThan) +std::vector> +WarningsProvider::LoadUpdatedFiles( + std::chrono::sys_time startTime) { using namespace std::chrono; -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono >= 201907L) + namespace df = std; + + static constexpr std::string_view kDateTimeFormat { + "warnings_{:%Y%m%d_%H}.txt"}; +#else using namespace date; + namespace df = date; + +# define kDateTimeFormat "warnings_%Y%m%d_%H.txt" #endif - static constexpr LazyRE2 reWarningsFilename = { - "warnings_[0-9]{8}_[0-9]{2}.txt"}; - static const std::string dateTimeFormat {"warnings_%Y%m%d_%H.txt"}; - - logger_->trace("Listing files"); - - size_t updatedObjects = 0; - size_t totalObjects = 0; - - // Perform a directory listing - auto records = network::DirList(p->baseUrl_); - - // Sort records by filename - std::sort(records.begin(), - records.end(), - [](auto& a, auto& b) { return a.filename_ < b.filename_; }); - - // Filter warning records - auto warningRecords = - records | - std::views::filter( - [](auto& record) - { - return record.type_ == std::filesystem::file_type::regular && - RE2::FullMatch(record.filename_, *reWarningsFilename); - }); - - std::unique_lock lock(p->filesMutex_); - - Impl::WarningFileMap warningFileMap; - - // Store records - for (auto& record : warningRecords) - { - // Determine start time - std::chrono::sys_time startTime; - std::istringstream ssFilename {record.filename_}; - - ssFilename >> parse(dateTimeFormat, startTime); - - // If start time is valid - if (!ssFilename.fail()) - { - // Determine if the record should be marked updated - bool updated = true; - auto it = p->files_.find(record.filename_); - if (it != p->files_.cend()) - { - auto& existingRecord = it->second; - - updated = existingRecord.updated_ || - record.size_ != existingRecord.size_ || - record.mtime_ != existingRecord.lastModified_; - } - - // Update object counts, but only if newer than threshold - if (newerThan < startTime) - { - if (updated) - { - ++updatedObjects; - } - ++totalObjects; - } - - // Store record - warningFileMap.emplace( - std::piecewise_construct, - std::forward_as_tuple(record.filename_), - std::forward_as_tuple( - startTime, record.mtime_, record.size_, updated)); - } - } - - p->files_ = std::move(warningFileMap); - - return std::make_pair(updatedObjects, totalObjects); -} - -std::vector> -WarningsProvider::LoadUpdatedFiles( - std::chrono::system_clock::time_point newerThan) -{ - logger_->debug("Loading updated files"); - + std::vector< + std::pair, false>>> + asyncCallbacks; std::vector> updatedFiles; - std::vector> asyncResponses; + const std::chrono::sys_time now = + std::chrono::floor(util::time::now()); + std::chrono::sys_time currentHour = + (startTime != std::chrono::sys_time {}) ? + startTime : + now - std::chrono::hours {1}; - std::unique_lock lock(p->filesMutex_); + logger_->trace("Querying files newer than: {}", util::TimeString(startTime)); - // For each warning file - for (auto& record : p->files_) + while (currentHour <= now) { - // If file is updated, and time is later than the threshold - if (record.second.updated_ && newerThan < record.second.startTime_) - { - // Retrieve warning file - asyncResponses.emplace_back( - record.first, - cpr::GetAsync(cpr::Url {p->baseUrl_ + "/" + record.first})); + const std::string filename = df::format(kDateTimeFormat, currentHour); + const std::string url = p->baseUrl_ + "/" + filename; - // Clear updated flag - record.second.updated_ = false; - } + logger_->trace("HEAD request for file: {}", filename); + + asyncCallbacks.emplace_back( + filename, + cpr::HeadCallback( + [url, filename, this]( + cpr::Response headResponse) -> std::optional + { + if (headResponse.status_code == cpr::status::HTTP_OK) + { + const bool updated = + p->UpdateFileRecord(headResponse, filename); + + if (updated) + { + logger_->trace("GET request for file: {}", filename); + return cpr::GetAsync(cpr::Url {url}); + } + } + else if (headResponse.status_code != cpr::status::HTTP_NOT_FOUND) + { + logger_->warn("HEAD request for file failed: {} ({})", + url, + headResponse.status_line); + } + + return std::nullopt; + }, + cpr::Url {url})); + + // Query the next hour + currentHour += 1h; } - lock.unlock(); - - // Wait for warning files to load - for (auto& asyncResponse : asyncResponses) + for (auto& asyncCallback : asyncCallbacks) { - cpr::Response response = asyncResponse.second.get(); - if (response.status_code == cpr::status::HTTP_OK) - { - logger_->debug("Loading file: {}", asyncResponse.first); + auto& filename = asyncCallback.first; + auto& callback = asyncCallback.second; - // Load file - std::shared_ptr textProductFile { - std::make_shared()}; - std::istringstream responseBody {response.text}; - if (textProductFile->LoadData(responseBody)) + if (callback.valid()) + { + // Wait for futures to complete + callback.wait(); + auto asyncResponse = callback.get(); + + if (asyncResponse.has_value()) { - updatedFiles.push_back(textProductFile); + auto response = asyncResponse.value().get(); + + if (response.status_code == cpr::status::HTTP_OK) + { + logger_->debug("Loading file: {}", filename); + + // Load file + const std::shared_ptr textProductFile { + std::make_shared()}; + std::istringstream responseBody {response.text}; + if (textProductFile->LoadData(filename, responseBody)) + { + updatedFiles.push_back(textProductFile); + } + } + else + { + logger_->warn("Could not load file: {} ({})", + filename, + response.status_line); + } } } + else + { + logger_->error("Invalid future state"); + } } return updatedFiles; } -} // namespace provider -} // namespace scwx +bool WarningsProvider::Impl::UpdateFileRecord(const cpr::Response& response, + const std::string& filename) +{ + bool updated = false; + + auto contentLengthIt = response.header.find("Content-Length"); + auto lastModifiedIt = response.header.find("Last-Modified"); + + std::string contentLength {}; + std::string lastModified {}; + + if (contentLengthIt != response.header.cend()) + { + contentLength = contentLengthIt->second; + } + if (lastModifiedIt != response.header.cend()) + { + lastModified = lastModifiedIt->second; + } + + const std::unique_lock lock(filesMutex_); + + auto it = files_.find(filename); + if (it != files_.cend()) + { + auto& existingRecord = it->second; + + // If the size or last modified changes, request an update + + if (!contentLength.empty() && + contentLength != existingRecord.contentLengthStr_) + { + // Size changed + existingRecord.contentLengthStr_ = contentLengthIt->second; + updated = true; + } + else if (!lastModified.empty() && + lastModified != existingRecord.lastModifiedStr_) + { + // Last modified changed + existingRecord.lastModifiedStr_ = lastModifiedIt->second; + updated = true; + } + } + else + { + // File not found + files_.emplace(std::piecewise_construct, + std::forward_as_tuple(filename), + std::forward_as_tuple(contentLength, lastModified)); + updated = true; + } + + return updated; +} + +} // namespace scwx::provider diff --git a/wxdata/source/scwx/types/iem_types.cpp b/wxdata/source/scwx/types/iem_types.cpp new file mode 100644 index 00000000..ed3884d3 --- /dev/null +++ b/wxdata/source/scwx/types/iem_types.cpp @@ -0,0 +1,111 @@ +#include + +#include + +namespace scwx::types::iem +{ + +AfosEntry tag_invoke(boost::json::value_to_tag, + const boost::json::value& jv) +{ + auto& jo = jv.as_object(); + + AfosEntry entry {}; + + // Required parameters + entry.index_ = jo.at("index").as_int64(); + entry.entered_ = jo.at("entered").as_string(); + entry.pil_ = jo.at("pil").as_string(); + entry.productId_ = jo.at("product_id").as_string(); + entry.cccc_ = jo.at("cccc").as_string(); + entry.count_ = jo.at("count").as_int64(); + entry.link_ = jo.at("link").as_string(); + entry.textLink_ = jo.at("text_link").as_string(); + + return entry; +} + +AfosList tag_invoke(boost::json::value_to_tag, + const boost::json::value& jv) +{ + auto& jo = jv.as_object(); + + AfosList list {}; + + // Required parameters + list.data_ = boost::json::value_to>(jo.at("data")); + + return list; +} + +BadRequest tag_invoke(boost::json::value_to_tag, + const boost::json::value& jv) +{ + auto& jo = jv.as_object(); + + BadRequest badRequest {}; + + // Required parameters + badRequest.detail_ = jo.at("detail").as_string(); + + return badRequest; +} + +ValidationError::Detail::Context +tag_invoke(boost::json::value_to_tag, + const boost::json::value& jv) +{ + auto& jo = jv.as_object(); + + ValidationError::Detail::Context ctx {}; + + // Required parameters + ctx.error_ = jo.at("error").as_string(); + + return ctx; +} + +ValidationError::Detail +tag_invoke(boost::json::value_to_tag, + const boost::json::value& jv) +{ + auto& jo = jv.as_object(); + + ValidationError::Detail detail {}; + + // Required parameters + detail.type_ = jo.at("type").as_string(); + detail.loc_ = boost::json::value_to< + std::vector>>(jo.at("loc")); + detail.msg_ = jo.at("msg").as_string(); + + // Optional parameters + if (jo.contains("input")) + { + detail.input_ = jo.at("input").as_string(); + } + + if (jo.contains("ctx")) + { + detail.ctx_ = + boost::json::value_to(jo.at("ctx")); + } + + return detail; +} + +ValidationError tag_invoke(boost::json::value_to_tag, + const boost::json::value& jv) +{ + auto& jo = jv.as_object(); + + ValidationError error {}; + + // Required parameters + error.detail_ = boost::json::value_to>( + jo.at("detail")); + + return error; +} + +} // namespace scwx::types::iem diff --git a/wxdata/source/scwx/types/ntp_types.cpp b/wxdata/source/scwx/types/ntp_types.cpp new file mode 100644 index 00000000..daf5d46b --- /dev/null +++ b/wxdata/source/scwx/types/ntp_types.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include + +#ifdef _WIN32 +# include +#else +# include +#endif + +namespace scwx::types::ntp +{ + +NtpPacket NtpPacket::Parse(const std::span data) +{ + NtpPacket packet {}; + + assert(data.size() >= sizeof(NtpPacket)); + + packet = *reinterpret_cast(data.data()); + + packet.rootDelay = ntohl(packet.rootDelay); + packet.rootDispersion = ntohl(packet.rootDispersion); + packet.refId = ntohl(packet.refId); + + packet.refTm_s = ntohl(packet.refTm_s); + packet.refTm_f = ntohl(packet.refTm_f); + + packet.origTm_s = ntohl(packet.origTm_s); + packet.origTm_f = ntohl(packet.origTm_f); + + packet.rxTm_s = ntohl(packet.rxTm_s); + packet.rxTm_f = ntohl(packet.rxTm_f); + + packet.txTm_s = ntohl(packet.txTm_s); + packet.txTm_f = ntohl(packet.txTm_f); + + return packet; +} + +} // namespace scwx::types::ntp diff --git a/wxdata/source/scwx/util/json.cpp b/wxdata/source/scwx/util/json.cpp new file mode 100644 index 00000000..d5873758 --- /dev/null +++ b/wxdata/source/scwx/util/json.cpp @@ -0,0 +1,201 @@ +#include +#include + +#include + +#include +#include + +namespace scwx::util::json +{ + +static const std::string logPrefix_ = "scwx::util::json"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +/* Adapted from: + * https://www.boost.org/doc/libs/1_77_0/libs/json/doc/html/json/examples.html#json.examples.pretty + * + * Copyright (c) 2019, 2020 Vinnie Falco + * Copyright (c) 2020 Krystian Stasiowski + * Distributed under the Boost Software License, Version 1.0. (See + * http://www.boost.org/LICENSE_1_0.txt) + */ +static void PrettyPrintJson(std::ostream& os, + boost::json::value const& jv, + std::string* indent = nullptr); + +boost::json::value ReadJsonFile(const std::string& path) +{ + boost::json::value json; + + std::ifstream ifs {path}; + json = ReadJsonStream(ifs); + + return json; +} + +boost::json::value ReadJsonStream(std::istream& is) +{ + std::string line; + + boost::json::stream_parser p; + boost::system::error_code ec; + + while (std::getline(is, line)) + { + p.write(line, ec); + if (ec) + { + logger_->warn("{}", ec.message()); + return nullptr; + } + } + + p.finish(ec); + if (ec) + { + logger_->warn("{}", ec.message()); + return nullptr; + } + + return p.release(); +} + +boost::json::value ReadJsonString(std::string_view sv) +{ + boost::json::stream_parser p; + boost::system::error_code ec; + + p.write(sv, ec); + if (ec) + { + logger_->warn("{}", ec.message()); + return nullptr; + } + + p.finish(ec); + if (ec) + { + logger_->warn("{}", ec.message()); + return nullptr; + } + + return p.release(); +} + +void WriteJsonFile(const std::string& path, + const boost::json::value& json, + bool prettyPrint) +{ + std::ofstream ofs {path}; + + if (!ofs.is_open()) + { + logger_->warn("Cannot write JSON file: \"{}\"", path); + } + else + { + if (prettyPrint) + { + PrettyPrintJson(ofs, json); + } + else + { + ofs << json; + } + ofs.close(); + } +} + +// Allow recursion within the pretty print function +// NOLINTNEXTLINE(misc-no-recursion) +static void PrettyPrintJson(std::ostream& os, + boost::json::value const& jv, + std::string* indent) +{ + std::string indent_; + if (!indent) + indent = &indent_; + switch (jv.kind()) + { + case boost::json::kind::object: + { + os << "{\n"; + indent->append(4, ' '); + auto const& obj = jv.get_object(); + if (!obj.empty()) + { + auto it = obj.begin(); + for (;;) + { + os << *indent << boost::json::serialize(it->key()) << " : "; + PrettyPrintJson(os, it->value(), indent); + if (++it == obj.end()) + break; + os << ",\n"; + } + } + os << "\n"; + indent->resize(indent->size() - 4); + os << *indent << "}"; + break; + } + + case boost::json::kind::array: + { + os << "[\n"; + indent->append(4, ' '); + auto const& arr = jv.get_array(); + if (!arr.empty()) + { + auto it = arr.begin(); + for (;;) + { + os << *indent; + PrettyPrintJson(os, *it, indent); + if (++it == arr.end()) + break; + os << ",\n"; + } + } + os << "\n"; + indent->resize(indent->size() - 4); + os << *indent << "]"; + break; + } + + case boost::json::kind::string: + { + os << boost::json::serialize(jv.get_string()); + break; + } + + case boost::json::kind::uint64: + os << jv.get_uint64(); + break; + + case boost::json::kind::int64: + os << jv.get_int64(); + break; + + case boost::json::kind::double_: + os << jv.get_double(); + break; + + case boost::json::kind::bool_: + if (jv.get_bool()) + os << "true"; + else + os << "false"; + break; + + case boost::json::kind::null: + os << "null"; + break; + } + + if (indent->empty()) + os << "\n"; +} + +} // namespace scwx::util::json diff --git a/wxdata/source/scwx/util/logger.cpp b/wxdata/source/scwx/util/logger.cpp index 8a97d80e..407ce354 100644 --- a/wxdata/source/scwx/util/logger.cpp +++ b/wxdata/source/scwx/util/logger.cpp @@ -20,6 +20,12 @@ static std::vector> extraSinks_ {}; void Initialize() { spdlog::set_pattern(logPattern_); + + // Periodically flush every 3 seconds + spdlog::flush_every(std::chrono::seconds(3)); + + // Flush whenever logging info or higher + spdlog::flush_on(spdlog::level::level_enum::info); } void AddFileSink(const std::string& baseFilename) diff --git a/wxdata/source/scwx/util/streams.cpp b/wxdata/source/scwx/util/streams.cpp index 9e094f9b..6374ed35 100644 --- a/wxdata/source/scwx/util/streams.cpp +++ b/wxdata/source/scwx/util/streams.cpp @@ -1,8 +1,7 @@ #include +#include -namespace scwx -{ -namespace util +namespace scwx::util { std::istream& getline(std::istream& is, std::string& t) @@ -17,7 +16,8 @@ std::istream& getline(std::istream& is, std::string& t) int c = sb->sbumpc(); switch (c) { - case '\n': return is; + case '\n': + return is; case '\r': while (sb->sgetc() == '\r') @@ -30,6 +30,10 @@ std::istream& getline(std::istream& is, std::string& t) } return is; + case common::Characters::ETX: + sb->sungetc(); + return is; + case std::streambuf::traits_type::eof(): if (t.empty()) { @@ -37,10 +41,10 @@ std::istream& getline(std::istream& is, std::string& t) } return is; - default: t += static_cast(c); + default: + t += static_cast(c); } } } -} // namespace util -} // namespace scwx +} // namespace scwx::util diff --git a/wxdata/source/scwx/util/time.cpp b/wxdata/source/scwx/util/time.cpp index 5cf091fe..1a687d32 100644 --- a/wxdata/source/scwx/util/time.cpp +++ b/wxdata/source/scwx/util/time.cpp @@ -8,6 +8,7 @@ # define __cpp_lib_format 202110L #endif +#include #include #include #include @@ -17,13 +18,11 @@ #include -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) # include #endif -namespace scwx -{ -namespace util +namespace scwx::util::time { static const std::string logPrefix_ = "scwx::util::time"; @@ -34,6 +33,8 @@ static const std::unordered_map clockFormatName_ { {ClockFormat::_24Hour, "24-hour"}, {ClockFormat::Unknown, "?"}}; +static std::shared_ptr ntpClient_ {nullptr}; + SCWX_GET_ENUM(ClockFormat, GetClockFormat, clockFormatName_) const std::string& GetClockFormatName(ClockFormat clockFormat) @@ -41,6 +42,26 @@ const std::string& GetClockFormatName(ClockFormat clockFormat) return clockFormatName_.at(clockFormat); } +template +std::chrono::time_point now() +{ + if (ntpClient_ == nullptr) + { + ntpClient_ = network::NtpClient::Instance(); + } + + if (ntpClient_ != nullptr) + { + return Clock::now() + ntpClient_->time_offset(); + } + else + { + return Clock::now(); + } +} + +template std::chrono::time_point now(); + std::chrono::system_clock::time_point TimePoint(uint32_t modifiedJulianDate, uint32_t milliseconds) { @@ -48,6 +69,7 @@ std::chrono::system_clock::time_point TimePoint(uint32_t modifiedJulianDate, using sys_days = time_point; constexpr auto epoch = sys_days {1969y / December / 31d}; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers): literals are used return epoch + (modifiedJulianDate * 24h) + std::chrono::milliseconds {milliseconds}; } @@ -59,16 +81,20 @@ std::string TimeString(std::chrono::system_clock::time_point time, { using namespace std::chrono; -#if defined(_MSC_VER) -# define FORMAT_STRING_24_HOUR "{:%Y-%m-%d %H:%M:%S %Z}" -# define FORMAT_STRING_12_HOUR "{:%Y-%m-%d %I:%M:%S %p %Z}" +#if (__cpp_lib_chrono >= 201907L) namespace date = std::chrono; namespace df = std; + + static constexpr std::string_view kFormatString24Hour = + "{:%Y-%m-%d %H:%M:%S %Z}"; + static constexpr std::string_view kFormatString12Hour = + "{:%Y-%m-%d %I:%M:%S %p %Z}"; #else -# define FORMAT_STRING_24_HOUR "%Y-%m-%d %H:%M:%S %Z" -# define FORMAT_STRING_12_HOUR "%Y-%m-%d %I:%M:%S %p %Z" using namespace date; namespace df = date; + +# define kFormatString24Hour "%Y-%m-%d %H:%M:%S %Z" +# define kFormatString12Hour "%Y-%m-%d %I:%M:%S %p %Z" #endif auto timeInSeconds = time_point_cast(time); @@ -84,11 +110,11 @@ std::string TimeString(std::chrono::system_clock::time_point time, if (clockFormat == ClockFormat::_24Hour) { - os << df::format(FORMAT_STRING_24_HOUR, zt); + os << df::format(kFormatString24Hour, zt); } else { - os << df::format(FORMAT_STRING_12_HOUR, zt); + os << df::format(kFormatString12Hour, zt); } } catch (const std::exception& ex) @@ -110,11 +136,11 @@ std::string TimeString(std::chrono::system_clock::time_point time, { if (clockFormat == ClockFormat::_24Hour) { - os << df::format(FORMAT_STRING_24_HOUR, timeInSeconds); + os << df::format(kFormatString24Hour, timeInSeconds); } else { - os << df::format(FORMAT_STRING_12_HOUR, timeInSeconds); + os << df::format(kFormatString12Hour, timeInSeconds); } } } @@ -128,7 +154,7 @@ TryParseDateTime(const std::string& dateTimeFormat, const std::string& str) { using namespace std::chrono; -#if !defined(_MSC_VER) +#if (__cpp_lib_chrono < 201907L) using namespace date; #endif @@ -150,5 +176,4 @@ template std::optional> TryParseDateTime(const std::string& dateTimeFormat, const std::string& str); -} // namespace util -} // namespace scwx +} // namespace scwx::util::time diff --git a/wxdata/source/scwx/wsr88d/ar2v_file.cpp b/wxdata/source/scwx/wsr88d/ar2v_file.cpp index 6d951d6c..f4a71c15 100644 --- a/wxdata/source/scwx/wsr88d/ar2v_file.cpp +++ b/wxdata/source/scwx/wsr88d/ar2v_file.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,7 @@ #include #include #include +#include #if defined(__GNUC__) # pragma GCC diagnostic pop @@ -65,7 +67,9 @@ public: std::map> radarData_ {}; std::map>> + std::map>>> index_ {}; std::list rawRecords_ {}; @@ -130,58 +134,60 @@ std::shared_ptr Ar2vFile::vcp_data() const } std::tuple, float, std::vector> -Ar2vFile::GetElevationScan(rda::DataBlockType dataBlockType, - float elevation, - std::chrono::system_clock::time_point /*time*/) const +Ar2vFile::GetElevationScan(rda::DataBlockType dataBlockType, + float elevation, + std::chrono::system_clock::time_point time) const { - logger_->debug("GetElevationScan: {} degrees", elevation); - - constexpr float scaleFactor = 8.0f / 0.043945f; + logger_->trace("GetElevationScan: {} degrees", elevation); std::shared_ptr elevationScan = nullptr; float elevationCut = 0.0f; std::vector elevationCuts; - std::uint16_t codedElevation = - static_cast(std::lroundf(elevation * scaleFactor)); - if (p->index_.contains(dataBlockType)) { auto& scans = p->index_.at(dataBlockType); - std::uint16_t lowerBound = scans.cbegin()->first; - std::uint16_t upperBound = scans.crbegin()->first; + float lowerBound = scans.cbegin()->first; + float upperBound = scans.crbegin()->first; + // Find closest elevation match for (auto& scan : scans) { - if (scan.first > lowerBound && scan.first <= codedElevation) + if (scan.first > lowerBound && scan.first <= elevation) { lowerBound = scan.first; } - if (scan.first < upperBound && scan.first >= codedElevation) + if (scan.first < upperBound && scan.first >= elevation) { upperBound = scan.first; } - elevationCuts.push_back(scan.first / scaleFactor); + elevationCuts.push_back(scan.first); } - std::int32_t lowerDelta = - std::abs(static_cast(codedElevation) - - static_cast(lowerBound)); - std::int32_t upperDelta = - std::abs(static_cast(codedElevation) - - static_cast(upperBound)); + const float lowerDelta = std::abs(elevation - lowerBound); + const float upperDelta = std::abs(elevation - upperBound); - if (lowerDelta < upperDelta) + // Select closest elevation match + elevationCut = (lowerDelta < upperDelta) ? lowerBound : upperBound; + + // Select closest time match, not newer than the selected time + std::chrono::system_clock::time_point foundTime {}; + auto& elevationScans = scans.at(elevationCut); + + for (auto& scan : elevationScans) { - elevationScan = scans.at(lowerBound); - elevationCut = lowerBound / scaleFactor; - } - else - { - elevationScan = scans.at(upperBound); - elevationCut = upperBound / scaleFactor; + auto scanTime = std::chrono::floor(scan.first); + + if (elevationScan == nullptr || + ((scanTime <= time || + time == std::chrono::system_clock::time_point {}) && + scanTime > foundTime)) + { + elevationScan = scan.second; + foundTime = scanTime; + } } } @@ -240,10 +246,13 @@ bool Ar2vFile::LoadData(std::istream& is) if (dataValid) { + auto timePoint = util::TimePoint(p->julianDate_, p->milliseconds_); + logger_->debug("Filename: {}", p->tapeFilename_); logger_->debug("Extension: {}", p->extensionNumber_); - logger_->debug("Date: {}", p->julianDate_); - logger_->debug("Time: {}", p->milliseconds_); + logger_->debug("Date: {} ({:%Y-%m-%d})", p->julianDate_, timePoint); + logger_->debug( + "Time: {} ({:%H:%M:%S})", p->milliseconds_, timePoint); logger_->debug("ICAO: {}", p->icao_); size_t decompressedRecords = p->DecompressLDMRecords(is); @@ -264,7 +273,7 @@ bool Ar2vFile::LoadData(std::istream& is) std::size_t Ar2vFileImpl::DecompressLDMRecords(std::istream& is) { - logger_->debug("Decompressing LDM Records"); + logger_->trace("Decompressing LDM Records"); std::size_t numRecords = 0; @@ -312,14 +321,14 @@ std::size_t Ar2vFileImpl::DecompressLDMRecords(std::istream& is) ++numRecords; } - logger_->debug("Decompressed {} LDM Records", numRecords); + logger_->trace("Decompressed {} LDM Records", numRecords); return numRecords; } void Ar2vFileImpl::ParseLDMRecords() { - logger_->debug("Parsing LDM Records"); + logger_->trace("Parsing LDM Records"); std::size_t count = 0; @@ -436,11 +445,11 @@ void Ar2vFileImpl::ProcessRadarData( void Ar2vFileImpl::IndexFile() { - logger_->debug("Indexing file"); + logger_->trace("Indexing file"); for (auto& elevationCut : radarData_) { - std::uint16_t elevationAngle {}; + float elevationAngle {}; rda::WaveformType waveformType = rda::WaveformType::Unknown; std::shared_ptr& radial0 = @@ -456,14 +465,15 @@ void Ar2vFileImpl::IndexFile() if (vcpData_ != nullptr) { - elevationAngle = vcpData_->elevation_angle_raw(elevationCut.first); - waveformType = vcpData_->waveform_type(elevationCut.first); + elevationAngle = + static_cast(vcpData_->elevation_angle(elevationCut.first)); + waveformType = vcpData_->waveform_type(elevationCut.first); } else if ((digitalRadarData0 = - std::dynamic_pointer_cast( - (*elevationCut.second)[0])) != nullptr) + std::dynamic_pointer_cast(radial0)) != + nullptr) { - elevationAngle = digitalRadarData0->elevation_angle_raw(); + elevationAngle = digitalRadarData0->elevation_angle().value(); } else { @@ -488,8 +498,239 @@ void Ar2vFileImpl::IndexFile() if (momentData != nullptr) { - // TODO: Handle multiple elevation scans - index_[dataBlockType][elevationAngle] = elevationCut.second; + auto time = util::TimePoint(radial0->modified_julian_date(), + radial0->collection_time()); + + index_[dataBlockType][elevationAngle][time] = elevationCut.second; + } + } + } +} + +bool Ar2vFile::LoadLDMRecords(std::istream& is) +{ + const size_t decompressedRecords = p->DecompressLDMRecords(is); + if (decompressedRecords == 0) + { + p->ParseLDMRecord(is); + } + else + { + p->ParseLDMRecords(); + } + + return true; +} + +bool Ar2vFile::IndexFile() +{ + p->IndexFile(); + return true; +} + +// NOLINTNEXTLINE +bool IsRadarDataIncomplete( + const std::shared_ptr& radarData) +{ + // Assume the data is incomplete when the delta between the first and last + // angles is greater than 2.5 degrees. + constexpr units::degrees kIncompleteDataAngleThreshold_ {2.5}; + + const units::degrees firstAngle = + radarData->cbegin()->second->azimuth_angle(); + const units::degrees lastAngle = + radarData->crbegin()->second->azimuth_angle(); + const units::degrees angleDelta = + common::GetAngleDelta(firstAngle, lastAngle); + + return angleDelta > kIncompleteDataAngleThreshold_; +} + +Ar2vFile::Ar2vFile(const std::shared_ptr& current, + const std::shared_ptr& last) : + Ar2vFile() +{ + // This is only used to index right now, so not a huge deal + p->vcpData_ = nullptr; + + // Reconstruct index from the other's indexes + if (current != nullptr) + { + for (const auto& type : current->p->index_) + { + for (const auto& elevation : type.second) + { + // Get the most recent scan + const auto& mostRecent = elevation.second.crbegin(); + if (mostRecent == elevation.second.crend()) + { + continue; + } + + // Add previous scans for stepping back in time + for (auto scan = ++(elevation.second.rbegin()); + scan != elevation.second.rend(); + ++scan) + { + p->index_[type.first][elevation.first][scan->first] = + scan->second; + } + + // Merge this scan with the last one if it is incomplete + if (IsRadarDataIncomplete(mostRecent->second)) + { + std::shared_ptr secondMostRecent = nullptr; + + // check if this volume scan has an earlier elevation scan + auto possibleSecondMostRecent = elevation.second.rbegin(); + ++possibleSecondMostRecent; + + if (possibleSecondMostRecent == elevation.second.rend()) + { + if (last == nullptr) + { + // Nothing to merge with + p->index_[type.first][elevation.first][mostRecent->first] = + mostRecent->second; + continue; + } + + // get the scan from the last scan + auto elevationScan = + std::get>( + last->GetElevationScan( + type.first, elevation.first, {})); + if (elevationScan == nullptr) + { + // Nothing to merge with + p->index_[type.first][elevation.first][mostRecent->first] = + mostRecent->second; + continue; + } + + secondMostRecent = elevationScan; + } + else + { + secondMostRecent = possibleSecondMostRecent->second; + } + + // Make the new scan + auto newScan = std::make_shared(); + + // Copy over the new radials + for (const auto& radial : *(mostRecent->second)) + { + (*newScan)[radial.first] = radial.second; + } + + /* Correctly order the old radials. The radials need to be in + * order for the rendering to work, and the index needs to start + * at 0 and increase by one from there. Since the new radial + * should have index 0, the old radial needs to be reshaped to + * match the new radials indexing. + */ + + const double lowestAzm = + mostRecent->second->cbegin()->second->azimuth_angle().value(); + const double heighestAzm = mostRecent->second->crbegin() + ->second->azimuth_angle() + .value(); + std::uint16_t index = mostRecent->second->crbegin()->first + 1; + + // Sort by the azimuth. Makes the rest of this way easier + auto secondMostRecentAzmMap = + std::map>(); + for (const auto& radial : *secondMostRecent) + { + secondMostRecentAzmMap[radial.second->azimuth_angle() + .value()] = radial.second; + } + + if (lowestAzm <= heighestAzm) // New scan does not contain 0/360 + { + // Get the radials following the new radials + for (const auto& radial : secondMostRecentAzmMap) + { + if (radial.first > heighestAzm) + { + (*newScan)[index] = radial.second; + ++index; + } + } + // Get the radials before the new radials + for (const auto& radial : secondMostRecentAzmMap) + { + if (radial.first < lowestAzm) + { + (*newScan)[index] = radial.second; + ++index; + } + else + { + break; + } + } + } + else // New scan includes 0/360 + { + // The radials will already be in the right order + for (const auto& radial : secondMostRecentAzmMap) + { + if (radial.first > heighestAzm && radial.first < lowestAzm) + { + (*newScan)[index] = radial.second; + ++index; + } + } + } + + p->index_[type.first][elevation.first][mostRecent->first] = + newScan; + } + else + { + p->index_[type.first][elevation.first][mostRecent->first] = + mostRecent->second; + } + } + } + } + + // Go though last, adding other elevations + if (last != nullptr) + { + for (const auto& type : last->p->index_) + { + // Find the highest elevation this type has for the current scan + // Start below any reasonable elevation + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) + float highestCurrentElevation = -90; + const auto& elevationScans = p->index_.find(type.first); + if (elevationScans != p->index_.cend()) + { + const auto& highestElevation = elevationScans->second.crbegin(); + if (highestElevation != elevationScans->second.crend()) + { + // Add a slight offset to ensure good floating point compare. + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) + highestCurrentElevation = highestElevation->first + 0.01f; + } + } + + for (const auto& elevation : type.second) + { + // Only add elevations above the current scan's elevation + if (elevation.first > highestCurrentElevation) + { + const auto& mostRecent = elevation.second.crbegin(); + if (mostRecent == elevation.second.crend()) + { + continue; + } + p->index_[type.first][elevation.first][mostRecent->first] = + mostRecent->second; + } } } } diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp index ebadf4f5..8f2643af 100644 --- a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp @@ -154,7 +154,18 @@ std::uint16_t DigitalRadarData::elevation_angle_raw() const units::degrees DigitalRadarData::elevation_angle() const { - return units::degrees {p->elevationAngle_ * kAngleDataScale}; + // NOLINTNEXTLINE This conversion is accurate + float elevationAngleConverted = p->elevationAngle_ * kAngleDataScale; + // Any elevation above 90 degrees should be interpreted as a + // negative angle + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + if (elevationAngleConverted > 90) + { + elevationAngleConverted -= 360; + } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + + return units::degrees {elevationAngleConverted}; } std::uint16_t DigitalRadarData::elevation_number() const diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp index 4870c1c3..4004fd07 100644 --- a/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp @@ -1,11 +1,7 @@ #include #include -namespace scwx -{ -namespace wsr88d -{ -namespace rda +namespace scwx::wsr88d::rda { static const std::string logPrefix_ = @@ -27,9 +23,9 @@ static const std::unordered_map strToDataBlock_ { class DigitalRadarDataGeneric::DataBlock::Impl { public: - explicit Impl(const std::string& dataBlockType, - const std::string& dataName) : - dataBlockType_ {dataBlockType}, dataName_ {dataName} + explicit Impl(std::string dataBlockType, std::string dataName) : + dataBlockType_ {std::move(dataBlockType)}, + dataName_ {std::move(dataName)} { } @@ -51,7 +47,13 @@ DigitalRadarDataGeneric::DataBlock::operator=(DataBlock&&) noexcept = default; class DigitalRadarDataGeneric::MomentDataBlock::Impl { public: - explicit Impl() {} + explicit Impl() = default; + ~Impl() = default; + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; std::uint16_t numberOfDataMomentGates_ {0}; std::int16_t dataMomentRange_ {0}; @@ -89,7 +91,9 @@ DigitalRadarDataGeneric::MomentDataBlock::number_of_data_moment_gates() const units::kilometers DigitalRadarDataGeneric::MomentDataBlock::data_moment_range() const { - return units::kilometers {p->dataMomentRange_ * 0.001f}; + static constexpr float kScale_ = 0.001f; + return units::kilometers {static_cast(p->dataMomentRange_) * + kScale_}; } std::int16_t @@ -102,7 +106,9 @@ units::kilometers DigitalRadarDataGeneric::MomentDataBlock::data_moment_range_sample_interval() const { - return units::kilometers {p->dataMomentRangeSampleInterval_ * 0.001f}; + static constexpr float kScale_ = 0.001f; + return units::kilometers { + static_cast(p->dataMomentRangeSampleInterval_) * kScale_}; } std::uint16_t DigitalRadarDataGeneric::MomentDataBlock:: @@ -113,7 +119,8 @@ std::uint16_t DigitalRadarDataGeneric::MomentDataBlock:: float DigitalRadarDataGeneric::MomentDataBlock::snr_threshold() const { - return p->snrThreshold_ * 0.1f; + static constexpr float kScale_ = 0.1f; + return static_cast(p->snrThreshold_) * kScale_; } std::int16_t DigitalRadarDataGeneric::MomentDataBlock::snr_threshold_raw() const @@ -138,14 +145,14 @@ float DigitalRadarDataGeneric::MomentDataBlock::offset() const const void* DigitalRadarDataGeneric::MomentDataBlock::data_moments() const { - const void* dataMoments; + const void* dataMoments = nullptr; switch (p->dataWordSize_) { - case 8: + case 8: // NOLINT(cppcoreguidelines-avoid-magic-numbers) dataMoments = p->momentGates8_.data(); break; - case 16: + case 16: // NOLINT(cppcoreguidelines-avoid-magic-numbers) dataMoments = p->momentGates16_.data(); break; default: @@ -189,13 +196,15 @@ bool DigitalRadarDataGeneric::MomentDataBlock::Parse(std::istream& is) is.read(reinterpret_cast(&p->scale_), 4); // 20-23 is.read(reinterpret_cast(&p->offset_), 4); // 24-27 - p->numberOfDataMomentGates_ = ntohs(p->numberOfDataMomentGates_); - p->dataMomentRange_ = ntohs(p->dataMomentRange_); + p->numberOfDataMomentGates_ = ntohs(p->numberOfDataMomentGates_); + p->dataMomentRange_ = static_cast(ntohs(p->dataMomentRange_)); p->dataMomentRangeSampleInterval_ = ntohs(p->dataMomentRangeSampleInterval_); p->tover_ = ntohs(p->tover_); - p->snrThreshold_ = ntohs(p->snrThreshold_); - p->scale_ = awips::Message::SwapFloat(p->scale_); - p->offset_ = awips::Message::SwapFloat(p->offset_); + p->snrThreshold_ = static_cast(ntohs(p->snrThreshold_)); + p->scale_ = awips::Message::SwapFloat(p->scale_); + p->offset_ = awips::Message::SwapFloat(p->offset_); + + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) if (p->numberOfDataMomentGates_ <= 1840) { @@ -209,7 +218,7 @@ bool DigitalRadarDataGeneric::MomentDataBlock::Parse(std::istream& is) { p->momentGates16_.resize(p->numberOfDataMomentGates_); is.read(reinterpret_cast(p->momentGates16_.data()), - p->numberOfDataMomentGates_ * 2); + static_cast(p->numberOfDataMomentGates_) * 2); awips::Message::SwapVector(p->momentGates16_); } else @@ -225,13 +234,21 @@ bool DigitalRadarDataGeneric::MomentDataBlock::Parse(std::istream& is) dataBlockValid = false; } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + return dataBlockValid; } class DigitalRadarDataGeneric::VolumeDataBlock::Impl { public: - explicit Impl() {} + explicit Impl() = default; + ~Impl() = default; + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; std::uint16_t lrtup_ {0}; std::uint8_t versionNumberMajor_ {0}; @@ -247,6 +264,7 @@ public: float initialSystemDifferentialPhase_ {0.0f}; std::uint16_t volumeCoveragePatternNumber_ {0}; std::uint16_t processingStatus_ {0}; + std::uint16_t zdrBiasEstimateWeightedMean_ {0}; }; DigitalRadarDataGeneric::VolumeDataBlock::VolumeDataBlock( @@ -320,7 +338,7 @@ bool DigitalRadarDataGeneric::VolumeDataBlock::Parse(std::istream& is) p->lrtup_ = ntohs(p->lrtup_); p->latitude_ = awips::Message::SwapFloat(p->latitude_); p->longitude_ = awips::Message::SwapFloat(p->longitude_); - p->siteHeight_ = ntohs(p->siteHeight_); + p->siteHeight_ = static_cast(ntohs(p->siteHeight_)); p->feedhornHeight_ = ntohs(p->feedhornHeight_); p->calibrationConstant_ = awips::Message::SwapFloat(p->calibrationConstant_); p->horizontaShvTxPower_ = awips::Message::SwapFloat(p->horizontaShvTxPower_); @@ -332,13 +350,35 @@ bool DigitalRadarDataGeneric::VolumeDataBlock::Parse(std::istream& is) p->volumeCoveragePatternNumber_ = ntohs(p->volumeCoveragePatternNumber_); p->processingStatus_ = ntohs(p->processingStatus_); + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + + if (p->lrtup_ >= 46) + { + is.read(reinterpret_cast(&p->zdrBiasEstimateWeightedMean_), + 2); // 44-45 + p->zdrBiasEstimateWeightedMean_ = ntohs(p->zdrBiasEstimateWeightedMean_); + } + + if (p->lrtup_ >= 52) + { + is.seekg(6, std::ios_base::cur); // 46-51 + } + + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + return dataBlockValid; } class DigitalRadarDataGeneric::ElevationDataBlock::Impl { public: - explicit Impl() {} + explicit Impl() = default; + ~Impl() = default; + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; std::uint16_t lrtup_ {0}; std::int16_t atmos_ {0}; @@ -384,7 +424,7 @@ bool DigitalRadarDataGeneric::ElevationDataBlock::Parse(std::istream& is) is.read(reinterpret_cast(&p->calibrationConstant_), 4); // 8-11 p->lrtup_ = ntohs(p->lrtup_); - p->atmos_ = ntohs(p->atmos_); + p->atmos_ = static_cast(ntohs(p->atmos_)); p->calibrationConstant_ = awips::Message::SwapFloat(p->calibrationConstant_); return dataBlockValid; @@ -393,7 +433,13 @@ bool DigitalRadarDataGeneric::ElevationDataBlock::Parse(std::istream& is) class DigitalRadarDataGeneric::RadialDataBlock::Impl { public: - explicit Impl() {} + explicit Impl() = default; + ~Impl() = default; + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; std::uint16_t lrtup_ {0}; std::uint16_t unambigiousRange_ {0}; @@ -420,7 +466,8 @@ DigitalRadarDataGeneric::RadialDataBlock::operator=( float DigitalRadarDataGeneric::RadialDataBlock::unambiguous_range() const { - return p->unambigiousRange_ / 10.0f; + static constexpr float kScale_ = 0.1f; + return static_cast(p->unambigiousRange_) * kScale_; } std::shared_ptr @@ -473,24 +520,31 @@ bool DigitalRadarDataGeneric::RadialDataBlock::Parse(std::istream& is) class DigitalRadarDataGeneric::Impl { public: - explicit Impl() {}; - ~Impl() = default; + explicit Impl() = default; + ~Impl() = default; - std::string radarIdentifier_ {}; - std::uint32_t collectionTime_ {0}; - std::uint16_t modifiedJulianDate_ {0}; - std::uint16_t azimuthNumber_ {0}; - float azimuthAngle_ {0.0f}; - std::uint8_t compressionIndicator_ {0}; - std::uint16_t radialLength_ {0}; - std::uint8_t azimuthResolutionSpacing_ {0}; - std::uint8_t radialStatus_ {0}; - std::uint8_t elevationNumber_ {0}; - std::uint8_t cutSectorNumber_ {0}; - float elevationAngle_ {0.0f}; - std::uint8_t radialSpotBlankingStatus_ {0}; - std::uint8_t azimuthIndexingMode_ {0}; - std::uint16_t dataBlockCount_ {0}; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + + std::string radarIdentifier_ {}; + std::uint32_t collectionTime_ {0}; + std::uint16_t modifiedJulianDate_ {0}; + std::uint16_t azimuthNumber_ {0}; + float azimuthAngle_ {0.0f}; + std::uint8_t compressionIndicator_ {0}; + std::uint16_t radialLength_ {0}; + std::uint8_t azimuthResolutionSpacing_ {0}; + std::uint8_t radialStatus_ {0}; + std::uint8_t elevationNumber_ {0}; + std::uint8_t cutSectorNumber_ {0}; + float elevationAngle_ {0.0f}; + std::uint8_t radialSpotBlankingStatus_ {0}; + std::uint8_t azimuthIndexingMode_ {0}; + std::uint16_t dataBlockCount_ {0}; + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) std::array dataBlockPointer_ {0}; std::shared_ptr volumeDataBlock_ {nullptr}; @@ -666,6 +720,8 @@ bool DigitalRadarDataGeneric::Parse(std::istream& is) p->elevationAngle_ = SwapFloat(p->elevationAngle_); p->dataBlockCount_ = ntohs(p->dataBlockCount_); + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + if (p->azimuthNumber_ < 1 || p->azimuthNumber_ > 720) { logger_->warn("Invalid azimuth number: {}", p->azimuthNumber_); @@ -687,18 +743,22 @@ bool DigitalRadarDataGeneric::Parse(std::istream& is) messageValid = false; } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + if (!messageValid) { p->dataBlockCount_ = 0; } is.read(reinterpret_cast(&p->dataBlockPointer_), - p->dataBlockCount_ * 4); + static_cast(p->dataBlockCount_) * 4); SwapArray(p->dataBlockPointer_, p->dataBlockCount_); for (uint16_t b = 0; b < p->dataBlockCount_; ++b) { + // Index already has bounds check + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) is.seekg(isBegin + std::streamoff(p->dataBlockPointer_[b]), std::ios_base::beg); @@ -721,15 +781,15 @@ bool DigitalRadarDataGeneric::Parse(std::istream& is) { case DataBlockType::Volume: p->volumeDataBlock_ = - std::move(VolumeDataBlock::Create(dataBlockType, dataName, is)); + VolumeDataBlock::Create(dataBlockType, dataName, is); break; case DataBlockType::Elevation: p->elevationDataBlock_ = - std::move(ElevationDataBlock::Create(dataBlockType, dataName, is)); + ElevationDataBlock::Create(dataBlockType, dataName, is); break; case DataBlockType::Radial: p->radialDataBlock_ = - std::move(RadialDataBlock::Create(dataBlockType, dataName, is)); + RadialDataBlock::Create(dataBlockType, dataName, is); break; case DataBlockType::MomentRef: case DataBlockType::MomentVel: @@ -739,7 +799,7 @@ bool DigitalRadarDataGeneric::Parse(std::istream& is) case DataBlockType::MomentRho: case DataBlockType::MomentCfp: p->momentDataBlock_[dataBlock] = - std::move(MomentDataBlock::Create(dataBlockType, dataName, is)); + MomentDataBlock::Create(dataBlockType, dataName, is); break; default: logger_->warn("Unknown data name: {}", dataName); @@ -771,6 +831,4 @@ DigitalRadarDataGeneric::Create(Level2MessageHeader&& header, std::istream& is) return message; } -} // namespace rda -} // namespace wsr88d -} // namespace scwx +} // namespace scwx::wsr88d::rda diff --git a/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp b/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp index 2a478a2f..7fe5b77e 100644 --- a/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp +++ b/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp @@ -8,26 +8,23 @@ #include #include #include +#include #include #include #include #include -namespace scwx -{ -namespace wsr88d -{ -namespace rda +namespace scwx::wsr88d::rda { static const std::string logPrefix_ = "scwx::wsr88d::rda::level2_message_factory"; static const auto logger_ = util::Logger::Create(logPrefix_); -typedef std::function(Level2MessageHeader&&, - std::istream&)> - CreateLevel2MessageFunction; +using CreateLevel2MessageFunction = + std::function(Level2MessageHeader&&, + std::istream&)>; static const std::unordered_map create_ {{1, DigitalRadarData::Create}, @@ -37,20 +34,18 @@ static const std::unordered_map {13, ClutterFilterBypassMap::Create}, {15, ClutterFilterMap::Create}, {18, RdaAdaptationData::Create}, - {31, DigitalRadarDataGeneric::Create}}; + {31, DigitalRadarDataGeneric::Create}, + {32, RdaPrfData::Create}}; struct Level2MessageFactory::Context { Context() : - messageData_ {}, - bufferedSize_ {}, - messageBuffer_ {messageData_}, - messageBufferStream_ {&messageBuffer_} + messageBuffer_ {messageData_}, messageBufferStream_ {&messageBuffer_} { } - std::vector messageData_; - size_t bufferedSize_; + std::vector messageData_ {}; + std::size_t bufferedSize_ {}; util::vectorbuf messageBuffer_; std::istream messageBufferStream_; bool bufferingData_ {false}; @@ -76,13 +71,16 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is, if (info.headerValid) { - if (header.message_size() == 65535) + if (header.message_size() == std::numeric_limits::max()) { + // A message size of 65535 indicates a message with a single segment. + // The size is specified in the bytes normally reserved for the segment + // number and total number of segments. segment = 1; totalSegments = 1; dataSize = (static_cast(header.number_of_message_segments()) - << 16) + + << 16) + // NOLINT(cppcoreguidelines-avoid-magic-numbers) header.message_segment_number(); } else @@ -143,14 +141,16 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is, logger_->debug("Bad size estimate, increasing size"); // Estimate remaining size - uint16_t remainingSegments = - std::max(totalSegments - segment + 1, 100u); - size_t remainingSize = remainingSegments * dataSize; + static const std::uint16_t kMinRemainingSegments_ = 100u; + const std::uint16_t remainingSegments = std::max( + totalSegments - segment + 1, kMinRemainingSegments_); + const std::size_t remainingSize = remainingSegments * dataSize; ctx->messageData_.resize(ctx->bufferedSize_ + remainingSize); } - is.read(ctx->messageData_.data() + ctx->bufferedSize_, dataSize); + is.read(&ctx->messageData_[ctx->bufferedSize_], + static_cast(dataSize)); ctx->bufferedSize_ += dataSize; if (is.eof()) @@ -164,7 +164,7 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is, else if (segment == totalSegments) { ctx->messageBuffer_.update_read_pointers(ctx->bufferedSize_); - header.set_message_size(static_cast( + header.set_message_size(static_cast( ctx->bufferedSize_ / 2 + Level2MessageHeader::SIZE)); messageStream = &ctx->messageBufferStream_; @@ -186,7 +186,7 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is, else if (info.headerValid) { // Seek to the end of the current message - is.seekg(dataSize, std::ios_base::cur); + is.seekg(static_cast(dataSize), std::ios_base::cur); } if (info.message == nullptr) @@ -197,6 +197,4 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is, return info; } -} // namespace rda -} // namespace wsr88d -} // namespace scwx +} // namespace scwx::wsr88d::rda diff --git a/wxdata/source/scwx/wsr88d/rda/performance_maintenance_data.cpp b/wxdata/source/scwx/wsr88d/rda/performance_maintenance_data.cpp index 662ab506..647221e6 100644 --- a/wxdata/source/scwx/wsr88d/rda/performance_maintenance_data.cpp +++ b/wxdata/source/scwx/wsr88d/rda/performance_maintenance_data.cpp @@ -3,700 +3,447 @@ #include -namespace scwx -{ -namespace wsr88d -{ -namespace rda +namespace scwx::wsr88d::rda { static const std::string logPrefix_ = "scwx::wsr88d::rda::performance_maintenance_data"; static const auto logger_ = util::Logger::Create(logPrefix_); -class PerformanceMaintenanceDataImpl +class PerformanceMaintenanceData::Impl { public: - explicit PerformanceMaintenanceDataImpl() : - loopBackSetStatus_ {0}, - t1OutputFrames_ {0}, - t1InputFrames_ {0}, - routerMemoryUsed_ {0}, - routerMemoryFree_ {0}, - routerMemoryUtilization_ {0}, - routeToRpg_ {0}, - csuLossOfSignal_ {0}, - csuLossOfFrames_ {0}, - csuYellowAlarms_ {0}, - csuBlueAlarms_ {0}, - csu24HrErroredSeconds_ {0}, - csu24HrSeverelyErroredSeconds_ {0}, - csu24HrSeverelyErroredFramingSeconds_ {0}, - csu24HrUnavailableSeconds_ {0}, - csu24HrControlledSlipSeconds_ {0}, - csu24HrPathCodingViolations_ {0}, - csu24HrLineErroredSeconds_ {0}, - csu24HrBurstyErroredSeconds_ {0}, - csu24HrDegradedMinutes_ {0}, - lanSwitchCpuUtilization_ {0}, - lanSwitchMemoryUtilization_ {0}, - ifdrChasisTemperature_ {0}, - ifdrFpgaTemperature_ {0}, - gpsSatellites_ {0}, - ipcStatus_ {0}, - commandedChannelControl_ {0}, - polarization_ {0}, - ameInternalTemperature_ {0.0f}, - ameReceiverModuleTemperature_ {0.0f}, - ameBiteCalModuleTemperature_ {0.0f}, - amePeltierPulseWidthModulation_ {0}, - amePeltierStatus_ {0}, - ameADConverterStatus_ {0}, - ameState_ {0}, - ame3_3VPsVoltage_ {0.0f}, - ame5VPsVoltage_ {0.0f}, - ame6_5VPsVoltage_ {0.0f}, - ame15VPsVoltage_ {0.0f}, - ame48VPsVoltage_ {0.0f}, - ameStaloPower_ {0.0f}, - peltierCurrent_ {0.0f}, - adcCalibrationReferenceVoltage_ {0.0f}, - ameMode_ {0}, - amePeltierMode_ {0}, - amePeltierInsideFanCurrent_ {0.0f}, - amePeltierOutsideFanCurrent_ {0.0f}, - horizontalTrLimiterVoltage_ {0.0f}, - verticalTrLimiterVoltage_ {0.0f}, - adcCalibrationOffsetVoltage_ {0.0f}, - adcCalibrationGainCorrection_ {0.0f}, - rcpStatus_ {0}, - rcpString_ {}, - spipPowerButtons_ {0}, - masterPowerAdministratorLoad_ {0.0f}, - expansionPowerAdministratorLoad_ {0.0f}, - _5VdcPs_ {0}, - _15VdcPs_ {0}, - _28VdcPs_ {0}, - neg15VdcPs_ {0}, - _45VdcPs_ {0}, - filamentPsVoltage_ {0}, - vacuumPumpPsVoltage_ {0}, - focusCoilPsVoltage_ {0}, - filamentPs_ {0}, - klystronWarmup_ {0}, - transmitterAvailable_ {0}, - wgSwitchPosition_ {0}, - wgPfnTransferInterlock_ {0}, - maintenanceMode_ {0}, - maintenanceRequired_ {0}, - pfnSwitchPosition_ {0}, - modulatorOverload_ {0}, - modulatorInvCurrent_ {0}, - modulatorSwitchFail_ {0}, - mainPowerVoltage_ {0}, - chargingSystemFail_ {0}, - inverseDiodeCurrent_ {0}, - triggerAmplifier_ {0}, - circulatorTemperature_ {0}, - spectrumFilterPressure_ {0}, - wgArcVswr_ {0}, - cabinetInterlock_ {0}, - cabinetAirTemperature_ {0}, - cabinetAirflow_ {0}, - klystronCurrent_ {0}, - klystronFilamentCurrent_ {0}, - klystronVacionCurrent_ {0}, - klystronAirTemperature_ {0}, - klystronAirflow_ {0}, - modulatorSwitchMaintenance_ {0}, - postChargeRegulatorMaintenance_ {0}, - wgPressureHumidity_ {0}, - transmitterOvervoltage_ {0}, - transmitterOvercurrent_ {0}, - focusCoilCurrent_ {0}, - focusCoilAirflow_ {0}, - oilTemperature_ {0}, - prfLimit_ {0}, - transmitterOilLevel_ {0}, - transmitterBatteryCharging_ {0}, - highVoltageStatus_ {0}, - transmitterRecyclingSummary_ {0}, - transmitterInoperable_ {0}, - transmitterAirFilter_ {0}, - zeroTestBit_ {0}, - oneTestBit_ {0}, - xmtrSpipInterface_ {0}, - transmitterSummaryStatus_ {0}, - transmitterRfPower_ {0.0f}, - horizontalXmtrPeakPower_ {0.0f}, - xmtrPeakPower_ {0.0f}, - verticalXmtrPeakPower_ {0.0f}, - xmtrRfAvgPower_ {0.0f}, - xmtrRecycleCount_ {0}, - receiverBias_ {0.0f}, - transmitImbalance_ {0.0f}, - xmtrPowerMeterZero_ {0.0f}, - acUnit1CompressorShutOff_ {0}, - acUnit2CompressorShutOff_ {0}, - generatorMaintenanceRequired_ {0}, - generatorBatteryVoltage_ {0}, - generatorEngine_ {0}, - generatorVoltFrequency_ {0}, - powerSource_ {0}, - transitionalPowerSource_ {0}, - generatorAutoRunOffSwitch_ {0}, - aircraftHazardLighting_ {0}, - equipmentShelterFireDetectionSystem_ {0}, - equipmentShelterFireSmoke_ {0}, - generatorShelterFireSmoke_ {0}, - utilityVoltageFrequency_ {0}, - siteSecurityAlarm_ {0}, - securityEquipment_ {0}, - securitySystem_ {0}, - receiverConnectedToAntenna_ {0}, - radomeHatch_ {0}, - acUnit1FilterDirty_ {0}, - acUnit2FilterDirty_ {0}, - equipmentShelterTemperature_ {0.0f}, - outsideAmbientTemperature_ {0.0f}, - transmitterLeavingAirTemp_ {0.0f}, - acUnit1DischargeAirTemp_ {0.0f}, - generatorShelterTemperature_ {0.0f}, - radomeAirTemperature_ {0.0f}, - acUnit2DischargeAirTemp_ {0.0f}, - spip15VPs_ {0.0f}, - spipNeg15VPs_ {0.0f}, - spip28VPsStatus_ {0}, - spip5VPs_ {0.0f}, - convertedGeneratorFuelLevel_ {0}, - elevationPosDeadLimit_ {0}, - _150VOvervoltage_ {0}, - _150VUndervoltage_ {0}, - elevationServoAmpInhibit_ {0}, - elevationServoAmpShortCircuit_ {0}, - elevationServoAmpOvertemp_ {0}, - elevationMotorOvertemp_ {0}, - elevationStowPin_ {0}, - elevationHousing5VPs_ {0}, - elevationNegDeadLimit_ {0}, - elevationPosNormalLimit_ {0}, - elevationNegNormalLimit_ {0}, - elevationEncoderLight_ {0}, - elevationGearboxOil_ {0}, - elevationHandwheel_ {0}, - elevationAmpPs_ {0}, - azimuthServoAmpInhibit_ {0}, - azimuthServoAmpShortCircuit_ {0}, - azimuthServoAmpOvertemp_ {0}, - azimuthMotorOvertemp_ {0}, - azimuthStowPin_ {0}, - azimuthHousing5VPs_ {0}, - azimuthEncoderLight_ {0}, - azimuthGearboxOil_ {0}, - azimuthBullGearOil_ {0}, - azimuthHandwheel_ {0}, - azimuthServoAmpPs_ {0}, - servo_ {0}, - pedestalInterlockSwitch_ {0}, - cohoClock_ {0}, - rfGeneratorFrequencySelectOscillator_ {0}, - rfGeneratorRfStalo_ {0}, - rfGeneratorPhaseShiftedCoho_ {0}, - _9VReceiverPs_ {0}, - _5VReceiverPs_ {0}, - _18VReceiverPs_ {0}, - neg9VReceiverPs_ {0}, - _5VSingleChannelRdaiuPs_ {0}, - horizontalShortPulseNoise_ {0.0f}, - horizontalLongPulseNoise_ {0.0f}, - horizontalNoiseTemperature_ {0.0f}, - verticalShortPulseNoise_ {0.0f}, - verticalLongPulseNoise_ {0.0f}, - verticalNoiseTemperature_ {0.0f}, - horizontalLinearity_ {0.0f}, - horizontalDynamicRange_ {0.0f}, - horizontalDeltaDbz0_ {0.0f}, - verticalDeltaDbz0_ {0.0f}, - kdPeakMeasured_ {0.0f}, - shortPulseHorizontalDbz0_ {0.0f}, - longPulseHorizontalDbz0_ {0.0f}, - velocityProcessed_ {0}, - widthProcessed_ {0}, - velocityRfGen_ {0}, - widthRfGen_ {0}, - horizontalI0_ {0.0f}, - verticalI0_ {0.0f}, - verticalDynamicRange_ {0.0f}, - shortPulseVerticalDbz0_ {0.0f}, - longPulseVerticalDbz0_ {0.0f}, - horizontalPowerSense_ {0.0f}, - verticalPowerSense_ {0.0f}, - zdrBias_ {0.0f}, - clutterSuppressionDelta_ {0.0f}, - clutterSuppressionUnfilteredPower_ {0.0f}, - clutterSuppressionFilteredPower_ {0.0f}, - verticalLinearity_ {0.0f}, - stateFileReadStatus_ {0}, - stateFileWriteStatus_ {0}, - bypassMapFileReadStatus_ {0}, - bypassMapFileWriteStatus_ {0}, - currentAdaptationFileReadStatus_ {0}, - currentAdaptationFileWriteStatus_ {0}, - censorZoneFileReadStatus_ {0}, - censorZoneFileWriteStatus_ {0}, - remoteVcpFileReadStatus_ {0}, - remoteVcpFileWriteStatus_ {0}, - baselineAdaptationFileReadStatus_ {0}, - readStatusOfPrfSets_ {0}, - clutterFilterMapFileReadStatus_ {0}, - clutterFilterMapFileWriteStatus_ {0}, - generatlDiskIoError_ {0}, - rspStatus_ {0}, - motherboardTemperature_ {0}, - cpu1Temperature_ {0}, - cpu2Temperature_ {0}, - cpu1FanSpeed_ {0}, - cpu2FanSpeed_ {0}, - rspFan1Speed_ {0}, - rspFan2Speed_ {0}, - rspFan3Speed_ {0}, - spipCommStatus_ {0}, - hciCommStatus_ {0}, - signalProcessorCommandStatus_ {0}, - ameCommunicationStatus_ {0}, - rmsLinkStatus_ {0}, - rpgLinkStatus_ {0}, - interpanelLinkStatus_ {0}, - performanceCheckTime_ {0}, - version_ {0} - { - } - ~PerformanceMaintenanceDataImpl() = default; + explicit Impl() = default; + ~Impl() = default; + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; // Communications - uint16_t loopBackSetStatus_; - uint32_t t1OutputFrames_; - uint32_t t1InputFrames_; - uint32_t routerMemoryUsed_; - uint32_t routerMemoryFree_; - uint16_t routerMemoryUtilization_; - uint16_t routeToRpg_; - uint32_t csuLossOfSignal_; - uint32_t csuLossOfFrames_; - uint32_t csuYellowAlarms_; - uint32_t csuBlueAlarms_; - uint32_t csu24HrErroredSeconds_; - uint32_t csu24HrSeverelyErroredSeconds_; - uint32_t csu24HrSeverelyErroredFramingSeconds_; - uint32_t csu24HrUnavailableSeconds_; - uint32_t csu24HrControlledSlipSeconds_; - uint32_t csu24HrPathCodingViolations_; - uint32_t csu24HrLineErroredSeconds_; - uint32_t csu24HrBurstyErroredSeconds_; - uint32_t csu24HrDegradedMinutes_; - uint32_t lanSwitchCpuUtilization_; - uint16_t lanSwitchMemoryUtilization_; - uint16_t ifdrChasisTemperature_; - uint16_t ifdrFpgaTemperature_; - int32_t gpsSatellites_; - uint16_t ipcStatus_; - uint16_t commandedChannelControl_; + std::uint16_t loopBackSetStatus_ {0}; + std::uint32_t t1OutputFrames_ {0}; + std::uint32_t t1InputFrames_ {0}; + std::uint32_t routerMemoryUsed_ {0}; + std::uint32_t routerMemoryFree_ {0}; + std::uint16_t routerMemoryUtilization_ {0}; + std::uint16_t routeToRpg_ {0}; + std::uint16_t t1PortStatus_ {0}; + std::uint16_t routerDedicatedEthernetPortStatus_ {0}; + std::uint16_t routerCommercialEthernetPortStatus_ {0}; + std::uint32_t csu24HrErroredSeconds_ {0}; + std::uint32_t csu24HrSeverelyErroredSeconds_ {0}; + std::uint32_t csu24HrSeverelyErroredFramingSeconds_ {0}; + std::uint32_t csu24HrUnavailableSeconds_ {0}; + std::uint32_t csu24HrControlledSlipSeconds_ {0}; + std::uint32_t csu24HrPathCodingViolations_ {0}; + std::uint32_t csu24HrLineErroredSeconds_ {0}; + std::uint32_t csu24HrBurstyErroredSeconds_ {0}; + std::uint32_t csu24HrDegradedMinutes_ {0}; + std::uint32_t lanSwitchCpuUtilization_ {0}; + std::uint16_t lanSwitchMemoryUtilization_ {0}; + std::uint16_t ifdrChasisTemperature_ {0}; + std::uint16_t ifdrFpgaTemperature_ {0}; + std::uint16_t ntpStatus_ {0}; + std::uint16_t ipcStatus_ {0}; + std::uint16_t commandedChannelControl_ {0}; // AME - uint16_t polarization_; - float ameInternalTemperature_; - float ameReceiverModuleTemperature_; - float ameBiteCalModuleTemperature_; - uint16_t amePeltierPulseWidthModulation_; - uint16_t amePeltierStatus_; - uint16_t ameADConverterStatus_; - uint16_t ameState_; - float ame3_3VPsVoltage_; - float ame5VPsVoltage_; - float ame6_5VPsVoltage_; - float ame15VPsVoltage_; - float ame48VPsVoltage_; - float ameStaloPower_; - float peltierCurrent_; - float adcCalibrationReferenceVoltage_; - uint16_t ameMode_; - uint16_t amePeltierMode_; - float amePeltierInsideFanCurrent_; - float amePeltierOutsideFanCurrent_; - float horizontalTrLimiterVoltage_; - float verticalTrLimiterVoltage_; - float adcCalibrationOffsetVoltage_; - float adcCalibrationGainCorrection_; + std::uint16_t polarization_ {0}; + float ameInternalTemperature_ {0.0f}; + float ameReceiverModuleTemperature_ {0.0f}; + float ameBiteCalModuleTemperature_ {0.0f}; + std::uint16_t amePeltierPulseWidthModulation_ {0}; + std::uint16_t amePeltierStatus_ {0}; + std::uint16_t ameADConverterStatus_ {0}; + std::uint16_t ameState_ {0}; + float ame3_3VPsVoltage_ {0.0f}; + float ame5VPsVoltage_ {0.0f}; + float ame6_5VPsVoltage_ {0.0f}; + float ame15VPsVoltage_ {0.0f}; + float ame48VPsVoltage_ {0.0f}; + float ameStaloPower_ {0.0f}; + float peltierCurrent_ {0.0f}; + float adcCalibrationReferenceVoltage_ {0.0f}; + std::uint16_t ameMode_ {0}; + std::uint16_t amePeltierMode_ {0}; + float amePeltierInsideFanCurrent_ {0.0f}; + float amePeltierOutsideFanCurrent_ {0.0f}; + float horizontalTrLimiterVoltage_ {0.0f}; + float verticalTrLimiterVoltage_ {0.0f}; + float adcCalibrationOffsetVoltage_ {0.0f}; + float adcCalibrationGainCorrection_ {0.0f}; // RCP/SPIP Power Button Status - uint16_t rcpStatus_; - std::string rcpString_; - uint16_t spipPowerButtons_; + std::uint16_t rcpStatus_ {0}; + std::string rcpString_ {}; + std::uint16_t spipPowerButtons_ {0}; // Power - float masterPowerAdministratorLoad_; - float expansionPowerAdministratorLoad_; + float masterPowerAdministratorLoad_ {0.0f}; + float expansionPowerAdministratorLoad_ {0.0f}; // Transmitter - uint16_t _5VdcPs_; - uint16_t _15VdcPs_; - uint16_t _28VdcPs_; - uint16_t neg15VdcPs_; - uint16_t _45VdcPs_; - uint16_t filamentPsVoltage_; - uint16_t vacuumPumpPsVoltage_; - uint16_t focusCoilPsVoltage_; - uint16_t filamentPs_; - uint16_t klystronWarmup_; - uint16_t transmitterAvailable_; - uint16_t wgSwitchPosition_; - uint16_t wgPfnTransferInterlock_; - uint16_t maintenanceMode_; - uint16_t maintenanceRequired_; - uint16_t pfnSwitchPosition_; - uint16_t modulatorOverload_; - uint16_t modulatorInvCurrent_; - uint16_t modulatorSwitchFail_; - uint16_t mainPowerVoltage_; - uint16_t chargingSystemFail_; - uint16_t inverseDiodeCurrent_; - uint16_t triggerAmplifier_; - uint16_t circulatorTemperature_; - uint16_t spectrumFilterPressure_; - uint16_t wgArcVswr_; - uint16_t cabinetInterlock_; - uint16_t cabinetAirTemperature_; - uint16_t cabinetAirflow_; - uint16_t klystronCurrent_; - uint16_t klystronFilamentCurrent_; - uint16_t klystronVacionCurrent_; - uint16_t klystronAirTemperature_; - uint16_t klystronAirflow_; - uint16_t modulatorSwitchMaintenance_; - uint16_t postChargeRegulatorMaintenance_; - uint16_t wgPressureHumidity_; - uint16_t transmitterOvervoltage_; - uint16_t transmitterOvercurrent_; - uint16_t focusCoilCurrent_; - uint16_t focusCoilAirflow_; - uint16_t oilTemperature_; - uint16_t prfLimit_; - uint16_t transmitterOilLevel_; - uint16_t transmitterBatteryCharging_; - uint16_t highVoltageStatus_; - uint16_t transmitterRecyclingSummary_; - uint16_t transmitterInoperable_; - uint16_t transmitterAirFilter_; - std::array zeroTestBit_; - std::array oneTestBit_; - uint16_t xmtrSpipInterface_; - uint16_t transmitterSummaryStatus_; - float transmitterRfPower_; - float horizontalXmtrPeakPower_; - float xmtrPeakPower_; - float verticalXmtrPeakPower_; - float xmtrRfAvgPower_; - uint32_t xmtrRecycleCount_; - float receiverBias_; - float transmitImbalance_; - float xmtrPowerMeterZero_; + std::uint16_t _5VdcPs_ {0}; + std::uint16_t _15VdcPs_ {0}; + std::uint16_t _28VdcPs_ {0}; + std::uint16_t neg15VdcPs_ {0}; + std::uint16_t _45VdcPs_ {0}; + std::uint16_t filamentPsVoltage_ {0}; + std::uint16_t vacuumPumpPsVoltage_ {0}; + std::uint16_t focusCoilPsVoltage_ {0}; + std::uint16_t filamentPs_ {0}; + std::uint16_t klystronWarmup_ {0}; + std::uint16_t transmitterAvailable_ {0}; + std::uint16_t wgSwitchPosition_ {0}; + std::uint16_t wgPfnTransferInterlock_ {0}; + std::uint16_t maintenanceMode_ {0}; + std::uint16_t maintenanceRequired_ {0}; + std::uint16_t pfnSwitchPosition_ {0}; + std::uint16_t modulatorOverload_ {0}; + std::uint16_t modulatorInvCurrent_ {0}; + std::uint16_t modulatorSwitchFail_ {0}; + std::uint16_t mainPowerVoltage_ {0}; + std::uint16_t chargingSystemFail_ {0}; + std::uint16_t inverseDiodeCurrent_ {0}; + std::uint16_t triggerAmplifier_ {0}; + std::uint16_t circulatorTemperature_ {0}; + std::uint16_t spectrumFilterPressure_ {0}; + std::uint16_t wgArcVswr_ {0}; + std::uint16_t cabinetInterlock_ {0}; + std::uint16_t cabinetAirTemperature_ {0}; + std::uint16_t cabinetAirflow_ {0}; + std::uint16_t klystronCurrent_ {0}; + std::uint16_t klystronFilamentCurrent_ {0}; + std::uint16_t klystronVacionCurrent_ {0}; + std::uint16_t klystronAirTemperature_ {0}; + std::uint16_t klystronAirflow_ {0}; + std::uint16_t modulatorSwitchMaintenance_ {0}; + std::uint16_t postChargeRegulatorMaintenance_ {0}; + std::uint16_t wgPressureHumidity_ {0}; + std::uint16_t transmitterOvervoltage_ {0}; + std::uint16_t transmitterOvercurrent_ {0}; + std::uint16_t focusCoilCurrent_ {0}; + std::uint16_t focusCoilAirflow_ {0}; + std::uint16_t oilTemperature_ {0}; + std::uint16_t prfLimit_ {0}; + std::uint16_t transmitterOilLevel_ {0}; + std::uint16_t transmitterBatteryCharging_ {0}; + std::uint16_t highVoltageStatus_ {0}; + std::uint16_t transmitterRecyclingSummary_ {0}; + std::uint16_t transmitterInoperable_ {0}; + std::uint16_t transmitterAirFilter_ {0}; + + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + std::array zeroTestBit_ {0}; + std::array oneTestBit_ {0}; + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + + std::uint16_t xmtrSpipInterface_ {0}; + std::uint16_t transmitterSummaryStatus_ {0}; + float transmitterRfPower_ {0.0f}; + float horizontalXmtrPeakPower_ {0.0f}; + float xmtrPeakPower_ {0.0f}; + float verticalXmtrPeakPower_ {0.0f}; + float xmtrRfAvgPower_ {0.0f}; + std::uint32_t xmtrRecycleCount_ {0}; + float receiverBias_ {0.0f}; + float transmitImbalance_ {0.0f}; + float xmtrPowerMeterZero_ {0.0f}; // Tower/Utilities - uint16_t acUnit1CompressorShutOff_; - uint16_t acUnit2CompressorShutOff_; - uint16_t generatorMaintenanceRequired_; - uint16_t generatorBatteryVoltage_; - uint16_t generatorEngine_; - uint16_t generatorVoltFrequency_; - uint16_t powerSource_; - uint16_t transitionalPowerSource_; - uint16_t generatorAutoRunOffSwitch_; - uint16_t aircraftHazardLighting_; + std::uint16_t acUnit1CompressorShutOff_ {0}; + std::uint16_t acUnit2CompressorShutOff_ {0}; + std::uint16_t generatorMaintenanceRequired_ {0}; + std::uint16_t generatorBatteryVoltage_ {0}; + std::uint16_t generatorEngine_ {0}; + std::uint16_t generatorVoltFrequency_ {0}; + std::uint16_t powerSource_ {0}; + std::uint16_t transitionalPowerSource_ {0}; + std::uint16_t generatorAutoRunOffSwitch_ {0}; + std::uint16_t aircraftHazardLighting_ {0}; // Equipment Shelter - uint16_t equipmentShelterFireDetectionSystem_; - uint16_t equipmentShelterFireSmoke_; - uint16_t generatorShelterFireSmoke_; - uint16_t utilityVoltageFrequency_; - uint16_t siteSecurityAlarm_; - uint16_t securityEquipment_; - uint16_t securitySystem_; - uint16_t receiverConnectedToAntenna_; - uint16_t radomeHatch_; - uint16_t acUnit1FilterDirty_; - uint16_t acUnit2FilterDirty_; - float equipmentShelterTemperature_; - float outsideAmbientTemperature_; - float transmitterLeavingAirTemp_; - float acUnit1DischargeAirTemp_; - float generatorShelterTemperature_; - float radomeAirTemperature_; - float acUnit2DischargeAirTemp_; - float spip15VPs_; - float spipNeg15VPs_; - uint16_t spip28VPsStatus_; - float spip5VPs_; - uint16_t convertedGeneratorFuelLevel_; + std::uint16_t equipmentShelterFireDetectionSystem_ {0}; + std::uint16_t equipmentShelterFireSmoke_ {0}; + std::uint16_t generatorShelterFireSmoke_ {0}; + std::uint16_t utilityVoltageFrequency_ {0}; + std::uint16_t siteSecurityAlarm_ {0}; + std::uint16_t securityEquipment_ {0}; + std::uint16_t securitySystem_ {0}; + std::uint16_t receiverConnectedToAntenna_ {0}; + std::uint16_t radomeHatch_ {0}; + std::uint16_t acUnit1FilterDirty_ {0}; + std::uint16_t acUnit2FilterDirty_ {0}; + float equipmentShelterTemperature_ {0.0f}; + float outsideAmbientTemperature_ {0.0f}; + float transmitterLeavingAirTemp_ {0.0f}; + float acUnit1DischargeAirTemp_ {0.0f}; + float generatorShelterTemperature_ {0.0f}; + float radomeAirTemperature_ {0.0f}; + float acUnit2DischargeAirTemp_ {0.0f}; + float spip15VPs_ {0.0f}; + float spipNeg15VPs_ {0.0f}; + std::uint16_t spip28VPsStatus_ {0}; + float spip5VPs_ {0.0f}; + std::uint16_t convertedGeneratorFuelLevel_ {0}; // Antenna/Pedestal - uint16_t elevationPosDeadLimit_; - uint16_t _150VOvervoltage_; - uint16_t _150VUndervoltage_; - uint16_t elevationServoAmpInhibit_; - uint16_t elevationServoAmpShortCircuit_; - uint16_t elevationServoAmpOvertemp_; - uint16_t elevationMotorOvertemp_; - uint16_t elevationStowPin_; - uint16_t elevationHousing5VPs_; - uint16_t elevationNegDeadLimit_; - uint16_t elevationPosNormalLimit_; - uint16_t elevationNegNormalLimit_; - uint16_t elevationEncoderLight_; - uint16_t elevationGearboxOil_; - uint16_t elevationHandwheel_; - uint16_t elevationAmpPs_; - uint16_t azimuthServoAmpInhibit_; - uint16_t azimuthServoAmpShortCircuit_; - uint16_t azimuthServoAmpOvertemp_; - uint16_t azimuthMotorOvertemp_; - uint16_t azimuthStowPin_; - uint16_t azimuthHousing5VPs_; - uint16_t azimuthEncoderLight_; - uint16_t azimuthGearboxOil_; - uint16_t azimuthBullGearOil_; - uint16_t azimuthHandwheel_; - uint16_t azimuthServoAmpPs_; - uint16_t servo_; - uint16_t pedestalInterlockSwitch_; + std::uint16_t elevationPosDeadLimit_ {0}; + std::uint16_t _150VOvervoltage_ {0}; + std::uint16_t _150VUndervoltage_ {0}; + std::uint16_t elevationServoAmpInhibit_ {0}; + std::uint16_t elevationServoAmpShortCircuit_ {0}; + std::uint16_t elevationServoAmpOvertemp_ {0}; + std::uint16_t elevationMotorOvertemp_ {0}; + std::uint16_t elevationStowPin_ {0}; + std::uint16_t elevationHousing5VPs_ {0}; + std::uint16_t elevationNegDeadLimit_ {0}; + std::uint16_t elevationPosNormalLimit_ {0}; + std::uint16_t elevationNegNormalLimit_ {0}; + std::uint16_t elevationEncoderLight_ {0}; + std::uint16_t elevationGearboxOil_ {0}; + std::uint16_t elevationHandwheel_ {0}; + std::uint16_t elevationAmpPs_ {0}; + std::uint16_t azimuthServoAmpInhibit_ {0}; + std::uint16_t azimuthServoAmpShortCircuit_ {0}; + std::uint16_t azimuthServoAmpOvertemp_ {0}; + std::uint16_t azimuthMotorOvertemp_ {0}; + std::uint16_t azimuthStowPin_ {0}; + std::uint16_t azimuthHousing5VPs_ {0}; + std::uint16_t azimuthEncoderLight_ {0}; + std::uint16_t azimuthGearboxOil_ {0}; + std::uint16_t azimuthBullGearOil_ {0}; + std::uint16_t azimuthHandwheel_ {0}; + std::uint16_t azimuthServoAmpPs_ {0}; + std::uint16_t servo_ {0}; + std::uint16_t pedestalInterlockSwitch_ {0}; // RF Generator/Receiver - uint16_t cohoClock_; - uint16_t rfGeneratorFrequencySelectOscillator_; - uint16_t rfGeneratorRfStalo_; - uint16_t rfGeneratorPhaseShiftedCoho_; - uint16_t _9VReceiverPs_; - uint16_t _5VReceiverPs_; - uint16_t _18VReceiverPs_; - uint16_t neg9VReceiverPs_; - uint16_t _5VSingleChannelRdaiuPs_; - float horizontalShortPulseNoise_; - float horizontalLongPulseNoise_; - float horizontalNoiseTemperature_; - float verticalShortPulseNoise_; - float verticalLongPulseNoise_; - float verticalNoiseTemperature_; + std::uint16_t cohoClock_ {0}; + std::uint16_t rfGeneratorFrequencySelectOscillator_ {0}; + std::uint16_t rfGeneratorRfStalo_ {0}; + std::uint16_t rfGeneratorPhaseShiftedCoho_ {0}; + std::uint16_t _9VReceiverPs_ {0}; + std::uint16_t _5VReceiverPs_ {0}; + std::uint16_t _18VReceiverPs_ {0}; + std::uint16_t neg9VReceiverPs_ {0}; + std::uint16_t _5VSingleChannelRdaiuPs_ {0}; + float horizontalShortPulseNoise_ {0.0f}; + float horizontalLongPulseNoise_ {0.0f}; + float horizontalNoiseTemperature_ {0.0f}; + float verticalShortPulseNoise_ {0.0f}; + float verticalLongPulseNoise_ {0.0f}; + float verticalNoiseTemperature_ {0.0f}; // Calibration - float horizontalLinearity_; - float horizontalDynamicRange_; - float horizontalDeltaDbz0_; - float verticalDeltaDbz0_; - float kdPeakMeasured_; - float shortPulseHorizontalDbz0_; - float longPulseHorizontalDbz0_; - uint16_t velocityProcessed_; - uint16_t widthProcessed_; - uint16_t velocityRfGen_; - uint16_t widthRfGen_; - float horizontalI0_; - float verticalI0_; - float verticalDynamicRange_; - float shortPulseVerticalDbz0_; - float longPulseVerticalDbz0_; - float horizontalPowerSense_; - float verticalPowerSense_; - float zdrBias_; - float clutterSuppressionDelta_; - float clutterSuppressionUnfilteredPower_; - float clutterSuppressionFilteredPower_; - float verticalLinearity_; + float horizontalLinearity_ {0.0f}; + float horizontalDynamicRange_ {0.0f}; + float horizontalDeltaDbz0_ {0.0f}; + float verticalDeltaDbz0_ {0.0f}; + float kdPeakMeasured_ {0.0f}; + float shortPulseHorizontalDbz0_ {0.0f}; + float longPulseHorizontalDbz0_ {0.0f}; + std::uint16_t velocityProcessed_ {0}; + std::uint16_t widthProcessed_ {0}; + std::uint16_t velocityRfGen_ {0}; + std::uint16_t widthRfGen_ {0}; + float horizontalI0_ {0.0f}; + float verticalI0_ {0.0f}; + float verticalDynamicRange_ {0.0f}; + float shortPulseVerticalDbz0_ {0.0f}; + float longPulseVerticalDbz0_ {0.0f}; + float horizontalPowerSense_ {0.0f}; + float verticalPowerSense_ {0.0f}; + float zdrOffset_ {0.0f}; + float clutterSuppressionDelta_ {0.0f}; + float clutterSuppressionUnfilteredPower_ {0.0f}; + float clutterSuppressionFilteredPower_ {0.0f}; + float verticalLinearity_ {0.0f}; // File Status - uint16_t stateFileReadStatus_; - uint16_t stateFileWriteStatus_; - uint16_t bypassMapFileReadStatus_; - uint16_t bypassMapFileWriteStatus_; - uint16_t currentAdaptationFileReadStatus_; - uint16_t currentAdaptationFileWriteStatus_; - uint16_t censorZoneFileReadStatus_; - uint16_t censorZoneFileWriteStatus_; - uint16_t remoteVcpFileReadStatus_; - uint16_t remoteVcpFileWriteStatus_; - uint16_t baselineAdaptationFileReadStatus_; - uint16_t readStatusOfPrfSets_; - uint16_t clutterFilterMapFileReadStatus_; - uint16_t clutterFilterMapFileWriteStatus_; - uint16_t generatlDiskIoError_; - uint8_t rspStatus_; - uint8_t motherboardTemperature_; - uint8_t cpu1Temperature_; - uint8_t cpu2Temperature_; - uint16_t cpu1FanSpeed_; - uint16_t cpu2FanSpeed_; - uint16_t rspFan1Speed_; - uint16_t rspFan2Speed_; - uint16_t rspFan3Speed_; + std::uint16_t stateFileReadStatus_ {0}; + std::uint16_t stateFileWriteStatus_ {0}; + std::uint16_t bypassMapFileReadStatus_ {0}; + std::uint16_t bypassMapFileWriteStatus_ {0}; + std::uint16_t currentAdaptationFileReadStatus_ {0}; + std::uint16_t currentAdaptationFileWriteStatus_ {0}; + std::uint16_t censorZoneFileReadStatus_ {0}; + std::uint16_t censorZoneFileWriteStatus_ {0}; + std::uint16_t remoteVcpFileReadStatus_ {0}; + std::uint16_t remoteVcpFileWriteStatus_ {0}; + std::uint16_t baselineAdaptationFileReadStatus_ {0}; + std::uint16_t readStatusOfPrfSets_ {0}; + std::uint16_t clutterFilterMapFileReadStatus_ {0}; + std::uint16_t clutterFilterMapFileWriteStatus_ {0}; + std::uint16_t generalDiskIoError_ {0}; + std::uint8_t rspStatus_ {0}; + std::uint8_t cpu1Temperature_ {0}; + std::uint8_t cpu2Temperature_ {0}; + std::uint16_t rspMotherboardPower_ {0}; // Device Status - uint16_t spipCommStatus_; - uint16_t hciCommStatus_; - uint16_t signalProcessorCommandStatus_; - uint16_t ameCommunicationStatus_; - uint16_t rmsLinkStatus_; - uint16_t rpgLinkStatus_; - uint16_t interpanelLinkStatus_; - uint32_t performanceCheckTime_; - uint16_t version_; + std::uint16_t spipCommStatus_ {0}; + std::uint16_t hciCommStatus_ {0}; + std::uint16_t signalProcessorCommandStatus_ {0}; + std::uint16_t ameCommunicationStatus_ {0}; + std::uint16_t rmsLinkStatus_ {0}; + std::uint16_t rpgLinkStatus_ {0}; + std::uint16_t interpanelLinkStatus_ {0}; + std::uint32_t performanceCheckTime_ {0}; + std::uint16_t version_ {0}; }; PerformanceMaintenanceData::PerformanceMaintenanceData() : - Level2Message(), p(std::make_unique()) + Level2Message(), p(std::make_unique()) { } PerformanceMaintenanceData::~PerformanceMaintenanceData() = default; PerformanceMaintenanceData::PerformanceMaintenanceData( - PerformanceMaintenanceData&&) noexcept = default; + PerformanceMaintenanceData&&) noexcept = default; PerformanceMaintenanceData& PerformanceMaintenanceData::operator=( PerformanceMaintenanceData&&) noexcept = default; -uint16_t PerformanceMaintenanceData::loop_back_set_status() const +std::uint16_t PerformanceMaintenanceData::loop_back_set_status() const { return p->loopBackSetStatus_; } -uint32_t PerformanceMaintenanceData::t1_output_frames() const +std::uint32_t PerformanceMaintenanceData::t1_output_frames() const { return p->t1OutputFrames_; } -uint32_t PerformanceMaintenanceData::t1_input_frames() const +std::uint32_t PerformanceMaintenanceData::t1_input_frames() const { return p->t1InputFrames_; } -uint32_t PerformanceMaintenanceData::router_memory_used() const +std::uint32_t PerformanceMaintenanceData::router_memory_used() const { return p->routerMemoryUsed_; } -uint32_t PerformanceMaintenanceData::router_memory_free() const +std::uint32_t PerformanceMaintenanceData::router_memory_free() const { return p->routerMemoryFree_; } -uint16_t PerformanceMaintenanceData::router_memory_utilization() const +std::uint16_t PerformanceMaintenanceData::router_memory_utilization() const { return p->routerMemoryUtilization_; } -uint16_t PerformanceMaintenanceData::route_to_rpg() const +std::uint16_t PerformanceMaintenanceData::route_to_rpg() const { return p->routeToRpg_; } -uint32_t PerformanceMaintenanceData::csu_loss_of_signal() const +std::uint16_t PerformanceMaintenanceData::t1_port_status() const { - return p->csuLossOfSignal_; + return p->t1PortStatus_; } -uint32_t PerformanceMaintenanceData::csu_loss_of_frames() const +std::uint16_t +PerformanceMaintenanceData::router_dedicated_ethernet_port_status() const { - return p->csuLossOfFrames_; + return p->routerDedicatedEthernetPortStatus_; } -uint32_t PerformanceMaintenanceData::csu_yellow_alarms() const +std::uint16_t +PerformanceMaintenanceData::router_commercial_ethernet_port_status() const { - return p->csuYellowAlarms_; + return p->routerCommercialEthernetPortStatus_; } -uint32_t PerformanceMaintenanceData::csu_blue_alarms() const -{ - return p->csuBlueAlarms_; -} - -uint32_t PerformanceMaintenanceData::csu_24hr_errored_seconds() const +std::uint32_t PerformanceMaintenanceData::csu_24hr_errored_seconds() const { return p->csu24HrErroredSeconds_; } -uint32_t PerformanceMaintenanceData::csu_24hr_severely_errored_seconds() const +std::uint32_t +PerformanceMaintenanceData::csu_24hr_severely_errored_seconds() const { return p->csu24HrSeverelyErroredSeconds_; } -uint32_t +std::uint32_t PerformanceMaintenanceData::csu_24hr_severely_errored_framing_seconds() const { return p->csu24HrSeverelyErroredFramingSeconds_; } -uint32_t PerformanceMaintenanceData::csu_24hr_unavailable_seconds() const +std::uint32_t PerformanceMaintenanceData::csu_24hr_unavailable_seconds() const { return p->csu24HrUnavailableSeconds_; } -uint32_t PerformanceMaintenanceData::csu_24hr_controlled_slip_seconds() const +std::uint32_t +PerformanceMaintenanceData::csu_24hr_controlled_slip_seconds() const { return p->csu24HrControlledSlipSeconds_; } -uint32_t PerformanceMaintenanceData::csu_24hr_path_coding_violations() const +std::uint32_t +PerformanceMaintenanceData::csu_24hr_path_coding_violations() const { return p->csu24HrPathCodingViolations_; } -uint32_t PerformanceMaintenanceData::csu_24hr_line_errored_seconds() const +std::uint32_t PerformanceMaintenanceData::csu_24hr_line_errored_seconds() const { return p->csu24HrLineErroredSeconds_; } -uint32_t PerformanceMaintenanceData::csu_24hr_bursty_errored_seconds() const +std::uint32_t +PerformanceMaintenanceData::csu_24hr_bursty_errored_seconds() const { return p->csu24HrBurstyErroredSeconds_; } -uint32_t PerformanceMaintenanceData::csu_24hr_degraded_minutes() const +std::uint32_t PerformanceMaintenanceData::csu_24hr_degraded_minutes() const { return p->csu24HrDegradedMinutes_; } -uint32_t PerformanceMaintenanceData::lan_switch_cpu_utilization() const +std::uint32_t PerformanceMaintenanceData::lan_switch_cpu_utilization() const { return p->lanSwitchCpuUtilization_; } -uint16_t PerformanceMaintenanceData::lan_switch_memory_utilization() const +std::uint16_t PerformanceMaintenanceData::lan_switch_memory_utilization() const { return p->lanSwitchMemoryUtilization_; } -uint16_t PerformanceMaintenanceData::ifdr_chasis_temperature() const +std::uint16_t PerformanceMaintenanceData::ifdr_chasis_temperature() const { return p->ifdrChasisTemperature_; } -uint16_t PerformanceMaintenanceData::ifdr_fpga_temperature() const +std::uint16_t PerformanceMaintenanceData::ifdr_fpga_temperature() const { return p->ifdrFpgaTemperature_; } -int32_t PerformanceMaintenanceData::gps_satellites() const +std::uint16_t PerformanceMaintenanceData::ntp_status() const { - return p->gpsSatellites_; + return p->ntpStatus_; } -uint16_t PerformanceMaintenanceData::ipc_status() const +std::uint16_t PerformanceMaintenanceData::ipc_status() const { return p->ipcStatus_; } -uint16_t PerformanceMaintenanceData::commanded_channel_control() const +std::uint16_t PerformanceMaintenanceData::commanded_channel_control() const { return p->commandedChannelControl_; } -uint16_t PerformanceMaintenanceData::polarization() const +std::uint16_t PerformanceMaintenanceData::polarization() const { return p->polarization_; } @@ -716,22 +463,23 @@ float PerformanceMaintenanceData::ame_bite_cal_module_temperature() const return p->ameBiteCalModuleTemperature_; } -uint16_t PerformanceMaintenanceData::ame_peltier_pulse_width_modulation() const +std::uint16_t +PerformanceMaintenanceData::ame_peltier_pulse_width_modulation() const { return p->amePeltierPulseWidthModulation_; } -uint16_t PerformanceMaintenanceData::ame_peltier_status() const +std::uint16_t PerformanceMaintenanceData::ame_peltier_status() const { return p->amePeltierStatus_; } -uint16_t PerformanceMaintenanceData::ame_a_d_converter_status() const +std::uint16_t PerformanceMaintenanceData::ame_a_d_converter_status() const { return p->ameADConverterStatus_; } -uint16_t PerformanceMaintenanceData::ame_state() const +std::uint16_t PerformanceMaintenanceData::ame_state() const { return p->ameState_; } @@ -776,12 +524,12 @@ float PerformanceMaintenanceData::adc_calibration_reference_voltage() const return p->adcCalibrationReferenceVoltage_; } -uint16_t PerformanceMaintenanceData::ame_mode() const +std::uint16_t PerformanceMaintenanceData::ame_mode() const { return p->ameMode_; } -uint16_t PerformanceMaintenanceData::ame_peltier_mode() const +std::uint16_t PerformanceMaintenanceData::ame_peltier_mode() const { return p->amePeltierMode_; } @@ -816,7 +564,7 @@ float PerformanceMaintenanceData::adc_calibration_gain_correction() const return p->adcCalibrationGainCorrection_; } -uint16_t PerformanceMaintenanceData::rcp_status() const +std::uint16_t PerformanceMaintenanceData::rcp_status() const { return p->rcpStatus_; } @@ -826,7 +574,7 @@ std::string PerformanceMaintenanceData::rcp_string() const return p->rcpString_; } -uint16_t PerformanceMaintenanceData::spip_power_buttons() const +std::uint16_t PerformanceMaintenanceData::spip_power_buttons() const { return p->spipPowerButtons_; } @@ -841,267 +589,268 @@ float PerformanceMaintenanceData::expansion_power_administrator_load() const return p->expansionPowerAdministratorLoad_; } -uint16_t PerformanceMaintenanceData::_5vdc_ps() const +std::uint16_t PerformanceMaintenanceData::_5vdc_ps() const { return p->_5VdcPs_; } -uint16_t PerformanceMaintenanceData::_15vdc_ps() const +std::uint16_t PerformanceMaintenanceData::_15vdc_ps() const { return p->_15VdcPs_; } -uint16_t PerformanceMaintenanceData::_28vdc_ps() const +std::uint16_t PerformanceMaintenanceData::_28vdc_ps() const { return p->_28VdcPs_; } -uint16_t PerformanceMaintenanceData::neg_15vdc_ps() const +std::uint16_t PerformanceMaintenanceData::neg_15vdc_ps() const { return p->neg15VdcPs_; } -uint16_t PerformanceMaintenanceData::_45vdc_ps() const +std::uint16_t PerformanceMaintenanceData::_45vdc_ps() const { return p->_45VdcPs_; } -uint16_t PerformanceMaintenanceData::filament_ps_voltage() const +std::uint16_t PerformanceMaintenanceData::filament_ps_voltage() const { return p->filamentPsVoltage_; } -uint16_t PerformanceMaintenanceData::vacuum_pump_ps_voltage() const +std::uint16_t PerformanceMaintenanceData::vacuum_pump_ps_voltage() const { return p->vacuumPumpPsVoltage_; } -uint16_t PerformanceMaintenanceData::focus_coil_ps_voltage() const +std::uint16_t PerformanceMaintenanceData::focus_coil_ps_voltage() const { return p->focusCoilPsVoltage_; } -uint16_t PerformanceMaintenanceData::filament_ps() const +std::uint16_t PerformanceMaintenanceData::filament_ps() const { return p->filamentPs_; } -uint16_t PerformanceMaintenanceData::klystron_warmup() const +std::uint16_t PerformanceMaintenanceData::klystron_warmup() const { return p->klystronWarmup_; } -uint16_t PerformanceMaintenanceData::transmitter_available() const +std::uint16_t PerformanceMaintenanceData::transmitter_available() const { return p->transmitterAvailable_; } -uint16_t PerformanceMaintenanceData::wg_switch_position() const +std::uint16_t PerformanceMaintenanceData::wg_switch_position() const { return p->wgSwitchPosition_; } -uint16_t PerformanceMaintenanceData::wg_pfn_transfer_interlock() const +std::uint16_t PerformanceMaintenanceData::wg_pfn_transfer_interlock() const { return p->wgPfnTransferInterlock_; } -uint16_t PerformanceMaintenanceData::maintenance_mode() const +std::uint16_t PerformanceMaintenanceData::maintenance_mode() const { return p->maintenanceMode_; } -uint16_t PerformanceMaintenanceData::maintenance_required() const +std::uint16_t PerformanceMaintenanceData::maintenance_required() const { return p->maintenanceRequired_; } -uint16_t PerformanceMaintenanceData::pfn_switch_position() const +std::uint16_t PerformanceMaintenanceData::pfn_switch_position() const { return p->pfnSwitchPosition_; } -uint16_t PerformanceMaintenanceData::modulator_overload() const +std::uint16_t PerformanceMaintenanceData::modulator_overload() const { return p->modulatorOverload_; } -uint16_t PerformanceMaintenanceData::modulator_inv_current() const +std::uint16_t PerformanceMaintenanceData::modulator_inv_current() const { return p->modulatorInvCurrent_; } -uint16_t PerformanceMaintenanceData::modulator_switch_fail() const +std::uint16_t PerformanceMaintenanceData::modulator_switch_fail() const { return p->modulatorSwitchFail_; } -uint16_t PerformanceMaintenanceData::main_power_voltage() const +std::uint16_t PerformanceMaintenanceData::main_power_voltage() const { return p->mainPowerVoltage_; } -uint16_t PerformanceMaintenanceData::charging_system_fail() const +std::uint16_t PerformanceMaintenanceData::charging_system_fail() const { return p->chargingSystemFail_; } -uint16_t PerformanceMaintenanceData::inverse_diode_current() const +std::uint16_t PerformanceMaintenanceData::inverse_diode_current() const { return p->inverseDiodeCurrent_; } -uint16_t PerformanceMaintenanceData::trigger_amplifier() const +std::uint16_t PerformanceMaintenanceData::trigger_amplifier() const { return p->triggerAmplifier_; } -uint16_t PerformanceMaintenanceData::circulator_temperature() const +std::uint16_t PerformanceMaintenanceData::circulator_temperature() const { return p->circulatorTemperature_; } -uint16_t PerformanceMaintenanceData::spectrum_filter_pressure() const +std::uint16_t PerformanceMaintenanceData::spectrum_filter_pressure() const { return p->spectrumFilterPressure_; } -uint16_t PerformanceMaintenanceData::wg_arc_vswr() const +std::uint16_t PerformanceMaintenanceData::wg_arc_vswr() const { return p->wgArcVswr_; } -uint16_t PerformanceMaintenanceData::cabinet_interlock() const +std::uint16_t PerformanceMaintenanceData::cabinet_interlock() const { return p->cabinetInterlock_; } -uint16_t PerformanceMaintenanceData::cabinet_air_temperature() const +std::uint16_t PerformanceMaintenanceData::cabinet_air_temperature() const { return p->cabinetAirTemperature_; } -uint16_t PerformanceMaintenanceData::cabinet_airflow() const +std::uint16_t PerformanceMaintenanceData::cabinet_airflow() const { return p->cabinetAirflow_; } -uint16_t PerformanceMaintenanceData::klystron_current() const +std::uint16_t PerformanceMaintenanceData::klystron_current() const { return p->klystronCurrent_; } -uint16_t PerformanceMaintenanceData::klystron_filament_current() const +std::uint16_t PerformanceMaintenanceData::klystron_filament_current() const { return p->klystronFilamentCurrent_; } -uint16_t PerformanceMaintenanceData::klystron_vacion_current() const +std::uint16_t PerformanceMaintenanceData::klystron_vacion_current() const { return p->klystronVacionCurrent_; } -uint16_t PerformanceMaintenanceData::klystron_air_temperature() const +std::uint16_t PerformanceMaintenanceData::klystron_air_temperature() const { return p->klystronAirTemperature_; } -uint16_t PerformanceMaintenanceData::klystron_airflow() const +std::uint16_t PerformanceMaintenanceData::klystron_airflow() const { return p->klystronAirflow_; } -uint16_t PerformanceMaintenanceData::modulator_switch_maintenance() const +std::uint16_t PerformanceMaintenanceData::modulator_switch_maintenance() const { return p->modulatorSwitchMaintenance_; } -uint16_t PerformanceMaintenanceData::post_charge_regulator_maintenance() const +std::uint16_t +PerformanceMaintenanceData::post_charge_regulator_maintenance() const { return p->postChargeRegulatorMaintenance_; } -uint16_t PerformanceMaintenanceData::wg_pressure_humidity() const +std::uint16_t PerformanceMaintenanceData::wg_pressure_humidity() const { return p->wgPressureHumidity_; } -uint16_t PerformanceMaintenanceData::transmitter_overvoltage() const +std::uint16_t PerformanceMaintenanceData::transmitter_overvoltage() const { return p->transmitterOvervoltage_; } -uint16_t PerformanceMaintenanceData::transmitter_overcurrent() const +std::uint16_t PerformanceMaintenanceData::transmitter_overcurrent() const { return p->transmitterOvercurrent_; } -uint16_t PerformanceMaintenanceData::focus_coil_current() const +std::uint16_t PerformanceMaintenanceData::focus_coil_current() const { return p->focusCoilCurrent_; } -uint16_t PerformanceMaintenanceData::focus_coil_airflow() const +std::uint16_t PerformanceMaintenanceData::focus_coil_airflow() const { return p->focusCoilAirflow_; } -uint16_t PerformanceMaintenanceData::oil_temperature() const +std::uint16_t PerformanceMaintenanceData::oil_temperature() const { return p->oilTemperature_; } -uint16_t PerformanceMaintenanceData::prf_limit() const +std::uint16_t PerformanceMaintenanceData::prf_limit() const { return p->prfLimit_; } -uint16_t PerformanceMaintenanceData::transmitter_oil_level() const +std::uint16_t PerformanceMaintenanceData::transmitter_oil_level() const { return p->transmitterOilLevel_; } -uint16_t PerformanceMaintenanceData::transmitter_battery_charging() const +std::uint16_t PerformanceMaintenanceData::transmitter_battery_charging() const { return p->transmitterBatteryCharging_; } -uint16_t PerformanceMaintenanceData::high_voltage_status() const +std::uint16_t PerformanceMaintenanceData::high_voltage_status() const { return p->highVoltageStatus_; } -uint16_t PerformanceMaintenanceData::transmitter_recycling_summary() const +std::uint16_t PerformanceMaintenanceData::transmitter_recycling_summary() const { return p->transmitterRecyclingSummary_; } -uint16_t PerformanceMaintenanceData::transmitter_inoperable() const +std::uint16_t PerformanceMaintenanceData::transmitter_inoperable() const { return p->transmitterInoperable_; } -uint16_t PerformanceMaintenanceData::transmitter_air_filter() const +std::uint16_t PerformanceMaintenanceData::transmitter_air_filter() const { return p->transmitterAirFilter_; } -uint16_t PerformanceMaintenanceData::zero_test_bit(unsigned i) const +std::uint16_t PerformanceMaintenanceData::zero_test_bit(unsigned i) const { - return p->zeroTestBit_[i]; + return p->zeroTestBit_.at(i); } -uint16_t PerformanceMaintenanceData::one_test_bit(unsigned i) const +std::uint16_t PerformanceMaintenanceData::one_test_bit(unsigned i) const { - return p->oneTestBit_[i]; + return p->oneTestBit_.at(i); } -uint16_t PerformanceMaintenanceData::xmtr_spip_interface() const +std::uint16_t PerformanceMaintenanceData::xmtr_spip_interface() const { return p->xmtrSpipInterface_; } -uint16_t PerformanceMaintenanceData::transmitter_summary_status() const +std::uint16_t PerformanceMaintenanceData::transmitter_summary_status() const { return p->transmitterSummaryStatus_; } @@ -1131,7 +880,7 @@ float PerformanceMaintenanceData::xmtr_rf_avg_power() const return p->xmtrRfAvgPower_; } -uint32_t PerformanceMaintenanceData::xmtr_recycle_count() const +std::uint32_t PerformanceMaintenanceData::xmtr_recycle_count() const { return p->xmtrRecycleCount_; } @@ -1151,108 +900,108 @@ float PerformanceMaintenanceData::xmtr_power_meter_zero() const return p->xmtrPowerMeterZero_; } -uint16_t PerformanceMaintenanceData::ac_unit1_compressor_shut_off() const +std::uint16_t PerformanceMaintenanceData::ac_unit1_compressor_shut_off() const { return p->acUnit1CompressorShutOff_; } -uint16_t PerformanceMaintenanceData::ac_unit2_compressor_shut_off() const +std::uint16_t PerformanceMaintenanceData::ac_unit2_compressor_shut_off() const { return p->acUnit2CompressorShutOff_; } -uint16_t PerformanceMaintenanceData::generator_maintenance_required() const +std::uint16_t PerformanceMaintenanceData::generator_maintenance_required() const { return p->generatorMaintenanceRequired_; } -uint16_t PerformanceMaintenanceData::generator_battery_voltage() const +std::uint16_t PerformanceMaintenanceData::generator_battery_voltage() const { return p->generatorBatteryVoltage_; } -uint16_t PerformanceMaintenanceData::generator_engine() const +std::uint16_t PerformanceMaintenanceData::generator_engine() const { return p->generatorEngine_; } -uint16_t PerformanceMaintenanceData::generator_volt_frequency() const +std::uint16_t PerformanceMaintenanceData::generator_volt_frequency() const { return p->generatorVoltFrequency_; } -uint16_t PerformanceMaintenanceData::power_source() const +std::uint16_t PerformanceMaintenanceData::power_source() const { return p->powerSource_; } -uint16_t PerformanceMaintenanceData::transitional_power_source() const +std::uint16_t PerformanceMaintenanceData::transitional_power_source() const { return p->transitionalPowerSource_; } -uint16_t PerformanceMaintenanceData::generator_auto_run_off_switch() const +std::uint16_t PerformanceMaintenanceData::generator_auto_run_off_switch() const { return p->generatorAutoRunOffSwitch_; } -uint16_t PerformanceMaintenanceData::aircraft_hazard_lighting() const +std::uint16_t PerformanceMaintenanceData::aircraft_hazard_lighting() const { return p->aircraftHazardLighting_; } -uint16_t +std::uint16_t PerformanceMaintenanceData::equipment_shelter_fire_detection_system() const { return p->equipmentShelterFireDetectionSystem_; } -uint16_t PerformanceMaintenanceData::equipment_shelter_fire_smoke() const +std::uint16_t PerformanceMaintenanceData::equipment_shelter_fire_smoke() const { return p->equipmentShelterFireSmoke_; } -uint16_t PerformanceMaintenanceData::generator_shelter_fire_smoke() const +std::uint16_t PerformanceMaintenanceData::generator_shelter_fire_smoke() const { return p->generatorShelterFireSmoke_; } -uint16_t PerformanceMaintenanceData::utility_voltage_frequency() const +std::uint16_t PerformanceMaintenanceData::utility_voltage_frequency() const { return p->utilityVoltageFrequency_; } -uint16_t PerformanceMaintenanceData::site_security_alarm() const +std::uint16_t PerformanceMaintenanceData::site_security_alarm() const { return p->siteSecurityAlarm_; } -uint16_t PerformanceMaintenanceData::security_equipment() const +std::uint16_t PerformanceMaintenanceData::security_equipment() const { return p->securityEquipment_; } -uint16_t PerformanceMaintenanceData::security_system() const +std::uint16_t PerformanceMaintenanceData::security_system() const { return p->securitySystem_; } -uint16_t PerformanceMaintenanceData::receiver_connected_to_antenna() const +std::uint16_t PerformanceMaintenanceData::receiver_connected_to_antenna() const { return p->receiverConnectedToAntenna_; } -uint16_t PerformanceMaintenanceData::radome_hatch() const +std::uint16_t PerformanceMaintenanceData::radome_hatch() const { return p->radomeHatch_; } -uint16_t PerformanceMaintenanceData::ac_unit1_filter_dirty() const +std::uint16_t PerformanceMaintenanceData::ac_unit1_filter_dirty() const { return p->acUnit1FilterDirty_; } -uint16_t PerformanceMaintenanceData::ac_unit2_filter_dirty() const +std::uint16_t PerformanceMaintenanceData::ac_unit2_filter_dirty() const { return p->acUnit2FilterDirty_; } @@ -1302,7 +1051,7 @@ float PerformanceMaintenanceData::spip_neg_15v_ps() const return p->spipNeg15VPs_; } -uint16_t PerformanceMaintenanceData::spip_28v_ps_status() const +std::uint16_t PerformanceMaintenanceData::spip_28v_ps_status() const { return p->spip28VPsStatus_; } @@ -1312,198 +1061,201 @@ float PerformanceMaintenanceData::spip_5v_ps() const return p->spip5VPs_; } -uint16_t PerformanceMaintenanceData::converted_generator_fuel_level() const +std::uint16_t PerformanceMaintenanceData::converted_generator_fuel_level() const { return p->convertedGeneratorFuelLevel_; } -uint16_t PerformanceMaintenanceData::elevation_pos_dead_limit() const +std::uint16_t PerformanceMaintenanceData::elevation_pos_dead_limit() const { return p->elevationPosDeadLimit_; } -uint16_t PerformanceMaintenanceData::_150v_overvoltage() const +std::uint16_t PerformanceMaintenanceData::_150v_overvoltage() const { return p->_150VOvervoltage_; } -uint16_t PerformanceMaintenanceData::_150v_undervoltage() const +std::uint16_t PerformanceMaintenanceData::_150v_undervoltage() const { return p->_150VUndervoltage_; } -uint16_t PerformanceMaintenanceData::elevation_servo_amp_inhibit() const +std::uint16_t PerformanceMaintenanceData::elevation_servo_amp_inhibit() const { return p->elevationServoAmpInhibit_; } -uint16_t PerformanceMaintenanceData::elevation_servo_amp_short_circuit() const +std::uint16_t +PerformanceMaintenanceData::elevation_servo_amp_short_circuit() const { return p->elevationServoAmpShortCircuit_; } -uint16_t PerformanceMaintenanceData::elevation_servo_amp_overtemp() const +std::uint16_t PerformanceMaintenanceData::elevation_servo_amp_overtemp() const { return p->elevationServoAmpOvertemp_; } -uint16_t PerformanceMaintenanceData::elevation_motor_overtemp() const +std::uint16_t PerformanceMaintenanceData::elevation_motor_overtemp() const { return p->elevationMotorOvertemp_; } -uint16_t PerformanceMaintenanceData::elevation_stow_pin() const +std::uint16_t PerformanceMaintenanceData::elevation_stow_pin() const { return p->elevationStowPin_; } -uint16_t PerformanceMaintenanceData::elevation_housing_5v_ps() const +std::uint16_t PerformanceMaintenanceData::elevation_housing_5v_ps() const { return p->elevationHousing5VPs_; } -uint16_t PerformanceMaintenanceData::elevation_neg_dead_limit() const +std::uint16_t PerformanceMaintenanceData::elevation_neg_dead_limit() const { return p->elevationNegDeadLimit_; } -uint16_t PerformanceMaintenanceData::elevation_pos_normal_limit() const +std::uint16_t PerformanceMaintenanceData::elevation_pos_normal_limit() const { return p->elevationPosNormalLimit_; } -uint16_t PerformanceMaintenanceData::elevation_neg_normal_limit() const +std::uint16_t PerformanceMaintenanceData::elevation_neg_normal_limit() const { return p->elevationNegNormalLimit_; } -uint16_t PerformanceMaintenanceData::elevation_encoder_light() const +std::uint16_t PerformanceMaintenanceData::elevation_encoder_light() const { return p->elevationEncoderLight_; } -uint16_t PerformanceMaintenanceData::elevation_gearbox_oil() const +std::uint16_t PerformanceMaintenanceData::elevation_gearbox_oil() const { return p->elevationGearboxOil_; } -uint16_t PerformanceMaintenanceData::elevation_handwheel() const +std::uint16_t PerformanceMaintenanceData::elevation_handwheel() const { return p->elevationHandwheel_; } -uint16_t PerformanceMaintenanceData::elevation_amp_ps() const +std::uint16_t PerformanceMaintenanceData::elevation_amp_ps() const { return p->elevationAmpPs_; } -uint16_t PerformanceMaintenanceData::azimuth_servo_amp_inhibit() const +std::uint16_t PerformanceMaintenanceData::azimuth_servo_amp_inhibit() const { return p->azimuthServoAmpInhibit_; } -uint16_t PerformanceMaintenanceData::azimuth_servo_amp_short_circuit() const +std::uint16_t +PerformanceMaintenanceData::azimuth_servo_amp_short_circuit() const { return p->azimuthServoAmpShortCircuit_; } -uint16_t PerformanceMaintenanceData::azimuth_servo_amp_overtemp() const +std::uint16_t PerformanceMaintenanceData::azimuth_servo_amp_overtemp() const { return p->azimuthServoAmpOvertemp_; } -uint16_t PerformanceMaintenanceData::azimuth_motor_overtemp() const +std::uint16_t PerformanceMaintenanceData::azimuth_motor_overtemp() const { return p->azimuthMotorOvertemp_; } -uint16_t PerformanceMaintenanceData::azimuth_stow_pin() const +std::uint16_t PerformanceMaintenanceData::azimuth_stow_pin() const { return p->azimuthStowPin_; } -uint16_t PerformanceMaintenanceData::azimuth_housing_5v_ps() const +std::uint16_t PerformanceMaintenanceData::azimuth_housing_5v_ps() const { return p->azimuthHousing5VPs_; } -uint16_t PerformanceMaintenanceData::azimuth_encoder_light() const +std::uint16_t PerformanceMaintenanceData::azimuth_encoder_light() const { return p->azimuthEncoderLight_; } -uint16_t PerformanceMaintenanceData::azimuth_gearbox_oil() const +std::uint16_t PerformanceMaintenanceData::azimuth_gearbox_oil() const { return p->azimuthGearboxOil_; } -uint16_t PerformanceMaintenanceData::azimuth_bull_gear_oil() const +std::uint16_t PerformanceMaintenanceData::azimuth_bull_gear_oil() const { return p->azimuthBullGearOil_; } -uint16_t PerformanceMaintenanceData::azimuth_handwheel() const +std::uint16_t PerformanceMaintenanceData::azimuth_handwheel() const { return p->azimuthHandwheel_; } -uint16_t PerformanceMaintenanceData::azimuth_servo_amp_ps() const +std::uint16_t PerformanceMaintenanceData::azimuth_servo_amp_ps() const { return p->azimuthServoAmpPs_; } -uint16_t PerformanceMaintenanceData::servo() const +std::uint16_t PerformanceMaintenanceData::servo() const { return p->servo_; } -uint16_t PerformanceMaintenanceData::pedestal_interlock_switch() const +std::uint16_t PerformanceMaintenanceData::pedestal_interlock_switch() const { return p->pedestalInterlockSwitch_; } -uint16_t PerformanceMaintenanceData::coho_clock() const +std::uint16_t PerformanceMaintenanceData::coho_clock() const { return p->cohoClock_; } -uint16_t +std::uint16_t PerformanceMaintenanceData::rf_generator_frequency_select_oscillator() const { return p->rfGeneratorFrequencySelectOscillator_; } -uint16_t PerformanceMaintenanceData::rf_generator_rf_stalo() const +std::uint16_t PerformanceMaintenanceData::rf_generator_rf_stalo() const { return p->rfGeneratorRfStalo_; } -uint16_t PerformanceMaintenanceData::rf_generator_phase_shifted_coho() const +std::uint16_t +PerformanceMaintenanceData::rf_generator_phase_shifted_coho() const { return p->rfGeneratorPhaseShiftedCoho_; } -uint16_t PerformanceMaintenanceData::_9v_receiver_ps() const +std::uint16_t PerformanceMaintenanceData::_9v_receiver_ps() const { return p->_9VReceiverPs_; } -uint16_t PerformanceMaintenanceData::_5v_receiver_ps() const +std::uint16_t PerformanceMaintenanceData::_5v_receiver_ps() const { return p->_5VReceiverPs_; } -uint16_t PerformanceMaintenanceData::_18v_receiver_ps() const +std::uint16_t PerformanceMaintenanceData::_18v_receiver_ps() const { return p->_18VReceiverPs_; } -uint16_t PerformanceMaintenanceData::neg_9v_receiver_ps() const +std::uint16_t PerformanceMaintenanceData::neg_9v_receiver_ps() const { return p->neg9VReceiverPs_; } -uint16_t PerformanceMaintenanceData::_5v_single_channel_rdaiu_ps() const +std::uint16_t PerformanceMaintenanceData::_5v_single_channel_rdaiu_ps() const { return p->_5VSingleChannelRdaiuPs_; } @@ -1573,22 +1325,22 @@ float PerformanceMaintenanceData::long_pulse_horizontal_dbz0() const return p->longPulseHorizontalDbz0_; } -uint16_t PerformanceMaintenanceData::velocity_processed() const +std::uint16_t PerformanceMaintenanceData::velocity_processed() const { return p->velocityProcessed_; } -uint16_t PerformanceMaintenanceData::width_processed() const +std::uint16_t PerformanceMaintenanceData::width_processed() const { return p->widthProcessed_; } -uint16_t PerformanceMaintenanceData::velocity_rf_gen() const +std::uint16_t PerformanceMaintenanceData::velocity_rf_gen() const { return p->velocityRfGen_; } -uint16_t PerformanceMaintenanceData::width_rf_gen() const +std::uint16_t PerformanceMaintenanceData::width_rf_gen() const { return p->widthRfGen_; } @@ -1628,9 +1380,9 @@ float PerformanceMaintenanceData::vertical_power_sense() const return p->verticalPowerSense_; } -float PerformanceMaintenanceData::zdr_bias() const +float PerformanceMaintenanceData::zdr_offset() const { - return p->zdrBias_; + return p->zdrOffset_; } float PerformanceMaintenanceData::clutter_suppression_delta() const @@ -1653,170 +1405,148 @@ float PerformanceMaintenanceData::vertical_linearity() const return p->verticalLinearity_; } -uint16_t PerformanceMaintenanceData::state_file_read_status() const +std::uint16_t PerformanceMaintenanceData::state_file_read_status() const { return p->stateFileReadStatus_; } -uint16_t PerformanceMaintenanceData::state_file_write_status() const +std::uint16_t PerformanceMaintenanceData::state_file_write_status() const { return p->stateFileWriteStatus_; } -uint16_t PerformanceMaintenanceData::bypass_map_file_read_status() const +std::uint16_t PerformanceMaintenanceData::bypass_map_file_read_status() const { return p->bypassMapFileReadStatus_; } -uint16_t PerformanceMaintenanceData::bypass_map_file_write_status() const +std::uint16_t PerformanceMaintenanceData::bypass_map_file_write_status() const { return p->bypassMapFileWriteStatus_; } -uint16_t PerformanceMaintenanceData::current_adaptation_file_read_status() const +std::uint16_t +PerformanceMaintenanceData::current_adaptation_file_read_status() const { return p->currentAdaptationFileReadStatus_; } -uint16_t +std::uint16_t PerformanceMaintenanceData::current_adaptation_file_write_status() const { return p->currentAdaptationFileWriteStatus_; } -uint16_t PerformanceMaintenanceData::censor_zone_file_read_status() const +std::uint16_t PerformanceMaintenanceData::censor_zone_file_read_status() const { return p->censorZoneFileReadStatus_; } -uint16_t PerformanceMaintenanceData::censor_zone_file_write_status() const +std::uint16_t PerformanceMaintenanceData::censor_zone_file_write_status() const { return p->censorZoneFileWriteStatus_; } -uint16_t PerformanceMaintenanceData::remote_vcp_file_read_status() const +std::uint16_t PerformanceMaintenanceData::remote_vcp_file_read_status() const { return p->remoteVcpFileReadStatus_; } -uint16_t PerformanceMaintenanceData::remote_vcp_file_write_status() const +std::uint16_t PerformanceMaintenanceData::remote_vcp_file_write_status() const { return p->remoteVcpFileWriteStatus_; } -uint16_t +std::uint16_t PerformanceMaintenanceData::baseline_adaptation_file_read_status() const { return p->baselineAdaptationFileReadStatus_; } -uint16_t PerformanceMaintenanceData::read_status_of_prf_sets() const +std::uint16_t PerformanceMaintenanceData::read_status_of_prf_sets() const { return p->readStatusOfPrfSets_; } -uint16_t PerformanceMaintenanceData::clutter_filter_map_file_read_status() const +std::uint16_t +PerformanceMaintenanceData::clutter_filter_map_file_read_status() const { return p->clutterFilterMapFileReadStatus_; } -uint16_t +std::uint16_t PerformanceMaintenanceData::clutter_filter_map_file_write_status() const { return p->clutterFilterMapFileWriteStatus_; } -uint16_t PerformanceMaintenanceData::generatl_disk_io_error() const +std::uint16_t PerformanceMaintenanceData::general_disk_io_error() const { - return p->generatlDiskIoError_; + return p->generalDiskIoError_; } -uint8_t PerformanceMaintenanceData::rsp_status() const +std::uint8_t PerformanceMaintenanceData::rsp_status() const { return p->rspStatus_; } -uint8_t PerformanceMaintenanceData::motherboard_temperature() const -{ - return p->motherboardTemperature_; -} - -uint8_t PerformanceMaintenanceData::cpu1_temperature() const +std::uint8_t PerformanceMaintenanceData::cpu1_temperature() const { return p->cpu1Temperature_; } -uint8_t PerformanceMaintenanceData::cpu2_temperature() const +std::uint8_t PerformanceMaintenanceData::cpu2_temperature() const { return p->cpu2Temperature_; } -uint16_t PerformanceMaintenanceData::cpu1_fan_speed() const +std::uint16_t PerformanceMaintenanceData::rsp_motherboard_power() const { - return p->cpu1FanSpeed_; + return p->rspMotherboardPower_; } -uint16_t PerformanceMaintenanceData::cpu2_fan_speed() const -{ - return p->cpu2FanSpeed_; -} - -uint16_t PerformanceMaintenanceData::rsp_fan1_speed() const -{ - return p->rspFan1Speed_; -} - -uint16_t PerformanceMaintenanceData::rsp_fan2_speed() const -{ - return p->rspFan2Speed_; -} - -uint16_t PerformanceMaintenanceData::rsp_fan3_speed() const -{ - return p->rspFan3Speed_; -} - -uint16_t PerformanceMaintenanceData::spip_comm_status() const +std::uint16_t PerformanceMaintenanceData::spip_comm_status() const { return p->spipCommStatus_; } -uint16_t PerformanceMaintenanceData::hci_comm_status() const +std::uint16_t PerformanceMaintenanceData::hci_comm_status() const { return p->hciCommStatus_; } -uint16_t PerformanceMaintenanceData::signal_processor_command_status() const +std::uint16_t +PerformanceMaintenanceData::signal_processor_command_status() const { return p->signalProcessorCommandStatus_; } -uint16_t PerformanceMaintenanceData::ame_communication_status() const +std::uint16_t PerformanceMaintenanceData::ame_communication_status() const { return p->ameCommunicationStatus_; } -uint16_t PerformanceMaintenanceData::rms_link_status() const +std::uint16_t PerformanceMaintenanceData::rms_link_status() const { return p->rmsLinkStatus_; } -uint16_t PerformanceMaintenanceData::rpg_link_status() const +std::uint16_t PerformanceMaintenanceData::rpg_link_status() const { return p->rpgLinkStatus_; } -uint16_t PerformanceMaintenanceData::interpanel_link_status() const +std::uint16_t PerformanceMaintenanceData::interpanel_link_status() const { return p->interpanelLinkStatus_; } -uint32_t PerformanceMaintenanceData::performance_check_time() const +std::uint32_t PerformanceMaintenanceData::performance_check_time() const { return p->performanceCheckTime_; } -uint16_t PerformanceMaintenanceData::version() const +std::uint16_t PerformanceMaintenanceData::version() const { return p->version_; } @@ -1825,9 +1555,10 @@ bool PerformanceMaintenanceData::Parse(std::istream& is) { logger_->trace("Parsing Performance/Maintenance Data (Message Type 3)"); - bool messageValid = true; - size_t bytesRead = 0; + bool messageValid = true; + std::size_t bytesRead = 0; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers): Readability p->rcpString_.resize(16); // Communications @@ -1839,11 +1570,13 @@ bool PerformanceMaintenanceData::Parse(std::istream& is) is.read(reinterpret_cast(&p->routerMemoryFree_), 4); // 9-10 is.read(reinterpret_cast(&p->routerMemoryUtilization_), 2); // 11 is.read(reinterpret_cast(&p->routeToRpg_), 2); // 12 - is.read(reinterpret_cast(&p->csuLossOfSignal_), 4); // 13-14 - is.read(reinterpret_cast(&p->csuLossOfFrames_), 4); // 15-16 - is.read(reinterpret_cast(&p->csuYellowAlarms_), 4); // 17-18 - is.read(reinterpret_cast(&p->csuBlueAlarms_), 4); // 19-20 - is.read(reinterpret_cast(&p->csu24HrErroredSeconds_), 4); // 21-22 + is.read(reinterpret_cast(&p->t1PortStatus_), 2); // 13 + is.read(reinterpret_cast(&p->routerDedicatedEthernetPortStatus_), + 2); // 14 + is.read(reinterpret_cast(&p->routerCommercialEthernetPortStatus_), + 2); // 15 + is.seekg(10, std::ios_base::cur); // 16-20 + is.read(reinterpret_cast(&p->csu24HrErroredSeconds_), 4); // 21-22 is.read(reinterpret_cast(&p->csu24HrSeverelyErroredSeconds_), 4); // 23-24 is.read(reinterpret_cast(&p->csu24HrSeverelyErroredFramingSeconds_), @@ -1863,9 +1596,8 @@ bool PerformanceMaintenanceData::Parse(std::istream& is) is.seekg(2, std::ios_base::cur); // 44 is.read(reinterpret_cast(&p->ifdrChasisTemperature_), 2); // 45 is.read(reinterpret_cast(&p->ifdrFpgaTemperature_), 2); // 46 - is.seekg(4, std::ios_base::cur); // 47-48 - is.read(reinterpret_cast(&p->gpsSatellites_), 4); // 49-50 - is.seekg(4, std::ios_base::cur); // 51-52 + is.read(reinterpret_cast(&p->ntpStatus_), 2); // 47 + is.seekg(10, std::ios_base::cur); // 48-52 is.read(reinterpret_cast(&p->ipcStatus_), 2); // 53 is.read(reinterpret_cast(&p->commandedChannelControl_), 2); // 54 is.seekg(6, std::ios_base::cur); // 55-57 @@ -1970,10 +1702,10 @@ bool PerformanceMaintenanceData::Parse(std::istream& is) is.read(reinterpret_cast(&p->transmitterInoperable_), 2); // 184 is.read(reinterpret_cast(&p->transmitterAirFilter_), 2); // 185 is.read(reinterpret_cast(&p->zeroTestBit_[0]), - p->zeroTestBit_.size() * 2); // 186-193 + static_cast(p->zeroTestBit_.size() * 2)); // 186-193 is.read(reinterpret_cast(&p->oneTestBit_[0]), - p->oneTestBit_.size() * 2); // 194-201 - is.read(reinterpret_cast(&p->xmtrSpipInterface_), 2); // 202 + static_cast(p->oneTestBit_.size() * 2)); // 194-201 + is.read(reinterpret_cast(&p->xmtrSpipInterface_), 2); // 202 is.read(reinterpret_cast(&p->transmitterSummaryStatus_), 2); // 203 is.seekg(2, std::ios_base::cur); // 204 is.read(reinterpret_cast(&p->transmitterRfPower_), 4); // 205-206 @@ -2112,7 +1844,7 @@ bool PerformanceMaintenanceData::Parse(std::istream& is) is.seekg(8, std::ios_base::cur); // 393-396 is.read(reinterpret_cast(&p->horizontalPowerSense_), 4); // 397-398 is.read(reinterpret_cast(&p->verticalPowerSense_), 4); // 399-400 - is.read(reinterpret_cast(&p->zdrBias_), 4); // 401-402 + is.read(reinterpret_cast(&p->zdrOffset_), 4); // 401-402 is.seekg(12, std::ios_base::cur); // 403-408 is.read(reinterpret_cast(&p->clutterSuppressionDelta_), 4); // 409-410 is.read(reinterpret_cast(&p->clutterSuppressionUnfilteredPower_), @@ -2143,18 +1875,14 @@ bool PerformanceMaintenanceData::Parse(std::istream& is) is.read(reinterpret_cast(&p->clutterFilterMapFileReadStatus_), 2); // 445 is.read(reinterpret_cast(&p->clutterFilterMapFileWriteStatus_), - 2); // 446 - is.read(reinterpret_cast(&p->generatlDiskIoError_), 2); // 447 - is.read(reinterpret_cast(&p->rspStatus_), 1); // 448 - is.read(reinterpret_cast(&p->motherboardTemperature_), 1); // 448 - is.read(reinterpret_cast(&p->cpu1Temperature_), 1); // 449 - is.read(reinterpret_cast(&p->cpu2Temperature_), 1); // 449 - is.read(reinterpret_cast(&p->cpu1FanSpeed_), 2); // 450 - is.read(reinterpret_cast(&p->cpu2FanSpeed_), 2); // 451 - is.read(reinterpret_cast(&p->rspFan1Speed_), 2); // 452 - is.read(reinterpret_cast(&p->rspFan2Speed_), 2); // 453 - is.read(reinterpret_cast(&p->rspFan3Speed_), 2); // 454 - is.seekg(12, std::ios_base::cur); // 455-460 + 2); // 446 + is.read(reinterpret_cast(&p->generalDiskIoError_), 2); // 447 + is.read(reinterpret_cast(&p->rspStatus_), 1); // 448 + is.seekg(1, std::ios_base::cur); // 448 + is.read(reinterpret_cast(&p->cpu1Temperature_), 1); // 449 + is.read(reinterpret_cast(&p->cpu2Temperature_), 1); // 449 + is.read(reinterpret_cast(&p->rspMotherboardPower_), 2); // 450 + is.seekg(20, std::ios_base::cur); // 451-460 // Device Status is.read(reinterpret_cast(&p->spipCommStatus_), 2); // 461 @@ -2172,18 +1900,21 @@ bool PerformanceMaintenanceData::Parse(std::istream& is) bytesRead += 960; + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + // Communications - p->loopBackSetStatus_ = ntohs(p->loopBackSetStatus_); - p->t1OutputFrames_ = ntohl(p->t1OutputFrames_); - p->t1InputFrames_ = ntohl(p->t1InputFrames_); - p->routerMemoryUsed_ = ntohl(p->routerMemoryUsed_); - p->routerMemoryFree_ = ntohl(p->routerMemoryFree_); - p->routerMemoryUtilization_ = ntohs(p->routerMemoryUtilization_); - p->routeToRpg_ = ntohs(p->routeToRpg_); - p->csuLossOfSignal_ = ntohl(p->csuLossOfSignal_); - p->csuLossOfFrames_ = ntohl(p->csuLossOfFrames_); - p->csuYellowAlarms_ = ntohl(p->csuYellowAlarms_); - p->csuBlueAlarms_ = ntohl(p->csuBlueAlarms_); + p->loopBackSetStatus_ = ntohs(p->loopBackSetStatus_); + p->t1OutputFrames_ = ntohl(p->t1OutputFrames_); + p->t1InputFrames_ = ntohl(p->t1InputFrames_); + p->routerMemoryUsed_ = ntohl(p->routerMemoryUsed_); + p->routerMemoryFree_ = ntohl(p->routerMemoryFree_); + p->routerMemoryUtilization_ = ntohs(p->routerMemoryUtilization_); + p->routeToRpg_ = ntohs(p->routeToRpg_); + p->t1PortStatus_ = ntohs(p->t1PortStatus_); + p->routerDedicatedEthernetPortStatus_ = + ntohs(p->routerDedicatedEthernetPortStatus_); + p->routerCommercialEthernetPortStatus_ = + ntohs(p->routerCommercialEthernetPortStatus_); p->csu24HrErroredSeconds_ = ntohl(p->csu24HrErroredSeconds_); p->csu24HrSeverelyErroredSeconds_ = ntohl(p->csu24HrSeverelyErroredSeconds_); p->csu24HrSeverelyErroredFramingSeconds_ = @@ -2198,7 +1929,7 @@ bool PerformanceMaintenanceData::Parse(std::istream& is) p->lanSwitchMemoryUtilization_ = ntohs(p->lanSwitchMemoryUtilization_); p->ifdrChasisTemperature_ = ntohs(p->ifdrChasisTemperature_); p->ifdrFpgaTemperature_ = ntohs(p->ifdrFpgaTemperature_); - p->gpsSatellites_ = ntohl(p->gpsSatellites_); + p->ntpStatus_ = ntohs(p->ntpStatus_); p->ipcStatus_ = ntohs(p->ipcStatus_); p->commandedChannelControl_ = ntohs(p->commandedChannelControl_); @@ -2413,7 +2144,7 @@ bool PerformanceMaintenanceData::Parse(std::istream& is) p->longPulseVerticalDbz0_ = SwapFloat(p->longPulseVerticalDbz0_); p->horizontalPowerSense_ = SwapFloat(p->horizontalPowerSense_); p->verticalPowerSense_ = SwapFloat(p->verticalPowerSense_); - p->zdrBias_ = SwapFloat(p->zdrBias_); + p->zdrOffset_ = SwapFloat(p->zdrOffset_); p->clutterSuppressionDelta_ = SwapFloat(p->clutterSuppressionDelta_); p->clutterSuppressionUnfilteredPower_ = SwapFloat(p->clutterSuppressionUnfilteredPower_); @@ -2441,12 +2172,8 @@ bool PerformanceMaintenanceData::Parse(std::istream& is) ntohs(p->clutterFilterMapFileReadStatus_); p->clutterFilterMapFileWriteStatus_ = ntohs(p->clutterFilterMapFileWriteStatus_); - p->generatlDiskIoError_ = ntohs(p->generatlDiskIoError_); - p->cpu1FanSpeed_ = ntohs(p->cpu1FanSpeed_); - p->cpu2FanSpeed_ = ntohs(p->cpu2FanSpeed_); - p->rspFan1Speed_ = ntohs(p->rspFan1Speed_); - p->rspFan2Speed_ = ntohs(p->rspFan2Speed_); - p->rspFan3Speed_ = ntohs(p->rspFan3Speed_); + p->generalDiskIoError_ = ntohs(p->generalDiskIoError_); + p->rspMotherboardPower_ = ntohs(p->rspMotherboardPower_); // Device Status p->spipCommStatus_ = ntohs(p->spipCommStatus_); @@ -2483,6 +2210,4 @@ PerformanceMaintenanceData::Create(Level2MessageHeader&& header, return message; } -} // namespace rda -} // namespace wsr88d -} // namespace scwx +} // namespace scwx::wsr88d::rda diff --git a/wxdata/source/scwx/wsr88d/rda/rda_adaptation_data.cpp b/wxdata/source/scwx/wsr88d/rda/rda_adaptation_data.cpp index 94a96283..7ccd5863 100644 --- a/wxdata/source/scwx/wsr88d/rda/rda_adaptation_data.cpp +++ b/wxdata/source/scwx/wsr88d/rda/rda_adaptation_data.cpp @@ -1,11 +1,7 @@ #include #include -namespace scwx -{ -namespace wsr88d -{ -namespace rda +namespace scwx::wsr88d::rda { static const std::string logPrefix_ = "scwx::wsr88d::rda::rda_adaptation_data"; @@ -13,362 +9,202 @@ static const auto logger_ = util::Logger::Create(logPrefix_); struct AntManualSetup { - int32_t ielmin_; - int32_t ielmax_; - uint32_t fazvelmax_; - uint32_t felvelmax_; - int32_t igndHgt_; - uint32_t iradHgt_; - - AntManualSetup() : - ielmin_ {0}, - ielmax_ {0}, - fazvelmax_ {0}, - felvelmax_ {0}, - igndHgt_ {0}, - iradHgt_ {0} - { - } + std::int32_t ielmin_ {0}; + std::int32_t ielmax_ {0}; + std::uint32_t fazvelmax_ {0}; + std::uint32_t felvelmax_ {0}; + std::int32_t igndHgt_ {0}; + std::uint32_t iradHgt_ {0}; }; -class RdaAdaptationDataImpl +class RdaAdaptationData::Impl { public: - explicit RdaAdaptationDataImpl() : - adapFileName_ {}, - adapFormat_ {}, - adapRevision_ {}, - adapDate_ {}, - adapTime_ {}, - lowerPreLimit_ {0.0f}, - azLat_ {0.0f}, - upperPreLimit_ {0.0f}, - elLat_ {0.0f}, - parkaz_ {0.0f}, - parkel_ {0.0f}, - aFuelConv_ {0.0f}, - aMinShelterTemp_ {0.0f}, - aMaxShelterTemp_ {0.0f}, - aMinShelterAcTempDiff_ {0.0f}, - aMaxXmtrAirTemp_ {0.0f}, - aMaxRadTemp_ {0.0f}, - aMaxRadTempRise_ {0.0f}, - lowerDeadLimit_ {0.0f}, - upperDeadLimit_ {0.0f}, - aMinGenRoomTemp_ {0.0f}, - aMaxGenRoomTemp_ {0.0f}, - spip5VRegLim_ {0.0f}, - spip15VRegLim_ {0.0f}, - rpgCoLocated_ {false}, - specFilterInstalled_ {false}, - tpsInstalled_ {false}, - rmsInstalled_ {false}, - aHvdlTstInt_ {0}, - aRpgLtInt_ {0}, - aMinStabUtilPwrTime_ {0}, - aGenAutoExerInterval_ {0}, - aUtilPwrSwReqInterval_ {0}, - aLowFuelLevel_ {0.0f}, - configChanNumber_ {0}, - redundantChanConfig_ {0}, - attenTable_ {0.0f}, - pathLosses_ {}, - vTsCw_ {0.0f}, - hRnscale_ {0.0f}, - atmos_ {0.0f}, - elIndex_ {0.0f}, - tfreqMhz_ {0}, - baseDataTcn_ {0.0f}, - reflDataTover_ {0.0f}, - tarHDbz0Lp_ {0.0f}, - tarVDbz0Lp_ {0.0f}, - initPhiDp_ {0}, - normInitPhiDp_ {0}, - lxLp_ {0.0f}, - lxSp_ {0.0f}, - meteorParam_ {0.0f}, - antennaGain_ {0.0f}, - velDegradLimit_ {0.0f}, - wthDegradLimit_ {0.0f}, - hNoisetempDgradLimit_ {0.0f}, - hMinNoisetemp_ {0}, - vNoisetempDgradLimit_ {0.0f}, - vMinNoisetemp_ {0}, - klyDegradeLimit_ {0.0f}, - tsCoho_ {0.0f}, - hTsCw_ {0.0f}, - tsStalo_ {0.0f}, - ameHNoiseEnr_ {0.0f}, - xmtrPeakPwrHighLimit_ {0.0f}, - xmtrPeakPwrLowLimit_ {0.0f}, - hDbz0DeltaLimit_ {0.0f}, - threshold1_ {0.0f}, - threshold2_ {0.0f}, - clutSuppDgradLim_ {0.0f}, - range0Value_ {0.0f}, - xmtrPwrMtrScale_ {0.0f}, - vDbz0DeltaLimit_ {0.0f}, - tarHDbz0Sp_ {0.0f}, - tarVDbz0Sp_ {0.0f}, - deltaprf_ {0}, - tauSp_ {0}, - tauLp_ {0}, - ncDeadValue_ {0}, - tauRfSp_ {0}, - tauRfLp_ {0}, - seg1Lim_ {0.0f}, - slatsec_ {0.0f}, - slonsec_ {0.0f}, - slatdeg_ {0}, - slatmin_ {0}, - slondeg_ {0}, - slonmin_ {0}, - slatdir_ {0}, - slondir_ {0}, - azCorrectionFactor_ {0.0f}, - elCorrectionFactor_ {0.0f}, - siteName_ {}, - antManualSetup_(), - azPosSustainDrive_ {0.0f}, - azNegSustainDrive_ {0.0f}, - azNomPosDriveSlope_ {0.0f}, - azNomNegDriveSlope_ {0.0f}, - azFeedbackSlope_ {0.0f}, - elPosSustainDrive_ {0.0f}, - elNegSustainDrive_ {0.0f}, - elNomPosDriveSlope_ {0.0f}, - elNomNegDriveSlope_ {0.0f}, - elFeedbackSlope_ {0.0f}, - elFirstSlope_ {0.0f}, - elSecondSlope_ {0.0f}, - elThirdSlope_ {0.0f}, - elDroopPos_ {0.0f}, - elOffNeutralDrive_ {0.0f}, - azIntertia_ {0.0f}, - elInertia_ {0.0f}, - rvp8nvIwaveguideLength_ {0}, - vRnscale_ {0.0f}, - velDataTover_ {0.0f}, - widthDataTover_ {0.0f}, - dopplerRangeStart_ {0.0f}, - maxElIndex_ {0}, - seg2Lim_ {0.0f}, - seg3Lim_ {0.0f}, - seg4Lim_ {0.0f}, - nbrElSegments_ {0}, - hNoiseLong_ {0.0f}, - antNoiseTemp_ {0.0f}, - hNoiseShort_ {0.0f}, - hNoiseTolerance_ {0.0f}, - minHDynRange_ {0.0f}, - genInstalled_ {false}, - genExercise_ {false}, - vNoiseTolerance_ {0.0f}, - minVDynRange_ {0.0f}, - zdrBiasDgradLim_ {0.0f}, - baselineZdrBias_ {0.0f}, - vNoiseLong_ {0.0f}, - vNoiseShort_ {0.0f}, - zdrDataTover_ {0.0f}, - phiDataTover_ {0.0f}, - rhoDataTover_ {0.0f}, - staloPowerDgradLimit_ {0.0f}, - staloPowerMaintLimit_ {0.0f}, - minHPwrSense_ {0.0f}, - minVPwrSense_ {0.0f}, - hPwrSenseOffset_ {0.0f}, - vPwrSenseOffset_ {0.0f}, - psGainRef_ {0.0f}, - rfPalletBroadLoss_ {0.0f}, - amePsTolerance_ {0.0f}, - ameMaxTemp_ {0.0f}, - ameMinTemp_ {0.0f}, - rcvrModMaxTemp_ {0.0f}, - rcvrModMinTemp_ {0.0f}, - biteModMaxTemp_ {0.0f}, - biteModMinTemp_ {0.0f}, - defaultPolarization_ {0}, - trLimitDgradLimit_ {0.0f}, - trLimitFailLimit_ {0.0f}, - rfpStepperEnabled_ {false}, - ameCurrentTolerance_ {0.0f}, - hOnlyPolarization_ {0}, - vOnlyPolarization_ {0}, - sunBias_ {0.0f}, - aMinShelterTempWarn_ {0.0f}, - powerMeterZero_ {0.0f}, - txbBaseline_ {0.0f}, - txbAlarmThresh_ {0.0f} {}; - ~RdaAdaptationDataImpl() = default; + explicit Impl() = default; + ~Impl() = default; - std::string adapFileName_; - std::string adapFormat_; - std::string adapRevision_; - std::string adapDate_; - std::string adapTime_; - float lowerPreLimit_; - float azLat_; - float upperPreLimit_; - float elLat_; - float parkaz_; - float parkel_; - std::array aFuelConv_; - float aMinShelterTemp_; - float aMaxShelterTemp_; - float aMinShelterAcTempDiff_; - float aMaxXmtrAirTemp_; - float aMaxRadTemp_; - float aMaxRadTempRise_; - float lowerDeadLimit_; - float upperDeadLimit_; - float aMinGenRoomTemp_; - float aMaxGenRoomTemp_; - float spip5VRegLim_; - float spip15VRegLim_; - bool rpgCoLocated_; - bool specFilterInstalled_; - bool tpsInstalled_; - bool rmsInstalled_; - uint32_t aHvdlTstInt_; - uint32_t aRpgLtInt_; - uint32_t aMinStabUtilPwrTime_; - uint32_t aGenAutoExerInterval_; - uint32_t aUtilPwrSwReqInterval_; - float aLowFuelLevel_; - uint32_t configChanNumber_; - uint32_t redundantChanConfig_; - std::array attenTable_; - std::map pathLosses_; - float vTsCw_; - std::array hRnscale_; - std::array atmos_; - std::array elIndex_; - uint32_t tfreqMhz_; - float baseDataTcn_; - float reflDataTover_; - float tarHDbz0Lp_; - float tarVDbz0Lp_; - uint32_t initPhiDp_; - uint32_t normInitPhiDp_; - float lxLp_; - float lxSp_; - float meteorParam_; - float antennaGain_; - float velDegradLimit_; - float wthDegradLimit_; - float hNoisetempDgradLimit_; - uint32_t hMinNoisetemp_; - float vNoisetempDgradLimit_; - uint32_t vMinNoisetemp_; - float klyDegradeLimit_; - float tsCoho_; - float hTsCw_; - float tsStalo_; - float ameHNoiseEnr_; - float xmtrPeakPwrHighLimit_; - float xmtrPeakPwrLowLimit_; - float hDbz0DeltaLimit_; - float threshold1_; - float threshold2_; - float clutSuppDgradLim_; - float range0Value_; - float xmtrPwrMtrScale_; - float vDbz0DeltaLimit_; - float tarHDbz0Sp_; - float tarVDbz0Sp_; - uint32_t deltaprf_; - uint32_t tauSp_; - uint32_t tauLp_; - uint32_t ncDeadValue_; - uint32_t tauRfSp_; - uint32_t tauRfLp_; - float seg1Lim_; - float slatsec_; - float slonsec_; - uint32_t slatdeg_; - uint32_t slatmin_; - uint32_t slondeg_; - uint32_t slonmin_; - char slatdir_; - char slondir_; - float azCorrectionFactor_; - float elCorrectionFactor_; - std::string siteName_; - AntManualSetup antManualSetup_; - float azPosSustainDrive_; - float azNegSustainDrive_; - float azNomPosDriveSlope_; - float azNomNegDriveSlope_; - float azFeedbackSlope_; - float elPosSustainDrive_; - float elNegSustainDrive_; - float elNomPosDriveSlope_; - float elNomNegDriveSlope_; - float elFeedbackSlope_; - float elFirstSlope_; - float elSecondSlope_; - float elThirdSlope_; - float elDroopPos_; - float elOffNeutralDrive_; - float azIntertia_; - float elInertia_; - uint32_t rvp8nvIwaveguideLength_; - std::array vRnscale_; - float velDataTover_; - float widthDataTover_; - float dopplerRangeStart_; - uint32_t maxElIndex_; - float seg2Lim_; - float seg3Lim_; - float seg4Lim_; - uint32_t nbrElSegments_; - float hNoiseLong_; - float antNoiseTemp_; - float hNoiseShort_; - float hNoiseTolerance_; - float minHDynRange_; - bool genInstalled_; - bool genExercise_; - float vNoiseTolerance_; - float minVDynRange_; - float zdrBiasDgradLim_; - float baselineZdrBias_; - float vNoiseLong_; - float vNoiseShort_; - float zdrDataTover_; - float phiDataTover_; - float rhoDataTover_; - float staloPowerDgradLimit_; - float staloPowerMaintLimit_; - float minHPwrSense_; - float minVPwrSense_; - float hPwrSenseOffset_; - float vPwrSenseOffset_; - float psGainRef_; - float rfPalletBroadLoss_; - float amePsTolerance_; - float ameMaxTemp_; - float ameMinTemp_; - float rcvrModMaxTemp_; - float rcvrModMinTemp_; - float biteModMaxTemp_; - float biteModMinTemp_; - uint32_t defaultPolarization_; - float trLimitDgradLimit_; - float trLimitFailLimit_; - bool rfpStepperEnabled_; - float ameCurrentTolerance_; - uint32_t hOnlyPolarization_; - uint32_t vOnlyPolarization_; - float sunBias_; - float aMinShelterTempWarn_; - float powerMeterZero_; - float txbBaseline_; - float txbAlarmThresh_; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + std::string adapFileName_ {}; + std::string adapFormat_ {}; + std::string adapRevision_ {}; + std::string adapDate_ {}; + std::string adapTime_ {}; + float lowerPreLimit_ {0.0f}; + float azLat_ {0.0f}; + float upperPreLimit_ {0.0f}; + float elLat_ {0.0f}; + float parkaz_ {0.0f}; + float parkel_ {0.0f}; + std::array aFuelConv_ {0.0f}; + float aMinShelterTemp_ {0.0f}; + float aMaxShelterTemp_ {0.0f}; + float aMinShelterAcTempDiff_ {0.0f}; + float aMaxXmtrAirTemp_ {0.0f}; + float aMaxRadTemp_ {0.0f}; + float aMaxRadTempRise_ {0.0f}; + float lowerDeadLimit_ {0.0f}; + float upperDeadLimit_ {0.0f}; + float aMinGenRoomTemp_ {0.0f}; + float aMaxGenRoomTemp_ {0.0f}; + float spip5VRegLim_ {0.0f}; + float spip15VRegLim_ {0.0f}; + bool rpgCoLocated_ {false}; + bool specFilterInstalled_ {false}; + bool tpsInstalled_ {false}; + bool rmsInstalled_ {false}; + std::uint32_t aHvdlTstInt_ {0}; + std::uint32_t aRpgLtInt_ {0}; + std::uint32_t aMinStabUtilPwrTime_ {0}; + std::uint32_t aGenAutoExerInterval_ {0}; + std::uint32_t aUtilPwrSwReqInterval_ {0}; + float aLowFuelLevel_ {0.0f}; + std::uint32_t configChanNumber_ {0}; + std::uint32_t redundantChanConfig_ {0}; + std::array attenTable_ {0.0f}; + std::map pathLosses_ {}; + float vTsCw_ {0.0f}; + std::array hRnscale_ {0.0f}; + std::array atmos_ {0.0f}; + std::array elIndex_ {0.0f}; + std::uint32_t tfreqMhz_ {0}; + float baseDataTcn_ {0.0f}; + float reflDataTover_ {0.0f}; + float tarHDbz0Lp_ {0.0f}; + float tarVDbz0Lp_ {0.0f}; + std::uint32_t initPhiDp_ {0}; + std::uint32_t normInitPhiDp_ {0}; + float lxLp_ {0.0f}; + float lxSp_ {0.0f}; + float meteorParam_ {0.0f}; + float antennaGain_ {0.0f}; + float velDegradLimit_ {0.0f}; + float wthDegradLimit_ {0.0f}; + float hNoisetempDgradLimit_ {0.0f}; + std::uint32_t hMinNoisetemp_ {0}; + float vNoisetempDgradLimit_ {0.0f}; + std::uint32_t vMinNoisetemp_ {0}; + float klyDegradeLimit_ {0.0f}; + float tsCoho_ {0.0f}; + float hTsCw_ {0.0f}; + float tsStalo_ {0.0f}; + float ameHNoiseEnr_ {0.0f}; + float xmtrPeakPwrHighLimit_ {0.0f}; + float xmtrPeakPwrLowLimit_ {0.0f}; + float hDbz0DeltaLimit_ {0.0f}; + float threshold1_ {0.0f}; + float threshold2_ {0.0f}; + float clutSuppDgradLim_ {0.0f}; + float range0Value_ {0.0f}; + float xmtrPwrMtrScale_ {0.0f}; + float vDbz0DeltaLimit_ {0.0f}; + float tarHDbz0Sp_ {0.0f}; + float tarVDbz0Sp_ {0.0f}; + std::uint32_t deltaprf_ {0}; + std::uint32_t tauSp_ {0}; + std::uint32_t tauLp_ {0}; + std::uint32_t ncDeadValue_ {0}; + std::uint32_t tauRfSp_ {0}; + std::uint32_t tauRfLp_ {0}; + float seg1Lim_ {0.0f}; + float slatsec_ {0.0f}; + float slonsec_ {0.0f}; + std::uint32_t slatdeg_ {0}; + std::uint32_t slatmin_ {0}; + std::uint32_t slondeg_ {0}; + std::uint32_t slonmin_ {0}; + char slatdir_ {0}; + char slondir_ {0}; + double digRcvrClockFreq_ {0.0}; + double cohoFreq_ {0.0}; + float azCorrectionFactor_ {0.0f}; + float elCorrectionFactor_ {0.0f}; + std::string siteName_ {}; + AntManualSetup antManualSetup_ {}; + float azPosSustainDrive_ {0.0f}; + float azNegSustainDrive_ {0.0f}; + float azNomPosDriveSlope_ {0.0f}; + float azNomNegDriveSlope_ {0.0f}; + float azFeedbackSlope_ {0.0f}; + float elPosSustainDrive_ {0.0f}; + float elNegSustainDrive_ {0.0f}; + float elNomPosDriveSlope_ {0.0f}; + float elNomNegDriveSlope_ {0.0f}; + float elFeedbackSlope_ {0.0f}; + float elFirstSlope_ {0.0f}; + float elSecondSlope_ {0.0f}; + float elThirdSlope_ {0.0f}; + float elDroopPos_ {0.0f}; + float elOffNeutralDrive_ {0.0f}; + float azIntertia_ {0.0f}; + float elInertia_ {0.0f}; + float azStowAngle_ {0.0f}; + float elStowAngle_ {0.0f}; + float azEncoderAlignment_ {0.0f}; + float elEncoderAlignment_ {0.0f}; + std::string refinedPark_ {}; + std::uint32_t rvp8nvIwaveguideLength_ {0}; + std::array vRnscale_ {0.0f}; + float velDataTover_ {0.0f}; + float widthDataTover_ {0.0f}; + float dopplerRangeStart_ {0.0f}; + std::uint32_t maxElIndex_ {0}; + float seg2Lim_ {0.0f}; + float seg3Lim_ {0.0f}; + float seg4Lim_ {0.0f}; + std::uint32_t nbrElSegments_ {0}; + float hNoiseLong_ {0.0f}; + float antNoiseTemp_ {0.0f}; + float hNoiseShort_ {0.0f}; + float hNoiseTolerance_ {0.0f}; + float minHDynRange_ {0.0f}; + bool genInstalled_ {false}; + bool genExercise_ {false}; + float vNoiseTolerance_ {0.0f}; + float minVDynRange_ {0.0f}; + float zdrOffsetDgradLim_ {0.0f}; + float baselineZdrOffset_ {0.0f}; + float vNoiseLong_ {0.0f}; + float vNoiseShort_ {0.0f}; + float zdrDataTover_ {0.0f}; + float phiDataTover_ {0.0f}; + float rhoDataTover_ {0.0f}; + float staloPowerDgradLimit_ {0.0f}; + float staloPowerMaintLimit_ {0.0f}; + float minHPwrSense_ {0.0f}; + float minVPwrSense_ {0.0f}; + float hPwrSenseOffset_ {0.0f}; + float vPwrSenseOffset_ {0.0f}; + float psGainRef_ {0.0f}; + float rfPalletBroadLoss_ {0.0f}; + float amePsTolerance_ {0.0f}; + float ameMaxTemp_ {0.0f}; + float ameMinTemp_ {0.0f}; + float rcvrModMaxTemp_ {0.0f}; + float rcvrModMinTemp_ {0.0f}; + float biteModMaxTemp_ {0.0f}; + float biteModMinTemp_ {0.0f}; + std::uint32_t defaultPolarization_ {0}; + float trLimitDgradLimit_ {0.0f}; + float trLimitFailLimit_ {0.0f}; + bool rfpStepperEnabled_ {false}; + float ameCurrentTolerance_ {0.0f}; + std::uint32_t hOnlyPolarization_ {0}; + std::uint32_t vOnlyPolarization_ {0}; + float sunBias_ {0.0f}; + float aMinShelterTempWarn_ {0.0f}; + float powerMeterZero_ {0.0f}; + float txbBaseline_ {0.0f}; + float txbAlarmThresh_ {0.0f}; + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) }; RdaAdaptationData::RdaAdaptationData() : - Level2Message(), p(std::make_unique()) + Level2Message(), p(std::make_unique()) { } RdaAdaptationData::~RdaAdaptationData() = default; @@ -434,7 +270,7 @@ float RdaAdaptationData::parkel() const float RdaAdaptationData::a_fuel_conv(unsigned i) const { - return p->aFuelConv_[i]; + return p->aFuelConv_.at(i); } float RdaAdaptationData::a_min_shelter_temp() const @@ -517,27 +353,27 @@ bool RdaAdaptationData::rms_installed() const return p->rmsInstalled_; } -uint32_t RdaAdaptationData::a_hvdl_tst_int() const +std::uint32_t RdaAdaptationData::a_hvdl_tst_int() const { return p->aHvdlTstInt_; } -uint32_t RdaAdaptationData::a_rpg_lt_int() const +std::uint32_t RdaAdaptationData::a_rpg_lt_int() const { return p->aRpgLtInt_; } -uint32_t RdaAdaptationData::a_min_stab_util_pwr_time() const +std::uint32_t RdaAdaptationData::a_min_stab_util_pwr_time() const { return p->aMinStabUtilPwrTime_; } -uint32_t RdaAdaptationData::a_gen_auto_exer_interval() const +std::uint32_t RdaAdaptationData::a_gen_auto_exer_interval() const { return p->aGenAutoExerInterval_; } -uint32_t RdaAdaptationData::a_util_pwr_sw_req_interval() const +std::uint32_t RdaAdaptationData::a_util_pwr_sw_req_interval() const { return p->aUtilPwrSwReqInterval_; } @@ -547,19 +383,19 @@ float RdaAdaptationData::a_low_fuel_level() const return p->aLowFuelLevel_; } -uint32_t RdaAdaptationData::config_chan_number() const +std::uint32_t RdaAdaptationData::config_chan_number() const { return p->configChanNumber_; } -uint32_t RdaAdaptationData::redundant_chan_config() const +std::uint32_t RdaAdaptationData::redundant_chan_config() const { return p->redundantChanConfig_; } float RdaAdaptationData::atten_table(unsigned i) const { - return p->attenTable_[i]; + return p->attenTable_.at(i); } float RdaAdaptationData::path_losses(unsigned i) const @@ -569,41 +405,49 @@ float RdaAdaptationData::path_losses(unsigned i) const float RdaAdaptationData::h_coupler_xmt_loss() const { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) return path_losses(29); } float RdaAdaptationData::h_coupler_cw_loss() const { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) return path_losses(48); } float RdaAdaptationData::v_coupler_xmt_loss() const { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) return path_losses(49); } float RdaAdaptationData::ame_ts_bias() const { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) return path_losses(51); } float RdaAdaptationData::v_coupler_cw_loss() const { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) return path_losses(53); } float RdaAdaptationData::pwr_sense_bias() const { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) return path_losses(56); } float RdaAdaptationData::ame_v_noise_enr() const { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) return path_losses(57); } float RdaAdaptationData::chan_cal_diff() const { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) return path_losses(70); } @@ -614,20 +458,20 @@ float RdaAdaptationData::v_ts_cw() const float RdaAdaptationData::h_rnscale(unsigned i) const { - return p->hRnscale_[i]; + return p->hRnscale_.at(i); } float RdaAdaptationData::atmos(unsigned i) const { - return p->atmos_[i]; + return p->atmos_.at(i); } float RdaAdaptationData::el_index(unsigned i) const { - return p->elIndex_[i]; + return p->elIndex_.at(i); } -uint32_t RdaAdaptationData::tfreq_mhz() const +std::uint32_t RdaAdaptationData::tfreq_mhz() const { return p->tfreqMhz_; } @@ -652,12 +496,12 @@ float RdaAdaptationData::tar_v_dbz0_lp() const return p->tarVDbz0Lp_; } -uint32_t RdaAdaptationData::init_phi_dp() const +std::uint32_t RdaAdaptationData::init_phi_dp() const { return p->initPhiDp_; } -uint32_t RdaAdaptationData::norm_init_phi_dp() const +std::uint32_t RdaAdaptationData::norm_init_phi_dp() const { return p->normInitPhiDp_; } @@ -697,7 +541,7 @@ float RdaAdaptationData::h_noisetemp_dgrad_limit() const return p->hNoisetempDgradLimit_; } -uint32_t RdaAdaptationData::h_min_noisetemp() const +std::uint32_t RdaAdaptationData::h_min_noisetemp() const { return p->hMinNoisetemp_; } @@ -707,7 +551,7 @@ float RdaAdaptationData::v_noisetemp_dgrad_limit() const return p->vNoisetempDgradLimit_; } -uint32_t RdaAdaptationData::v_min_noisetemp() const +std::uint32_t RdaAdaptationData::v_min_noisetemp() const { return p->vMinNoisetemp_; } @@ -792,32 +636,32 @@ float RdaAdaptationData::tar_v_dbz0_sp() const return p->tarVDbz0Sp_; } -uint32_t RdaAdaptationData::deltaprf() const +std::uint32_t RdaAdaptationData::deltaprf() const { return p->deltaprf_; } -uint32_t RdaAdaptationData::tau_sp() const +std::uint32_t RdaAdaptationData::tau_sp() const { return p->tauSp_; } -uint32_t RdaAdaptationData::tau_lp() const +std::uint32_t RdaAdaptationData::tau_lp() const { return p->tauLp_; } -uint32_t RdaAdaptationData::nc_dead_value() const +std::uint32_t RdaAdaptationData::nc_dead_value() const { return p->ncDeadValue_; } -uint32_t RdaAdaptationData::tau_rf_sp() const +std::uint32_t RdaAdaptationData::tau_rf_sp() const { return p->tauRfSp_; } -uint32_t RdaAdaptationData::tau_rf_lp() const +std::uint32_t RdaAdaptationData::tau_rf_lp() const { return p->tauRfLp_; } @@ -837,22 +681,22 @@ float RdaAdaptationData::slonsec() const return p->slonsec_; } -uint32_t RdaAdaptationData::slatdeg() const +std::uint32_t RdaAdaptationData::slatdeg() const { return p->slatdeg_; } -uint32_t RdaAdaptationData::slatmin() const +std::uint32_t RdaAdaptationData::slatmin() const { return p->slatmin_; } -uint32_t RdaAdaptationData::slondeg() const +std::uint32_t RdaAdaptationData::slondeg() const { return p->slondeg_; } -uint32_t RdaAdaptationData::slonmin() const +std::uint32_t RdaAdaptationData::slonmin() const { return p->slonmin_; } @@ -867,6 +711,16 @@ char RdaAdaptationData::slondir() const return p->slondir_; } +double RdaAdaptationData::dig_rcvr_clock_freq() const +{ + return p->digRcvrClockFreq_; +} + +double RdaAdaptationData::coho_freq() const +{ + return p->cohoFreq_; +} + float RdaAdaptationData::az_correction_factor() const { return p->azCorrectionFactor_; @@ -885,31 +739,31 @@ std::string RdaAdaptationData::site_name() const float RdaAdaptationData::ant_manual_setup_ielmin() const { constexpr float SCALE = 360.0f / 65536.0f; - return p->antManualSetup_.ielmin_ * SCALE; + return static_cast(p->antManualSetup_.ielmin_) * SCALE; } float RdaAdaptationData::ant_manual_setup_ielmax() const { constexpr float SCALE = 360.0f / 65536.0f; - return p->antManualSetup_.ielmax_ * SCALE; + return static_cast(p->antManualSetup_.ielmax_) * SCALE; } -uint32_t RdaAdaptationData::ant_manual_setup_fazvelmax() const +std::uint32_t RdaAdaptationData::ant_manual_setup_fazvelmax() const { return p->antManualSetup_.fazvelmax_; } -uint32_t RdaAdaptationData::ant_manual_setup_felvelmax() const +std::uint32_t RdaAdaptationData::ant_manual_setup_felvelmax() const { return p->antManualSetup_.felvelmax_; } -int32_t RdaAdaptationData::ant_manual_setup_ignd_hgt() const +std::int32_t RdaAdaptationData::ant_manual_setup_ignd_hgt() const { return p->antManualSetup_.igndHgt_; } -uint32_t RdaAdaptationData::ant_manual_setup_irad_hgt() const +std::uint32_t RdaAdaptationData::ant_manual_setup_irad_hgt() const { return p->antManualSetup_.iradHgt_; } @@ -999,14 +853,39 @@ float RdaAdaptationData::el_inertia() const return p->elInertia_; } -uint32_t RdaAdaptationData::rvp8nv_iwaveguide_length() const +float RdaAdaptationData::az_stow_angle() const +{ + return p->azStowAngle_; +} + +float RdaAdaptationData::el_stow_angle() const +{ + return p->elStowAngle_; +} + +float RdaAdaptationData::az_encoder_alignment() const +{ + return p->azEncoderAlignment_; +} + +float RdaAdaptationData::el_encoder_alignment() const +{ + return p->elEncoderAlignment_; +} + +std::string RdaAdaptationData::refined_park() const +{ + return p->refinedPark_; +} + +std::uint32_t RdaAdaptationData::rvp8nv_iwaveguide_length() const { return p->rvp8nvIwaveguideLength_; } float RdaAdaptationData::v_rnscale(unsigned i) const { - return p->vRnscale_[i]; + return p->vRnscale_.at(i); } float RdaAdaptationData::vel_data_tover() const @@ -1024,7 +903,7 @@ float RdaAdaptationData::doppler_range_start() const return p->dopplerRangeStart_; } -uint32_t RdaAdaptationData::max_el_index() const +std::uint32_t RdaAdaptationData::max_el_index() const { return p->maxElIndex_; } @@ -1044,7 +923,7 @@ float RdaAdaptationData::seg4_lim() const return p->seg4Lim_; } -uint32_t RdaAdaptationData::nbr_el_segments() const +std::uint32_t RdaAdaptationData::nbr_el_segments() const { return p->nbrElSegments_; } @@ -1094,14 +973,14 @@ float RdaAdaptationData::min_v_dyn_range() const return p->minVDynRange_; } -float RdaAdaptationData::zdr_bias_dgrad_lim() const +float RdaAdaptationData::zdr_offset_dgrad_lim() const { - return p->zdrBiasDgradLim_; + return p->zdrOffsetDgradLim_; } -float RdaAdaptationData::baseline_zdr_bias() const +float RdaAdaptationData::baseline_zdr_offset() const { - return p->baselineZdrBias_; + return p->baselineZdrOffset_; } float RdaAdaptationData::v_noise_long() const @@ -1204,7 +1083,7 @@ float RdaAdaptationData::bite_mod_min_temp() const return p->biteModMinTemp_; } -uint32_t RdaAdaptationData::default_polarization() const +std::uint32_t RdaAdaptationData::default_polarization() const { return p->defaultPolarization_; } @@ -1229,12 +1108,12 @@ float RdaAdaptationData::ame_current_tolerance() const return p->ameCurrentTolerance_; } -uint32_t RdaAdaptationData::h_only_polarization() const +std::uint32_t RdaAdaptationData::h_only_polarization() const { return p->hOnlyPolarization_; } -uint32_t RdaAdaptationData::v_only_polarization() const +std::uint32_t RdaAdaptationData::v_only_polarization() const { return p->vOnlyPolarization_; } @@ -1268,15 +1147,17 @@ bool RdaAdaptationData::Parse(std::istream& is) { logger_->trace("Parsing RDA Adaptation Data (Message Type 18)"); - bool messageValid = true; - size_t bytesRead = 0; + bool messageValid = true; + std::size_t bytesRead = 0; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers): Readability p->adapFileName_.resize(12); p->adapFormat_.resize(4); p->adapRevision_.resize(4); p->adapDate_.resize(12); p->adapTime_.resize(12); p->siteName_.resize(4); + p->refinedPark_.resize(4); is.read(&p->adapFileName_[0], 12); // 0-11 is.read(&p->adapFormat_[0], 4); // 12-15 @@ -1292,7 +1173,7 @@ bool RdaAdaptationData::Parse(std::istream& is) is.read(reinterpret_cast(&p->parkel_), 4); // 64-67 is.read(reinterpret_cast(&p->aFuelConv_[0]), - p->aFuelConv_.size() * 4); // 68-111 + static_cast(p->aFuelConv_.size() * 4)); // 68-111 is.read(reinterpret_cast(&p->aMinShelterTemp_), 4); // 112-115 is.read(reinterpret_cast(&p->aMaxShelterTemp_), 4); // 116-119 @@ -1330,7 +1211,7 @@ bool RdaAdaptationData::Parse(std::istream& is) is.read(reinterpret_cast(&p->redundantChanConfig_), 4); // 224-227 is.read(reinterpret_cast(&p->attenTable_[0]), - p->attenTable_.size() * 4); // 228-643 + static_cast(p->attenTable_.size() * 4)); // 228-643 is.seekg(24, std::ios_base::cur); // 644-667 is.read(reinterpret_cast(&p->pathLosses_[7]), 4); // 668-671 @@ -1383,13 +1264,13 @@ bool RdaAdaptationData::Parse(std::istream& is) is.read(reinterpret_cast(&p->vTsCw_), 4); // 936-939 is.read(reinterpret_cast(&p->hRnscale_[0]), - p->hRnscale_.size() * 4); // 940-991 + static_cast(p->hRnscale_.size() * 4)); // 940-991 is.read(reinterpret_cast(&p->atmos_[0]), - p->atmos_.size() * 4); // 992-1043 + static_cast(p->atmos_.size() * 4)); // 992-1043 is.read(reinterpret_cast(&p->elIndex_[0]), - p->elIndex_.size() * 4); // 1044-1091 + static_cast(p->elIndex_.size() * 4)); // 1044-1091 is.read(reinterpret_cast(&p->tfreqMhz_), 4); // 1092-1095 is.read(reinterpret_cast(&p->baseDataTcn_), 4); // 1096-1099 @@ -1458,7 +1339,12 @@ bool RdaAdaptationData::Parse(std::istream& is) ReadChar(is, p->slatdir_); // 1316-1319 ReadChar(is, p->slondir_); // 1320-1323 - is.seekg(7036, std::ios_base::cur); // 1324-8359 + is.seekg(1176, std::ios_base::cur); // 1324-2499 + + is.read(reinterpret_cast(&p->digRcvrClockFreq_), 8); // 2500-2507 + is.read(reinterpret_cast(&p->cohoFreq_), 8); // 2508-2515 + + is.seekg(5844, std::ios_base::cur); // 2516-8359 is.read(reinterpret_cast(&p->azCorrectionFactor_), 4); // 8360-8363 is.read(reinterpret_cast(&p->elCorrectionFactor_), 4); // 8364-8367 @@ -1493,17 +1379,29 @@ bool RdaAdaptationData::Parse(std::istream& is) is.read(reinterpret_cast(&p->azIntertia_), 4); // 8456-8459 is.read(reinterpret_cast(&p->elInertia_), 4); // 8460-8463 - is.seekg(232, std::ios_base::cur); // 8464-8695 + is.seekg(32, std::ios_base::cur); // 8464-8495 + + is.read(reinterpret_cast(&p->azStowAngle_), 4); // 8496-8499 + is.read(reinterpret_cast(&p->elStowAngle_), 4); // 8500-8503 + is.read(reinterpret_cast(&p->azEncoderAlignment_), 4); // 8504-8507 + is.read(reinterpret_cast(&p->elEncoderAlignment_), 4); // 8508-8511 + + is.seekg(176, std::ios_base::cur); // 8512-8687 + + is.read(&p->refinedPark_[0], 4); // 8688-8691 + + is.seekg(4, std::ios_base::cur); // 8692-8695 is.read(reinterpret_cast(&p->rvp8nvIwaveguideLength_), 4); // 8696-8699 is.read(reinterpret_cast(&p->vRnscale_[0]), - 11 * 4); // 8700-8743 + static_cast(11 * 4)); // 8700-8743 - is.read(reinterpret_cast(&p->velDataTover_), 4); // 8744-8747 - is.read(reinterpret_cast(&p->widthDataTover_), 4); // 8748-8751 - is.read(reinterpret_cast(&p->vRnscale_[11]), 2 * 4); // 8752-8759 + is.read(reinterpret_cast(&p->velDataTover_), 4); // 8744-8747 + is.read(reinterpret_cast(&p->widthDataTover_), 4); // 8748-8751 + is.read(reinterpret_cast(&p->vRnscale_[11]), + static_cast(2 * 4)); // 8752-8759 is.seekg(4, std::ios_base::cur); // 8760-8763 @@ -1522,8 +1420,8 @@ bool RdaAdaptationData::Parse(std::istream& is) ReadBoolean(is, p->genExercise_); // 8812-8815 is.read(reinterpret_cast(&p->vNoiseTolerance_), 4); // 8816-8819 is.read(reinterpret_cast(&p->minVDynRange_), 4); // 8820-8823 - is.read(reinterpret_cast(&p->zdrBiasDgradLim_), 4); // 8824-8827 - is.read(reinterpret_cast(&p->baselineZdrBias_), 4); // 8828-8831 + is.read(reinterpret_cast(&p->zdrOffsetDgradLim_), 4); // 8824-8827 + is.read(reinterpret_cast(&p->baselineZdrOffset_), 4); // 8828-8831 is.seekg(12, std::ios_base::cur); // 8832-8843 @@ -1573,6 +1471,8 @@ bool RdaAdaptationData::Parse(std::istream& is) bytesRead += 9468; + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + p->lowerPreLimit_ = SwapFloat(p->lowerPreLimit_); p->azLat_ = SwapFloat(p->azLat_); p->upperPreLimit_ = SwapFloat(p->upperPreLimit_); @@ -1612,78 +1512,87 @@ bool RdaAdaptationData::Parse(std::istream& is) SwapArray(p->atmos_); SwapArray(p->elIndex_); - p->tfreqMhz_ = ntohl(p->tfreqMhz_); - p->baseDataTcn_ = SwapFloat(p->baseDataTcn_); - p->reflDataTover_ = SwapFloat(p->reflDataTover_); - p->tarHDbz0Lp_ = SwapFloat(p->tarHDbz0Lp_); - p->tarVDbz0Lp_ = SwapFloat(p->tarVDbz0Lp_); - p->initPhiDp_ = ntohl(p->initPhiDp_); - p->normInitPhiDp_ = ntohl(p->normInitPhiDp_); - p->lxLp_ = SwapFloat(p->lxLp_); - p->lxSp_ = SwapFloat(p->lxSp_); - p->meteorParam_ = SwapFloat(p->meteorParam_); - p->antennaGain_ = SwapFloat(p->antennaGain_); - p->velDegradLimit_ = SwapFloat(p->velDegradLimit_); - p->wthDegradLimit_ = SwapFloat(p->wthDegradLimit_); - p->hNoisetempDgradLimit_ = SwapFloat(p->hNoisetempDgradLimit_); - p->hMinNoisetemp_ = ntohl(p->hMinNoisetemp_); - p->vNoisetempDgradLimit_ = SwapFloat(p->vNoisetempDgradLimit_); - p->vMinNoisetemp_ = ntohl(p->vMinNoisetemp_); - p->klyDegradeLimit_ = SwapFloat(p->klyDegradeLimit_); - p->tsCoho_ = SwapFloat(p->tsCoho_); - p->hTsCw_ = SwapFloat(p->hTsCw_); - p->tsStalo_ = SwapFloat(p->tsStalo_); - p->ameHNoiseEnr_ = SwapFloat(p->ameHNoiseEnr_); - p->xmtrPeakPwrHighLimit_ = SwapFloat(p->xmtrPeakPwrHighLimit_); - p->xmtrPeakPwrLowLimit_ = SwapFloat(p->xmtrPeakPwrLowLimit_); - p->hDbz0DeltaLimit_ = SwapFloat(p->hDbz0DeltaLimit_); - p->threshold1_ = SwapFloat(p->threshold1_); - p->threshold2_ = SwapFloat(p->threshold2_); - p->clutSuppDgradLim_ = SwapFloat(p->clutSuppDgradLim_); - p->range0Value_ = SwapFloat(p->range0Value_); - p->xmtrPwrMtrScale_ = SwapFloat(p->xmtrPwrMtrScale_); - p->vDbz0DeltaLimit_ = SwapFloat(p->vDbz0DeltaLimit_); - p->tarHDbz0Sp_ = SwapFloat(p->tarHDbz0Sp_); - p->tarVDbz0Sp_ = SwapFloat(p->tarVDbz0Sp_); - p->deltaprf_ = ntohl(p->deltaprf_); - p->tauSp_ = ntohl(p->tauSp_); - p->tauLp_ = ntohl(p->tauLp_); - p->ncDeadValue_ = ntohl(p->ncDeadValue_); - p->tauRfSp_ = ntohl(p->tauRfSp_); - p->tauRfLp_ = ntohl(p->tauRfLp_); - p->seg1Lim_ = SwapFloat(p->seg1Lim_); - p->slatsec_ = SwapFloat(p->slatsec_); - p->slonsec_ = SwapFloat(p->slonsec_); - p->slatdeg_ = ntohl(p->slatdeg_); - p->slatmin_ = ntohl(p->slatmin_); - p->slondeg_ = ntohl(p->slondeg_); - p->slonmin_ = ntohl(p->slonmin_); - p->azCorrectionFactor_ = SwapFloat(p->azCorrectionFactor_); - p->elCorrectionFactor_ = SwapFloat(p->elCorrectionFactor_); - p->antManualSetup_.ielmin_ = ntohl(p->antManualSetup_.ielmin_); - p->antManualSetup_.ielmax_ = ntohl(p->antManualSetup_.ielmax_); + p->tfreqMhz_ = ntohl(p->tfreqMhz_); + p->baseDataTcn_ = SwapFloat(p->baseDataTcn_); + p->reflDataTover_ = SwapFloat(p->reflDataTover_); + p->tarHDbz0Lp_ = SwapFloat(p->tarHDbz0Lp_); + p->tarVDbz0Lp_ = SwapFloat(p->tarVDbz0Lp_); + p->initPhiDp_ = ntohl(p->initPhiDp_); + p->normInitPhiDp_ = ntohl(p->normInitPhiDp_); + p->lxLp_ = SwapFloat(p->lxLp_); + p->lxSp_ = SwapFloat(p->lxSp_); + p->meteorParam_ = SwapFloat(p->meteorParam_); + p->antennaGain_ = SwapFloat(p->antennaGain_); + p->velDegradLimit_ = SwapFloat(p->velDegradLimit_); + p->wthDegradLimit_ = SwapFloat(p->wthDegradLimit_); + p->hNoisetempDgradLimit_ = SwapFloat(p->hNoisetempDgradLimit_); + p->hMinNoisetemp_ = ntohl(p->hMinNoisetemp_); + p->vNoisetempDgradLimit_ = SwapFloat(p->vNoisetempDgradLimit_); + p->vMinNoisetemp_ = ntohl(p->vMinNoisetemp_); + p->klyDegradeLimit_ = SwapFloat(p->klyDegradeLimit_); + p->tsCoho_ = SwapFloat(p->tsCoho_); + p->hTsCw_ = SwapFloat(p->hTsCw_); + p->tsStalo_ = SwapFloat(p->tsStalo_); + p->ameHNoiseEnr_ = SwapFloat(p->ameHNoiseEnr_); + p->xmtrPeakPwrHighLimit_ = SwapFloat(p->xmtrPeakPwrHighLimit_); + p->xmtrPeakPwrLowLimit_ = SwapFloat(p->xmtrPeakPwrLowLimit_); + p->hDbz0DeltaLimit_ = SwapFloat(p->hDbz0DeltaLimit_); + p->threshold1_ = SwapFloat(p->threshold1_); + p->threshold2_ = SwapFloat(p->threshold2_); + p->clutSuppDgradLim_ = SwapFloat(p->clutSuppDgradLim_); + p->range0Value_ = SwapFloat(p->range0Value_); + p->xmtrPwrMtrScale_ = SwapFloat(p->xmtrPwrMtrScale_); + p->vDbz0DeltaLimit_ = SwapFloat(p->vDbz0DeltaLimit_); + p->tarHDbz0Sp_ = SwapFloat(p->tarHDbz0Sp_); + p->tarVDbz0Sp_ = SwapFloat(p->tarVDbz0Sp_); + p->deltaprf_ = ntohl(p->deltaprf_); + p->tauSp_ = ntohl(p->tauSp_); + p->tauLp_ = ntohl(p->tauLp_); + p->ncDeadValue_ = ntohl(p->ncDeadValue_); + p->tauRfSp_ = ntohl(p->tauRfSp_); + p->tauRfLp_ = ntohl(p->tauRfLp_); + p->seg1Lim_ = SwapFloat(p->seg1Lim_); + p->slatsec_ = SwapFloat(p->slatsec_); + p->slonsec_ = SwapFloat(p->slonsec_); + p->slatdeg_ = ntohl(p->slatdeg_); + p->slatmin_ = ntohl(p->slatmin_); + p->slondeg_ = ntohl(p->slondeg_); + p->slonmin_ = ntohl(p->slonmin_); + p->digRcvrClockFreq_ = SwapDouble(p->digRcvrClockFreq_); + p->cohoFreq_ = SwapDouble(p->cohoFreq_); + p->azCorrectionFactor_ = SwapFloat(p->azCorrectionFactor_); + p->elCorrectionFactor_ = SwapFloat(p->elCorrectionFactor_); + p->antManualSetup_.ielmin_ = + static_cast(ntohl(p->antManualSetup_.ielmin_)); + p->antManualSetup_.ielmax_ = + static_cast(ntohl(p->antManualSetup_.ielmax_)); p->antManualSetup_.fazvelmax_ = ntohl(p->antManualSetup_.fazvelmax_); p->antManualSetup_.felvelmax_ = ntohl(p->antManualSetup_.felvelmax_); - p->antManualSetup_.igndHgt_ = ntohl(p->antManualSetup_.igndHgt_); - p->antManualSetup_.iradHgt_ = ntohl(p->antManualSetup_.iradHgt_); - p->azPosSustainDrive_ = SwapFloat(p->azPosSustainDrive_); - p->azNegSustainDrive_ = SwapFloat(p->azNegSustainDrive_); - p->azNomPosDriveSlope_ = SwapFloat(p->azNomPosDriveSlope_); - p->azNomNegDriveSlope_ = SwapFloat(p->azNomNegDriveSlope_); - p->azFeedbackSlope_ = SwapFloat(p->azFeedbackSlope_); - p->elPosSustainDrive_ = SwapFloat(p->elPosSustainDrive_); - p->elNegSustainDrive_ = SwapFloat(p->elNegSustainDrive_); - p->elNomPosDriveSlope_ = SwapFloat(p->elNomPosDriveSlope_); - p->elNomNegDriveSlope_ = SwapFloat(p->elNomNegDriveSlope_); - p->elFeedbackSlope_ = SwapFloat(p->elFeedbackSlope_); - p->elFirstSlope_ = SwapFloat(p->elFirstSlope_); - p->elSecondSlope_ = SwapFloat(p->elSecondSlope_); - p->elThirdSlope_ = SwapFloat(p->elThirdSlope_); - p->elDroopPos_ = SwapFloat(p->elDroopPos_); - p->elOffNeutralDrive_ = SwapFloat(p->elOffNeutralDrive_); - p->azIntertia_ = SwapFloat(p->azIntertia_); - p->elInertia_ = SwapFloat(p->elInertia_); - p->rvp8nvIwaveguideLength_ = ntohl(p->rvp8nvIwaveguideLength_); + p->antManualSetup_.igndHgt_ = + static_cast(ntohl(p->antManualSetup_.igndHgt_)); + p->antManualSetup_.iradHgt_ = ntohl(p->antManualSetup_.iradHgt_); + p->azPosSustainDrive_ = SwapFloat(p->azPosSustainDrive_); + p->azNegSustainDrive_ = SwapFloat(p->azNegSustainDrive_); + p->azNomPosDriveSlope_ = SwapFloat(p->azNomPosDriveSlope_); + p->azNomNegDriveSlope_ = SwapFloat(p->azNomNegDriveSlope_); + p->azFeedbackSlope_ = SwapFloat(p->azFeedbackSlope_); + p->elPosSustainDrive_ = SwapFloat(p->elPosSustainDrive_); + p->elNegSustainDrive_ = SwapFloat(p->elNegSustainDrive_); + p->elNomPosDriveSlope_ = SwapFloat(p->elNomPosDriveSlope_); + p->elNomNegDriveSlope_ = SwapFloat(p->elNomNegDriveSlope_); + p->elFeedbackSlope_ = SwapFloat(p->elFeedbackSlope_); + p->elFirstSlope_ = SwapFloat(p->elFirstSlope_); + p->elSecondSlope_ = SwapFloat(p->elSecondSlope_); + p->elThirdSlope_ = SwapFloat(p->elThirdSlope_); + p->elDroopPos_ = SwapFloat(p->elDroopPos_); + p->elOffNeutralDrive_ = SwapFloat(p->elOffNeutralDrive_); + p->azIntertia_ = SwapFloat(p->azIntertia_); + p->elInertia_ = SwapFloat(p->elInertia_); + p->azStowAngle_ = SwapFloat(p->azStowAngle_); + p->elStowAngle_ = SwapFloat(p->elStowAngle_); + p->azEncoderAlignment_ = SwapFloat(p->azEncoderAlignment_); + p->elEncoderAlignment_ = SwapFloat(p->elEncoderAlignment_); + p->rvp8nvIwaveguideLength_ = ntohl(p->rvp8nvIwaveguideLength_); SwapArray(p->vRnscale_); @@ -1702,8 +1611,8 @@ bool RdaAdaptationData::Parse(std::istream& is) p->minHDynRange_ = SwapFloat(p->minHDynRange_); p->vNoiseTolerance_ = SwapFloat(p->vNoiseTolerance_); p->minVDynRange_ = SwapFloat(p->minVDynRange_); - p->zdrBiasDgradLim_ = SwapFloat(p->zdrBiasDgradLim_); - p->baselineZdrBias_ = SwapFloat(p->baselineZdrBias_); + p->zdrOffsetDgradLim_ = SwapFloat(p->zdrOffsetDgradLim_); + p->baselineZdrOffset_ = SwapFloat(p->baselineZdrOffset_); p->vNoiseLong_ = SwapFloat(p->vNoiseLong_); p->vNoiseShort_ = SwapFloat(p->vNoiseShort_); p->zdrDataTover_ = SwapFloat(p->zdrDataTover_); @@ -1759,6 +1668,4 @@ RdaAdaptationData::Create(Level2MessageHeader&& header, std::istream& is) return message; } -} // namespace rda -} // namespace wsr88d -} // namespace scwx +} // namespace scwx::wsr88d::rda diff --git a/wxdata/source/scwx/wsr88d/rda/rda_prf_data.cpp b/wxdata/source/scwx/wsr88d/rda/rda_prf_data.cpp new file mode 100644 index 00000000..147714c7 --- /dev/null +++ b/wxdata/source/scwx/wsr88d/rda/rda_prf_data.cpp @@ -0,0 +1,120 @@ +#include +#include + +namespace scwx::wsr88d::rda +{ + +static const std::string logPrefix_ = "scwx::wsr88d::rda::rda_prf_data"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +struct RdaPrfWaveformData +{ + std::uint16_t waveformType_ {0}; + std::uint16_t prfCount_ {0}; + std::vector prfValues_ {}; +}; + +class RdaPrfData::Impl +{ +public: + explicit Impl() = default; + ~Impl() = default; + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + + std::uint16_t numberOfWaveforms_ {0}; + std::vector waveformData_ {}; +}; + +RdaPrfData::RdaPrfData() : p(std::make_unique()) {} +RdaPrfData::~RdaPrfData() = default; + +RdaPrfData::RdaPrfData(RdaPrfData&&) noexcept = default; +RdaPrfData& RdaPrfData::operator=(RdaPrfData&&) noexcept = default; + +bool RdaPrfData::Parse(std::istream& is) +{ + logger_->trace("Parsing RDA PRF Data (Message Type 32)"); + + bool messageValid = true; + std::size_t bytesRead = 0; + + const std::streampos isBegin = is.tellg(); + + is.read(reinterpret_cast(&p->numberOfWaveforms_), 2); // 1 + is.seekg(2, std::ios_base::cur); // 2 + + bytesRead += 4; + + p->numberOfWaveforms_ = ntohs(p->numberOfWaveforms_); + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers): Readability + if (p->numberOfWaveforms_ < 1 || p->numberOfWaveforms_ > 5) + { + logger_->warn("Invalid number of waveforms: {}", p->numberOfWaveforms_); + p->numberOfWaveforms_ = 0; + messageValid = false; + } + + p->waveformData_.resize(p->numberOfWaveforms_); + + for (std::uint16_t i = 0; i < p->numberOfWaveforms_; ++i) + { + auto& w = p->waveformData_[i]; + + is.read(reinterpret_cast(&w.waveformType_), 2); // P1 + is.read(reinterpret_cast(&w.prfCount_), 2); // P2 + + w.waveformType_ = ntohs(w.waveformType_); + w.prfCount_ = ntohs(w.prfCount_); + + bytesRead += 4; + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers): Readability + if (w.prfCount_ > 255) + { + logger_->warn("Invalid PRF count: {} (waveform {})", w.prfCount_, i); + w.prfCount_ = 0; + messageValid = false; + break; + } + + w.prfValues_.resize(w.prfCount_); + + for (std::uint16_t j = 0; j < w.prfCount_; ++j) + { + is.read(reinterpret_cast(&w.prfValues_[j]), 4); + } + + bytesRead += static_cast(w.prfCount_) * 4; + + SwapVector(w.prfValues_); + } + + is.seekg(isBegin, std::ios_base::beg); + if (!ValidateMessage(is, bytesRead)) + { + messageValid = false; + } + + return messageValid; +} + +std::shared_ptr RdaPrfData::Create(Level2MessageHeader&& header, + std::istream& is) +{ + std::shared_ptr message = std::make_shared(); + message->set_header(std::move(header)); + + if (!message->Parse(is)) + { + message.reset(); + } + + return message; +} + +} // namespace scwx::wsr88d::rda diff --git a/wxdata/source/scwx/wsr88d/rda/rda_status_data.cpp b/wxdata/source/scwx/wsr88d/rda/rda_status_data.cpp index ced8ff59..81ff4463 100644 --- a/wxdata/source/scwx/wsr88d/rda/rda_status_data.cpp +++ b/wxdata/source/scwx/wsr88d/rda/rda_status_data.cpp @@ -1,232 +1,214 @@ #include #include -namespace scwx -{ -namespace wsr88d -{ -namespace rda +namespace scwx::wsr88d::rda { static const std::string logPrefix_ = "scwx::wsr88d::rda::rda_status_data"; static const auto logger_ = util::Logger::Create(logPrefix_); -class RdaStatusDataImpl +class RdaStatusData::Impl { public: - explicit RdaStatusDataImpl() : - rdaStatus_ {0}, - operabilityStatus_ {0}, - controlStatus_ {0}, - auxiliaryPowerGeneratorState_ {0}, - averageTransmitterPower_ {0}, - horizontalReflectivityCalibrationCorrection_ {0}, - dataTransmissionEnabled_ {0}, - volumeCoveragePatternNumber_ {0}, - rdaControlAuthorization_ {0}, - rdaBuildNumber_ {0}, - operationalMode_ {0}, - superResolutionStatus_ {0}, - clutterMitigationDecisionStatus_ {0}, - avsetEbcRdaLogDataStatus_ {0}, - rdaAlarmSummary_ {0}, - commandAcknowledgement_ {0}, - channelControlStatus_ {0}, - spotBlankingStatus_ {0}, - bypassMapGenerationDate_ {0}, - bypassMapGenerationTime_ {0}, - clutterFilterMapGenerationDate_ {0}, - clutterFilterMapGenerationTime_ {0}, - verticalReflectivityCalibrationCorrection_ {0}, - transitionPowerSourceStatus_ {0}, - rmsControlStatus_ {0}, - performanceCheckStatus_ {0}, - alarmCodes_ {0}, - signalProcessingOptions_ {0}, - statusVersion_ {0} {}; - ~RdaStatusDataImpl() = default; + explicit Impl() = default; + ~Impl() = default; - uint16_t rdaStatus_; - uint16_t operabilityStatus_; - uint16_t controlStatus_; - uint16_t auxiliaryPowerGeneratorState_; - uint16_t averageTransmitterPower_; - int16_t horizontalReflectivityCalibrationCorrection_; - uint16_t dataTransmissionEnabled_; - uint16_t volumeCoveragePatternNumber_; - uint16_t rdaControlAuthorization_; - uint16_t rdaBuildNumber_; - uint16_t operationalMode_; - uint16_t superResolutionStatus_; - uint16_t clutterMitigationDecisionStatus_; - uint16_t avsetEbcRdaLogDataStatus_; - uint16_t rdaAlarmSummary_; - uint16_t commandAcknowledgement_; - uint16_t channelControlStatus_; - uint16_t spotBlankingStatus_; - uint16_t bypassMapGenerationDate_; - uint16_t bypassMapGenerationTime_; - uint16_t clutterFilterMapGenerationDate_; - uint16_t clutterFilterMapGenerationTime_; - int16_t verticalReflectivityCalibrationCorrection_; - uint16_t transitionPowerSourceStatus_; - uint16_t rmsControlStatus_; - uint16_t performanceCheckStatus_; - std::array alarmCodes_; - uint16_t signalProcessingOptions_; - uint16_t statusVersion_; + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + + std::uint16_t rdaStatus_ {0}; + std::uint16_t operabilityStatus_ {0}; + std::uint16_t controlStatus_ {0}; + std::uint16_t auxiliaryPowerGeneratorState_ {0}; + std::uint16_t averageTransmitterPower_ {0}; + std::int16_t horizontalReflectivityCalibrationCorrection_ {0}; + std::uint16_t dataTransmissionEnabled_ {0}; + std::uint16_t volumeCoveragePatternNumber_ {0}; + std::uint16_t rdaControlAuthorization_ {0}; + std::uint16_t rdaBuildNumber_ {0}; + std::uint16_t operationalMode_ {0}; + std::uint16_t superResolutionStatus_ {0}; + std::uint16_t clutterMitigationDecisionStatus_ {0}; + std::uint16_t rdaScanAndDataFlags_ {0}; + std::uint16_t rdaAlarmSummary_ {0}; + std::uint16_t commandAcknowledgement_ {0}; + std::uint16_t channelControlStatus_ {0}; + std::uint16_t spotBlankingStatus_ {0}; + std::uint16_t bypassMapGenerationDate_ {0}; + std::uint16_t bypassMapGenerationTime_ {0}; + std::uint16_t clutterFilterMapGenerationDate_ {0}; + std::uint16_t clutterFilterMapGenerationTime_ {0}; + std::int16_t verticalReflectivityCalibrationCorrection_ {0}; + std::uint16_t transitionPowerSourceStatus_ {0}; + std::uint16_t rmsControlStatus_ {0}; + std::uint16_t performanceCheckStatus_ {0}; + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) + std::array alarmCodes_ {0}; + + std::uint16_t signalProcessingOptions_ {0}; + std::uint16_t downloadedPatternNumber_ {0}; + std::uint16_t statusVersion_ {0}; }; -RdaStatusData::RdaStatusData() : - Level2Message(), p(std::make_unique()) -{ -} +RdaStatusData::RdaStatusData() : Level2Message(), p(std::make_unique()) {} RdaStatusData::~RdaStatusData() = default; -RdaStatusData::RdaStatusData(RdaStatusData&&) noexcept = default; +RdaStatusData::RdaStatusData(RdaStatusData&&) noexcept = default; RdaStatusData& RdaStatusData::operator=(RdaStatusData&&) noexcept = default; -uint16_t RdaStatusData::rda_status() const +std::uint16_t RdaStatusData::rda_status() const { return p->rdaStatus_; } -uint16_t RdaStatusData::operability_status() const +std::uint16_t RdaStatusData::operability_status() const { return p->operabilityStatus_; } -uint16_t RdaStatusData::control_status() const +std::uint16_t RdaStatusData::control_status() const { return p->controlStatus_; } -uint16_t RdaStatusData::auxiliary_power_generator_state() const +std::uint16_t RdaStatusData::auxiliary_power_generator_state() const { return p->auxiliaryPowerGeneratorState_; } -uint16_t RdaStatusData::average_transmitter_power() const +std::uint16_t RdaStatusData::average_transmitter_power() const { return p->averageTransmitterPower_; } float RdaStatusData::horizontal_reflectivity_calibration_correction() const { - return p->horizontalReflectivityCalibrationCorrection_ * 0.01f; + constexpr float kScale_ = 0.01f; + return static_cast(p->horizontalReflectivityCalibrationCorrection_) * + kScale_; } -uint16_t RdaStatusData::data_transmission_enabled() const +std::uint16_t RdaStatusData::data_transmission_enabled() const { return p->dataTransmissionEnabled_; } -uint16_t RdaStatusData::volume_coverage_pattern_number() const +std::uint16_t RdaStatusData::volume_coverage_pattern_number() const { return p->volumeCoveragePatternNumber_; } -uint16_t RdaStatusData::rda_control_authorization() const +std::uint16_t RdaStatusData::rda_control_authorization() const { return p->rdaControlAuthorization_; } -uint16_t RdaStatusData::rda_build_number() const +std::uint16_t RdaStatusData::rda_build_number() const { return p->rdaBuildNumber_; } -uint16_t RdaStatusData::operational_mode() const +std::uint16_t RdaStatusData::operational_mode() const { return p->operationalMode_; } -uint16_t RdaStatusData::super_resolution_status() const +std::uint16_t RdaStatusData::super_resolution_status() const { return p->superResolutionStatus_; } -uint16_t RdaStatusData::clutter_mitigation_decision_status() const +std::uint16_t RdaStatusData::clutter_mitigation_decision_status() const { return p->clutterMitigationDecisionStatus_; } -uint16_t RdaStatusData::avset_ebc_rda_log_data_status() const +std::uint16_t RdaStatusData::rda_scan_and_data_flags() const { - return p->avsetEbcRdaLogDataStatus_; + return p->rdaScanAndDataFlags_; } -uint16_t RdaStatusData::rda_alarm_summary() const +std::uint16_t RdaStatusData::rda_alarm_summary() const { return p->rdaAlarmSummary_; } -uint16_t RdaStatusData::command_acknowledgement() const +std::uint16_t RdaStatusData::command_acknowledgement() const { return p->commandAcknowledgement_; } -uint16_t RdaStatusData::channel_control_status() const +std::uint16_t RdaStatusData::channel_control_status() const { return p->channelControlStatus_; } -uint16_t RdaStatusData::spot_blanking_status() const +std::uint16_t RdaStatusData::spot_blanking_status() const { return p->spotBlankingStatus_; } -uint16_t RdaStatusData::bypass_map_generation_date() const +std::uint16_t RdaStatusData::bypass_map_generation_date() const { return p->bypassMapGenerationDate_; } -uint16_t RdaStatusData::bypass_map_generation_time() const +std::uint16_t RdaStatusData::bypass_map_generation_time() const { return p->bypassMapGenerationTime_; } -uint16_t RdaStatusData::clutter_filter_map_generation_date() const +std::uint16_t RdaStatusData::clutter_filter_map_generation_date() const { return p->clutterFilterMapGenerationDate_; } -uint16_t RdaStatusData::clutter_filter_map_generation_time() const +std::uint16_t RdaStatusData::clutter_filter_map_generation_time() const { return p->clutterFilterMapGenerationTime_; } float RdaStatusData::vertical_reflectivity_calibration_correction() const { - return p->verticalReflectivityCalibrationCorrection_ * 0.01f; + constexpr float kScale_ = 0.01f; + return static_cast(p->verticalReflectivityCalibrationCorrection_) * + kScale_; } -uint16_t RdaStatusData::transition_power_source_status() const +std::uint16_t RdaStatusData::transition_power_source_status() const { return p->transitionPowerSourceStatus_; } -uint16_t RdaStatusData::rms_control_status() const +std::uint16_t RdaStatusData::rms_control_status() const { return p->rmsControlStatus_; } -uint16_t RdaStatusData::performance_check_status() const +std::uint16_t RdaStatusData::performance_check_status() const { return p->performanceCheckStatus_; } -uint16_t RdaStatusData::alarm_codes(unsigned i) const +std::uint16_t RdaStatusData::alarm_codes(unsigned i) const { - return p->alarmCodes_[i]; + return p->alarmCodes_.at(i); } -uint16_t RdaStatusData::signal_processing_options() const +std::uint16_t RdaStatusData::signal_processing_options() const { return p->signalProcessingOptions_; } -uint16_t RdaStatusData::status_version() const +std::uint16_t RdaStatusData::downloaded_pattern_number() const +{ + return p->downloadedPatternNumber_; +} + +std::uint16_t RdaStatusData::status_version() const { return p->statusVersion_; } @@ -238,6 +220,7 @@ bool RdaStatusData::Parse(std::istream& is) bool messageValid = true; size_t bytesRead = 0; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers): Readability is.read(reinterpret_cast(&p->rdaStatus_), 2); // 1 is.read(reinterpret_cast(&p->operabilityStatus_), 2); // 2 is.read(reinterpret_cast(&p->controlStatus_), 2); // 3 @@ -253,14 +236,14 @@ bool RdaStatusData::Parse(std::istream& is) is.read(reinterpret_cast(&p->operationalMode_), 2); // 11 is.read(reinterpret_cast(&p->superResolutionStatus_), 2); // 12 is.read(reinterpret_cast(&p->clutterMitigationDecisionStatus_), - 2); // 13 - is.read(reinterpret_cast(&p->avsetEbcRdaLogDataStatus_), 2); // 14 - is.read(reinterpret_cast(&p->rdaAlarmSummary_), 2); // 15 - is.read(reinterpret_cast(&p->commandAcknowledgement_), 2); // 16 - is.read(reinterpret_cast(&p->channelControlStatus_), 2); // 17 - is.read(reinterpret_cast(&p->spotBlankingStatus_), 2); // 18 - is.read(reinterpret_cast(&p->bypassMapGenerationDate_), 2); // 19 - is.read(reinterpret_cast(&p->bypassMapGenerationTime_), 2); // 20 + 2); // 13 + is.read(reinterpret_cast(&p->rdaScanAndDataFlags_), 2); // 14 + is.read(reinterpret_cast(&p->rdaAlarmSummary_), 2); // 15 + is.read(reinterpret_cast(&p->commandAcknowledgement_), 2); // 16 + is.read(reinterpret_cast(&p->channelControlStatus_), 2); // 17 + is.read(reinterpret_cast(&p->spotBlankingStatus_), 2); // 18 + is.read(reinterpret_cast(&p->bypassMapGenerationDate_), 2); // 19 + is.read(reinterpret_cast(&p->bypassMapGenerationTime_), 2); // 20 is.read(reinterpret_cast(&p->clutterFilterMapGenerationDate_), 2); // 21 is.read(reinterpret_cast(&p->clutterFilterMapGenerationTime_), @@ -272,7 +255,7 @@ bool RdaStatusData::Parse(std::istream& is) is.read(reinterpret_cast(&p->rmsControlStatus_), 2); // 25 is.read(reinterpret_cast(&p->performanceCheckStatus_), 2); // 26 is.read(reinterpret_cast(&p->alarmCodes_), - p->alarmCodes_.size() * 2); // 27-40 + static_cast(p->alarmCodes_.size() * 2)); // 27-40 bytesRead += 80; p->rdaStatus_ = ntohs(p->rdaStatus_); @@ -280,8 +263,8 @@ bool RdaStatusData::Parse(std::istream& is) p->controlStatus_ = ntohs(p->controlStatus_); p->auxiliaryPowerGeneratorState_ = ntohs(p->auxiliaryPowerGeneratorState_); p->averageTransmitterPower_ = ntohs(p->averageTransmitterPower_); - p->horizontalReflectivityCalibrationCorrection_ = - ntohs(p->horizontalReflectivityCalibrationCorrection_); + p->horizontalReflectivityCalibrationCorrection_ = static_cast( + ntohs(p->horizontalReflectivityCalibrationCorrection_)); p->dataTransmissionEnabled_ = ntohs(p->dataTransmissionEnabled_); p->volumeCoveragePatternNumber_ = ntohs(p->volumeCoveragePatternNumber_); p->rdaControlAuthorization_ = ntohs(p->rdaControlAuthorization_); @@ -290,36 +273,40 @@ bool RdaStatusData::Parse(std::istream& is) p->superResolutionStatus_ = ntohs(p->superResolutionStatus_); p->clutterMitigationDecisionStatus_ = ntohs(p->clutterMitigationDecisionStatus_); - p->avsetEbcRdaLogDataStatus_ = ntohs(p->avsetEbcRdaLogDataStatus_); - p->rdaAlarmSummary_ = ntohs(p->rdaAlarmSummary_); - p->commandAcknowledgement_ = ntohs(p->commandAcknowledgement_); - p->channelControlStatus_ = ntohs(p->channelControlStatus_); - p->spotBlankingStatus_ = ntohs(p->spotBlankingStatus_); - p->bypassMapGenerationDate_ = ntohs(p->bypassMapGenerationDate_); - p->bypassMapGenerationTime_ = ntohs(p->bypassMapGenerationTime_); + p->rdaScanAndDataFlags_ = ntohs(p->rdaScanAndDataFlags_); + p->rdaAlarmSummary_ = ntohs(p->rdaAlarmSummary_); + p->commandAcknowledgement_ = ntohs(p->commandAcknowledgement_); + p->channelControlStatus_ = ntohs(p->channelControlStatus_); + p->spotBlankingStatus_ = ntohs(p->spotBlankingStatus_); + p->bypassMapGenerationDate_ = ntohs(p->bypassMapGenerationDate_); + p->bypassMapGenerationTime_ = ntohs(p->bypassMapGenerationTime_); p->clutterFilterMapGenerationDate_ = ntohs(p->clutterFilterMapGenerationDate_); p->clutterFilterMapGenerationTime_ = ntohs(p->clutterFilterMapGenerationTime_); - p->verticalReflectivityCalibrationCorrection_ = - ntohs(p->verticalReflectivityCalibrationCorrection_); + p->verticalReflectivityCalibrationCorrection_ = static_cast( + ntohs(p->verticalReflectivityCalibrationCorrection_)); p->transitionPowerSourceStatus_ = ntohs(p->transitionPowerSourceStatus_); p->rmsControlStatus_ = ntohs(p->rmsControlStatus_); p->performanceCheckStatus_ = ntohs(p->performanceCheckStatus_); SwapArray(p->alarmCodes_); // RDA Build 18.0 increased the size of the message from 80 to 120 bytes - if (header().message_size() * 2 > Level2MessageHeader::SIZE + 80) + if (static_cast(header().message_size()) * 2 > + Level2MessageHeader::SIZE + 80) { is.read(reinterpret_cast(&p->signalProcessingOptions_), 2); // 41 - is.seekg(36, std::ios_base::cur); // 42-59 - is.read(reinterpret_cast(&p->statusVersion_), 2); // 60 + is.seekg(34, std::ios_base::cur); // 42-58 + is.read(reinterpret_cast(&p->downloadedPatternNumber_), 2); // 59 + is.read(reinterpret_cast(&p->statusVersion_), 2); // 60 bytesRead += 40; p->signalProcessingOptions_ = ntohs(p->signalProcessingOptions_); p->statusVersion_ = ntohs(p->statusVersion_); } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + if (!ValidateMessage(is, bytesRead)) { messageValid = false; @@ -342,6 +329,4 @@ RdaStatusData::Create(Level2MessageHeader&& header, std::istream& is) return message; } -} // namespace rda -} // namespace wsr88d -} // namespace scwx +} // namespace scwx::wsr88d::rda diff --git a/wxdata/source/scwx/wsr88d/rda/volume_coverage_pattern_data.cpp b/wxdata/source/scwx/wsr88d/rda/volume_coverage_pattern_data.cpp index d2f3dec2..b2159648 100644 --- a/wxdata/source/scwx/wsr88d/rda/volume_coverage_pattern_data.cpp +++ b/wxdata/source/scwx/wsr88d/rda/volume_coverage_pattern_data.cpp @@ -220,7 +220,19 @@ uint16_t VolumeCoveragePatternData::number_of_base_tilts() const double VolumeCoveragePatternData::elevation_angle(uint16_t e) const { - return p->elevationCuts_[e].elevationAngle_ * ANGLE_DATA_SCALE; + + double elevationAngleConverted = + p->elevationCuts_[e].elevationAngle_ * ANGLE_DATA_SCALE; + // Any elevation above 90 degrees should be interpreted as a + // negative angle + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + if (elevationAngleConverted > 90) + { + elevationAngleConverted -= 360; + } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + + return elevationAngleConverted; } uint16_t VolumeCoveragePatternData::elevation_angle_raw(uint16_t e) const diff --git a/wxdata/source/scwx/wsr88d/rpg/digital_raster_data_array_packet.cpp b/wxdata/source/scwx/wsr88d/rpg/digital_raster_data_array_packet.cpp new file mode 100644 index 00000000..2e0ef662 --- /dev/null +++ b/wxdata/source/scwx/wsr88d/rpg/digital_raster_data_array_packet.cpp @@ -0,0 +1,225 @@ +#include +#include + +#include +#include + +namespace scwx::wsr88d::rpg +{ + +static const std::string logPrefix_ = + "scwx::wsr88d::rpg::digital_raster_data_array_packet"; +static const auto logger_ = util::Logger::Create(logPrefix_); + +class DigitalRasterDataArrayPacket::Impl +{ +public: + struct Row + { + std::uint16_t numberOfBytes_ {0}; + std::vector level_ {}; + }; + + explicit Impl() = default; + ~Impl() = default; + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; + + std::uint16_t packetCode_ {0}; + std::uint16_t iCoordinateStart_ {0}; + std::uint16_t jCoordinateStart_ {0}; + std::uint16_t iScaleFactor_ {0}; + std::uint16_t jScaleFactor_ {0}; + std::uint16_t numberOfCells_ {0}; + std::uint16_t numberOfRows_ {0}; + std::uint16_t numberOfBytesInRow_ {0}; + + // Repeat for each row + std::vector row_ {}; + + std::size_t dataSize_ {0}; +}; + +DigitalRasterDataArrayPacket::DigitalRasterDataArrayPacket() : + p(std::make_unique()) +{ +} +DigitalRasterDataArrayPacket::~DigitalRasterDataArrayPacket() = default; + +DigitalRasterDataArrayPacket::DigitalRasterDataArrayPacket( + DigitalRasterDataArrayPacket&&) noexcept = default; +DigitalRasterDataArrayPacket& DigitalRasterDataArrayPacket::operator=( + DigitalRasterDataArrayPacket&&) noexcept = default; + +std::uint16_t DigitalRasterDataArrayPacket::packet_code() const +{ + return p->packetCode_; +} + +std::uint16_t DigitalRasterDataArrayPacket::i_coordinate_start() const +{ + return p->iCoordinateStart_; +} + +std::uint16_t DigitalRasterDataArrayPacket::j_coordinate_start() const +{ + return p->jCoordinateStart_; +} + +std::uint16_t DigitalRasterDataArrayPacket::i_scale_factor() const +{ + return p->iScaleFactor_; +} + +std::uint16_t DigitalRasterDataArrayPacket::j_scale_factor() const +{ + return p->jScaleFactor_; +} + +std::uint16_t DigitalRasterDataArrayPacket::number_of_cells() const +{ + return p->numberOfCells_; +} + +std::uint16_t DigitalRasterDataArrayPacket::number_of_rows() const +{ + return p->numberOfRows_; +} + +std::uint16_t +DigitalRasterDataArrayPacket::number_of_bytes_in_row(std::uint16_t r) const +{ + return p->row_[r].numberOfBytes_; +} + +const std::vector& +DigitalRasterDataArrayPacket::level(std::uint16_t r) const +{ + return p->row_[r].level_; +} + +size_t DigitalRasterDataArrayPacket::data_size() const +{ + return p->dataSize_; +} + +bool DigitalRasterDataArrayPacket::Parse(std::istream& is) +{ + bool blockValid = true; + std::size_t bytesRead = 0; + + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + + is.read(reinterpret_cast(&p->packetCode_), 2); + is.read(reinterpret_cast(&p->iCoordinateStart_), 2); + is.read(reinterpret_cast(&p->jCoordinateStart_), 2); + is.read(reinterpret_cast(&p->iScaleFactor_), 2); + is.read(reinterpret_cast(&p->jScaleFactor_), 2); + is.read(reinterpret_cast(&p->numberOfCells_), 2); + is.read(reinterpret_cast(&p->numberOfRows_), 2); + bytesRead += 14; + + p->packetCode_ = ntohs(p->packetCode_); + p->iCoordinateStart_ = ntohs(p->iCoordinateStart_); + p->jCoordinateStart_ = ntohs(p->jCoordinateStart_); + p->iScaleFactor_ = ntohs(p->iScaleFactor_); + p->jScaleFactor_ = ntohs(p->jScaleFactor_); + p->numberOfCells_ = ntohs(p->numberOfCells_); + p->numberOfRows_ = ntohs(p->numberOfRows_); + + if (is.eof()) + { + logger_->debug("Reached end of file"); + blockValid = false; + } + else + { + if (p->packetCode_ != 33) + { + logger_->warn("Invalid packet code: {}", p->packetCode_); + blockValid = false; + } + if (p->numberOfCells_ < 1 || p->numberOfCells_ > 1840) + { + logger_->warn("Invalid number of cells: {}", p->numberOfCells_); + blockValid = false; + } + if (p->numberOfRows_ < 1 || p->numberOfRows_ > 464) + { + logger_->warn("Invalid number of rows: {}", p->numberOfRows_); + blockValid = false; + } + } + + if (blockValid) + { + p->row_.resize(p->numberOfRows_); + + for (std::uint16_t r = 0; r < p->numberOfRows_; r++) + { + auto& row = p->row_[r]; + + is.read(reinterpret_cast(&row.numberOfBytes_), 2); + bytesRead += 2; + + row.numberOfBytes_ = ntohs(row.numberOfBytes_); + + if (row.numberOfBytes_ < 1 || row.numberOfBytes_ > 1840) + { + logger_->warn( + "Invalid number of bytes: {} (Row {})", row.numberOfBytes_, r); + blockValid = false; + break; + } + else if (row.numberOfBytes_ < p->numberOfCells_) + { + logger_->warn("Number of bytes < number of cells: {} < {} (Row {})", + row.numberOfBytes_, + p->numberOfCells_, + r); + blockValid = false; + break; + } + + // Read raster bins + const std::size_t dataSize = p->numberOfCells_; + row.level_.resize(dataSize); + is.read(reinterpret_cast(row.level_.data()), + static_cast(dataSize)); + + is.seekg(static_cast(row.numberOfBytes_ - dataSize), + std::ios_base::cur); + bytesRead += row.numberOfBytes_; + } + } + + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + + p->dataSize_ = bytesRead; + + if (!ValidateMessage(is, bytesRead)) + { + blockValid = false; + } + + return blockValid; +} + +std::shared_ptr +DigitalRasterDataArrayPacket::Create(std::istream& is) +{ + std::shared_ptr packet = + std::make_shared(); + + if (!packet->Parse(is)) + { + packet.reset(); + } + + return packet; +} + +} // namespace scwx::wsr88d::rpg diff --git a/wxdata/source/scwx/wsr88d/rpg/level3_message_factory.cpp b/wxdata/source/scwx/wsr88d/rpg/level3_message_factory.cpp index 25675470..0d9d1725 100644 --- a/wxdata/source/scwx/wsr88d/rpg/level3_message_factory.cpp +++ b/wxdata/source/scwx/wsr88d/rpg/level3_message_factory.cpp @@ -9,22 +9,17 @@ #include #include -#include -namespace scwx -{ -namespace wsr88d -{ -namespace rpg +namespace scwx::wsr88d::rpg { static const std::string logPrefix_ = "scwx::wsr88d::rpg::level3_message_factory"; static const auto logger_ = util::Logger::Create(logPrefix_); -typedef std::function(Level3MessageHeader&&, - std::istream&)> - CreateLevel3MessageFunction; +using CreateLevel3MessageFunction = + std::function(Level3MessageHeader&&, + std::istream&)>; static const std::unordered_map // create_ {{2, GeneralStatusMessage::Create}, @@ -119,9 +114,14 @@ static const std::unordered_map // {182, GraphicProductMessage::Create}, {184, GraphicProductMessage::Create}, {186, GraphicProductMessage::Create}, + {189, GraphicProductMessage::Create}, + {190, GraphicProductMessage::Create}, + {191, GraphicProductMessage::Create}, + {192, GraphicProductMessage::Create}, {193, GraphicProductMessage::Create}, {195, GraphicProductMessage::Create}, {196, GraphicProductMessage::Create}, + {197, GraphicProductMessage::Create}, {202, GraphicProductMessage::Create}}; std::shared_ptr Level3MessageFactory::Create(std::istream& is) @@ -149,13 +149,12 @@ std::shared_ptr Level3MessageFactory::Create(std::istream& is) else if (headerValid) { // Seek to the end of the current message - is.seekg(header.length_of_message() - Level3MessageHeader::SIZE, + is.seekg(static_cast(header.length_of_message()) - + static_cast(Level3MessageHeader::SIZE), std::ios_base::cur); } return message; } -} // namespace rpg -} // namespace wsr88d -} // namespace scwx +} // namespace scwx::wsr88d::rpg diff --git a/wxdata/source/scwx/wsr88d/rpg/packet_factory.cpp b/wxdata/source/scwx/wsr88d/rpg/packet_factory.cpp index e3cd034b..5458119c 100644 --- a/wxdata/source/scwx/wsr88d/rpg/packet_factory.cpp +++ b/wxdata/source/scwx/wsr88d/rpg/packet_factory.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -27,18 +28,14 @@ #include -namespace scwx -{ -namespace wsr88d -{ -namespace rpg +namespace scwx::wsr88d::rpg { static const std::string logPrefix_ = "scwx::wsr88d::rpg::packet_factory"; static const auto logger_ = util::Logger::Create(logPrefix_); -typedef std::function(std::istream&)> - CreateMessageFunction; +using CreateMessageFunction = + std::function(std::istream&)>; static const std::unordered_map create_ { {1, TextAndSpecialSymbolPacket::Create}, @@ -69,6 +66,7 @@ static const std::unordered_map create_ { {26, PointGraphicSymbolPacket::Create}, {28, GenericDataPacket::Create}, {29, GenericDataPacket::Create}, + {33, DigitalRasterDataArrayPacket::Create}, {0x0802, SetColorLevelPacket::Create}, {0x0E03, LinkedContourVectorPacket::Create}, {0x3501, UnlinkedContourVectorPacket::Create}, @@ -81,7 +79,7 @@ std::shared_ptr PacketFactory::Create(std::istream& is) std::shared_ptr packet = nullptr; bool packetValid = true; - uint16_t packetCode; + std::uint16_t packetCode {0}; is.read(reinterpret_cast(&packetCode), 2); packetCode = ntohs(packetCode); @@ -108,6 +106,4 @@ std::shared_ptr PacketFactory::Create(std::istream& is) return packet; } -} // namespace rpg -} // namespace wsr88d -} // namespace scwx +} // namespace scwx::wsr88d::rpg diff --git a/wxdata/source/scwx/wsr88d/rpg/product_description_block.cpp b/wxdata/source/scwx/wsr88d/rpg/product_description_block.cpp index 1a0effaa..016128f3 100644 --- a/wxdata/source/scwx/wsr88d/rpg/product_description_block.cpp +++ b/wxdata/source/scwx/wsr88d/rpg/product_description_block.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -21,28 +22,13 @@ static const std::string logPrefix_ = static const auto logger_ = util::Logger::Create(logPrefix_); static const std::set compressedProducts_ = { - 32, 94, 99, 134, 135, 138, 149, 152, 153, 154, 155, - 159, 161, 163, 165, 167, 168, 170, 172, 173, 174, 175, - 176, 177, 178, 179, 180, 182, 186, 193, 195, 202}; + 32, 94, 99, 113, 134, 135, 138, 149, 152, 153, 154, 155, 159, + 161, 163, 165, 167, 168, 170, 172, 173, 174, 175, 176, 177, 178, + 179, 180, 182, 186, 189, 190, 191, 192, 193, 195, 197, 202}; -static const std::set uncodedDataLevelProducts_ = {32, - 34, - 81, - 93, - 94, - 99, - 134, - 135, - 138, - 153, - 154, - 155, - 159, - 161, - 163, - 177, - 193, - 195}; +static const std::set uncodedDataLevelProducts_ = { + 32, 34, 81, 93, 94, 99, 134, 135, 138, 153, 154, 155, + 159, 161, 163, 177, 189, 190, 191, 192, 193, 195, 197}; static const std::unordered_map rangeMap_ { {19, 230}, {20, 460}, {27, 230}, {30, 230}, {31, 230}, {32, 230}, @@ -57,7 +43,8 @@ static const std::unordered_map rangeMap_ { {163, 300}, {165, 300}, {166, 230}, {167, 300}, {168, 300}, {169, 230}, {170, 230}, {171, 230}, {172, 230}, {173, 230}, {174, 230}, {175, 230}, {176, 230}, {177, 230}, {178, 300}, {179, 300}, {180, 89}, {181, 89}, - {182, 89}, {184, 89}, {186, 412}, {193, 460}, {195, 460}, {196, 50}}; + {182, 89}, {184, 89}, {186, 417}, {193, 460}, {195, 460}, {196, 50}, + {197, 230}}; static const std::unordered_map xResolutionMap_ { {19, 1000}, {20, 2000}, {27, 1000}, {30, 1000}, {31, 2000}, {32, 1000}, @@ -71,7 +58,7 @@ static const std::unordered_map xResolutionMap_ { {166, 250}, {167, 250}, {168, 250}, {169, 2000}, {170, 250}, {171, 2000}, {172, 250}, {173, 250}, {174, 250}, {175, 250}, {176, 250}, {177, 250}, {178, 1000}, {179, 1000}, {180, 150}, {181, 150}, {182, 150}, {184, 150}, - {186, 300}, {193, 250}, {195, 1000}}; + {186, 300}, {193, 250}, {195, 1000}, {197, 250}}; static const std::unordered_map yResolutionMap_ {{37, 1000}, {38, 4000}, @@ -86,7 +73,11 @@ static const std::unordered_map yResolutionMap_ {{37, 1000}, {90, 4000}, {97, 1000}, {98, 4000}, - {166, 250}}; + {166, 250}, + {189, 20}, + {190, 20}, + {191, 20}, + {192, 20}}; // GR uses different internal units than defined units in level 3 products static const std::unordered_map grScale_ { @@ -105,73 +96,57 @@ static const std::unordered_map grScale_ { {174, ((units::inches {1} * 0.01f) / units::millimeters {1})}, {175, ((units::inches {1} * 0.01f) / units::millimeters {1})}}; -class ProductDescriptionBlockImpl +class ProductDescriptionBlock::Impl { public: - explicit ProductDescriptionBlockImpl() : - blockDivider_ {0}, - latitudeOfRadar_ {0}, - longitudeOfRadar_ {0}, - heightOfRadar_ {0}, - productCode_ {0}, - operationalMode_ {0}, - volumeCoveragePattern_ {0}, - sequenceNumber_ {0}, - volumeScanNumber_ {0}, - volumeScanDate_ {0}, - volumeScanStartTime_ {0}, - generationDateOfProduct_ {0}, - generationTimeOfProduct_ {0}, - elevationNumber_ {0}, - version_ {0}, - spotBlank_ {0}, - offsetToSymbology_ {0}, - offsetToGraphic_ {0}, - offsetToTabular_ {0}, - parameters_ {0}, - halfwords_ {0} - { - } - ~ProductDescriptionBlockImpl() = default; + explicit Impl() = default; + ~Impl() = default; - uint16_t halfword(size_t i); + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(const Impl&&) = delete; + Impl& operator=(const Impl&&) = delete; - int16_t blockDivider_; - int32_t latitudeOfRadar_; - int32_t longitudeOfRadar_; - int16_t heightOfRadar_; - int16_t productCode_; - uint16_t operationalMode_; - uint16_t volumeCoveragePattern_; - int16_t sequenceNumber_; - uint16_t volumeScanNumber_; - uint16_t volumeScanDate_; - uint32_t volumeScanStartTime_; - uint16_t generationDateOfProduct_; - uint32_t generationTimeOfProduct_; + std::uint16_t halfword(std::size_t i); + + std::int16_t blockDivider_ {0}; + std::int32_t latitudeOfRadar_ {0}; + std::int32_t longitudeOfRadar_ {0}; + std::int16_t heightOfRadar_ {0}; + std::int16_t productCode_ {0}; + std::uint16_t operationalMode_ {0}; + std::uint16_t volumeCoveragePattern_ {0}; + std::int16_t sequenceNumber_ {0}; + std::uint16_t volumeScanNumber_ {0}; + std::uint16_t volumeScanDate_ {0}; + std::uint32_t volumeScanStartTime_ {0}; + std::uint16_t generationDateOfProduct_ {0}; + std::uint32_t generationTimeOfProduct_ {0}; // 27-28: Product dependent parameters 1 and 2 (Table V) - uint16_t elevationNumber_; + std::uint16_t elevationNumber_ {0}; // 30: Product dependent parameter 3 (Table V) // 31-46: Product dependent (Note 1) // 47-53: Product dependent parameters 4-10 (Table V, Note 3) - uint8_t version_; - uint8_t spotBlank_; - uint32_t offsetToSymbology_; - uint32_t offsetToGraphic_; - uint32_t offsetToTabular_; + std::uint8_t version_ {0}; + std::uint8_t spotBlank_ {0}; + std::uint32_t offsetToSymbology_ {0}; + std::uint32_t offsetToGraphic_ {0}; + std::uint32_t offsetToTabular_ {0}; - std::array parameters_; - std::array halfwords_; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + std::array parameters_ {0}; + std::array halfwords_ {0}; + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) }; -uint16_t ProductDescriptionBlockImpl::halfword(size_t i) +std::uint16_t ProductDescriptionBlock::Impl::halfword(std::size_t i) { // Halfwords start at halfword 31 - return halfwords_[i - 31]; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) + return halfwords_.at(i - 31); } -ProductDescriptionBlock::ProductDescriptionBlock() : - p(std::make_unique()) +ProductDescriptionBlock::ProductDescriptionBlock() : p(std::make_unique()) { } ProductDescriptionBlock::~ProductDescriptionBlock() = default; @@ -181,102 +156,104 @@ ProductDescriptionBlock::ProductDescriptionBlock( ProductDescriptionBlock& ProductDescriptionBlock::operator=( ProductDescriptionBlock&&) noexcept = default; -int16_t ProductDescriptionBlock::block_divider() const +std::int16_t ProductDescriptionBlock::block_divider() const { return p->blockDivider_; } float ProductDescriptionBlock::latitude_of_radar() const { - return p->latitudeOfRadar_ * 0.001f; + static constexpr float kScale = 0.001f; + return static_cast(p->latitudeOfRadar_) * kScale; } float ProductDescriptionBlock::longitude_of_radar() const { - return p->longitudeOfRadar_ * 0.001f; + static constexpr float kScale = 0.001f; + return static_cast(p->longitudeOfRadar_) * kScale; } -int16_t ProductDescriptionBlock::height_of_radar() const +std::int16_t ProductDescriptionBlock::height_of_radar() const { return p->heightOfRadar_; } -int16_t ProductDescriptionBlock::product_code() const +std::int16_t ProductDescriptionBlock::product_code() const { return p->productCode_; } -uint16_t ProductDescriptionBlock::operational_mode() const +std::uint16_t ProductDescriptionBlock::operational_mode() const { return p->operationalMode_; } -uint16_t ProductDescriptionBlock::volume_coverage_pattern() const +std::uint16_t ProductDescriptionBlock::volume_coverage_pattern() const { return p->volumeCoveragePattern_; } -int16_t ProductDescriptionBlock::sequence_number() const +std::int16_t ProductDescriptionBlock::sequence_number() const { return p->sequenceNumber_; } -uint16_t ProductDescriptionBlock::volume_scan_number() const +std::uint16_t ProductDescriptionBlock::volume_scan_number() const { return p->volumeScanNumber_; } -uint16_t ProductDescriptionBlock::volume_scan_date() const +std::uint16_t ProductDescriptionBlock::volume_scan_date() const { return p->volumeScanDate_; } -uint32_t ProductDescriptionBlock::volume_scan_start_time() const +std::uint32_t ProductDescriptionBlock::volume_scan_start_time() const { return p->volumeScanStartTime_; } -uint16_t ProductDescriptionBlock::generation_date_of_product() const +std::uint16_t ProductDescriptionBlock::generation_date_of_product() const { return p->generationDateOfProduct_; } -uint32_t ProductDescriptionBlock::generation_time_of_product() const +std::uint32_t ProductDescriptionBlock::generation_time_of_product() const { return p->generationTimeOfProduct_; } -uint16_t ProductDescriptionBlock::elevation_number() const +std::uint16_t ProductDescriptionBlock::elevation_number() const { return p->elevationNumber_; } -uint16_t ProductDescriptionBlock::data_level_threshold(size_t i) const +std::uint16_t ProductDescriptionBlock::data_level_threshold(std::size_t i) const { - return p->halfwords_[i]; + return p->halfwords_.at(i); } -uint8_t ProductDescriptionBlock::version() const +std::uint8_t ProductDescriptionBlock::version() const { return p->version_; } -uint8_t ProductDescriptionBlock::spot_blank() const +std::uint8_t ProductDescriptionBlock::spot_blank() const { return p->spotBlank_; } -uint32_t ProductDescriptionBlock::offset_to_symbology() const +std::uint32_t ProductDescriptionBlock::offset_to_symbology() const { return p->offsetToSymbology_; } -uint32_t ProductDescriptionBlock::offset_to_graphic() const +std::uint32_t ProductDescriptionBlock::offset_to_graphic() const { return p->offsetToGraphic_; } -uint32_t ProductDescriptionBlock::offset_to_tabular() const +std::uint32_t ProductDescriptionBlock::offset_to_tabular() const { return p->offsetToTabular_; } @@ -286,14 +263,14 @@ float ProductDescriptionBlock::range() const return range_raw(); } -uint16_t ProductDescriptionBlock::range_raw() const +std::uint16_t ProductDescriptionBlock::range_raw() const { - uint16_t range = 0; + std::uint16_t range = 0; auto it = rangeMap_.find(p->productCode_); if (it != rangeMap_.cend()) { - range = static_cast(it->second); + range = static_cast(it->second); } return range; @@ -301,17 +278,18 @@ uint16_t ProductDescriptionBlock::range_raw() const float ProductDescriptionBlock::x_resolution() const { - return x_resolution_raw() * 0.001f; + static constexpr float kScale = 0.001f; + return static_cast(x_resolution_raw()) * kScale; } -uint16_t ProductDescriptionBlock::x_resolution_raw() const +std::uint16_t ProductDescriptionBlock::x_resolution_raw() const { - uint16_t xResolution = 0; + std::uint16_t xResolution = 0; auto it = xResolutionMap_.find(p->productCode_); if (it != xResolutionMap_.cend()) { - xResolution = static_cast(it->second); + xResolution = static_cast(it->second); } return xResolution; @@ -319,25 +297,28 @@ uint16_t ProductDescriptionBlock::x_resolution_raw() const float ProductDescriptionBlock::y_resolution() const { - return y_resolution_raw() * 0.001f; + static constexpr float kScale = 0.001f; + return static_cast(y_resolution_raw()) * kScale; } -uint16_t ProductDescriptionBlock::y_resolution_raw() const +std::uint16_t ProductDescriptionBlock::y_resolution_raw() const { - uint16_t yResolution = 0; + std::uint16_t yResolution = 0; auto it = yResolutionMap_.find(p->productCode_); if (it != yResolutionMap_.cend()) { - yResolution = static_cast(it->second); + yResolution = static_cast(it->second); } return yResolution; } -uint16_t ProductDescriptionBlock::threshold() const +std::uint16_t ProductDescriptionBlock::threshold() const { - uint16_t threshold = 1; + std::uint16_t threshold = 1; + + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) switch (p->productCode_) { @@ -394,6 +375,8 @@ uint16_t ProductDescriptionBlock::threshold() const break; } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + return threshold; } @@ -401,6 +384,8 @@ float ProductDescriptionBlock::offset() const { float offset = 0.0f; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + switch (p->productCode_) { case 32: @@ -416,7 +401,8 @@ float ProductDescriptionBlock::offset() const case 186: case 193: case 195: - offset = static_cast(p->halfword(31)) * 0.1f; + offset = + static_cast(static_cast(p->halfword(31))) * 0.1f; break; case 134: @@ -424,7 +410,7 @@ float ProductDescriptionBlock::offset() const break; case 135: - offset = static_cast(p->halfword(33)); + offset = static_cast(p->halfword(33)); break; case 159: @@ -445,6 +431,8 @@ float ProductDescriptionBlock::offset() const break; } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + return offset; } @@ -452,6 +440,8 @@ float ProductDescriptionBlock::scale() const { float scale = 1.0f; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + switch (p->productCode_) { case 32: @@ -466,11 +456,11 @@ float ProductDescriptionBlock::scale() const case 186: case 193: case 195: - scale = p->halfword(32) * 0.1f; + scale = static_cast(p->halfword(32)) * 0.1f; break; case 81: - scale = p->halfword(32) * 0.001f; + scale = static_cast(p->halfword(32)) * 0.001f; break; case 134: @@ -482,7 +472,7 @@ float ProductDescriptionBlock::scale() const break; case 138: - scale = p->halfword(32) * 0.01f; + scale = static_cast(p->halfword(32)) * 0.01f; break; case 159: @@ -503,12 +493,16 @@ float ProductDescriptionBlock::scale() const break; } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + return scale; } -uint16_t ProductDescriptionBlock::number_of_levels() const +std::uint16_t ProductDescriptionBlock::number_of_levels() const { - uint16_t numberOfLevels = 16u; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + + std::uint16_t numberOfLevels = 16u; switch (p->productCode_) { @@ -580,6 +574,10 @@ uint16_t ProductDescriptionBlock::number_of_levels() const break; case 134: + case 189: + case 190: + case 191: + case 192: numberOfLevels = 256; break; @@ -619,6 +617,8 @@ uint16_t ProductDescriptionBlock::number_of_levels() const break; } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + return numberOfLevels; } @@ -626,6 +626,8 @@ std::uint16_t ProductDescriptionBlock::log_start() const { std::uint16_t logStart = std::numeric_limits::max(); + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + switch (p->productCode_) { case 134: @@ -636,6 +638,8 @@ std::uint16_t ProductDescriptionBlock::log_start() const break; } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + return logStart; } @@ -643,6 +647,8 @@ float ProductDescriptionBlock::log_offset() const { float logOffset = 0.0f; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + switch (p->productCode_) { case 134: @@ -653,6 +659,8 @@ float ProductDescriptionBlock::log_offset() const break; } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + return logOffset; } @@ -660,6 +668,8 @@ float ProductDescriptionBlock::log_scale() const { float logScale = 1.0f; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + switch (p->productCode_) { case 134: @@ -670,6 +680,8 @@ float ProductDescriptionBlock::log_scale() const break; } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + return logScale; } @@ -688,6 +700,8 @@ float ProductDescriptionBlock::gr_scale() const std::uint8_t ProductDescriptionBlock::data_mask() const { + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + std::uint8_t dataMask = 0xff; switch (p->productCode_) @@ -700,6 +714,8 @@ std::uint8_t ProductDescriptionBlock::data_mask() const break; } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + return dataMask; } @@ -707,6 +723,8 @@ std::uint8_t ProductDescriptionBlock::topped_mask() const { std::uint8_t toppedMask = 0x00; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + switch (p->productCode_) { case 135: @@ -717,6 +735,8 @@ std::uint8_t ProductDescriptionBlock::topped_mask() const break; } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + return toppedMask; } @@ -724,23 +744,34 @@ units::angle::degrees ProductDescriptionBlock::elevation() const { double elevation = 0.0; - if (p->elevationNumber_ > 0) + if (has_elevation()) { - elevation = p->parameters_[2] * 0.1; + // Elevation is given in tenths of a degree + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) + elevation = static_cast(p->parameters_[2]) * 0.1; } return units::angle::degrees {elevation}; } +bool ProductDescriptionBlock::has_elevation() const +{ + return p->elevationNumber_ > 0; +} + bool ProductDescriptionBlock::IsCompressionEnabled() const { bool isCompressed = false; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + if (compressedProducts_.contains(p->productCode_)) { isCompressed = (p->parameters_[7] == 1u); } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + return isCompressed; } @@ -749,7 +780,7 @@ bool ProductDescriptionBlock::IsDataLevelCoded() const return !uncodedDataLevelProducts_.contains(p->productCode_); } -size_t ProductDescriptionBlock::data_size() const +std::size_t ProductDescriptionBlock::data_size() const { return SIZE; } @@ -760,6 +791,8 @@ bool ProductDescriptionBlock::Parse(std::istream& is) const std::streampos blockStart = is.tellg(); + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + is.read(reinterpret_cast(&p->blockDivider_), 2); // 10 is.read(reinterpret_cast(&p->latitudeOfRadar_), 4); // 11-12 is.read(reinterpret_cast(&p->longitudeOfRadar_), 4); // 13-14 @@ -773,27 +806,31 @@ bool ProductDescriptionBlock::Parse(std::istream& is) is.read(reinterpret_cast(&p->volumeScanStartTime_), 4); // 22-23 is.read(reinterpret_cast(&p->generationDateOfProduct_), 2); // 24 is.read(reinterpret_cast(&p->generationTimeOfProduct_), 4); // 25-26 - is.read(reinterpret_cast(&p->parameters_[0]), 2 * 2); // 27-28 - is.read(reinterpret_cast(&p->elevationNumber_), 2); // 29 - is.read(reinterpret_cast(&p->parameters_[2]), 2); // 30 - is.read(reinterpret_cast(&p->halfwords_[0]), 16 * 2); // 31-46 - is.read(reinterpret_cast(&p->parameters_[3]), 7 * 2); // 47-53 - is.read(reinterpret_cast(&p->version_), 1); // 54 - is.read(reinterpret_cast(&p->spotBlank_), 1); // 54 - is.read(reinterpret_cast(&p->offsetToSymbology_), 4); // 55-56 - is.read(reinterpret_cast(&p->offsetToGraphic_), 4); // 57-58 - is.read(reinterpret_cast(&p->offsetToTabular_), 4); // 59-60 + is.read(reinterpret_cast(&p->parameters_[0]), + static_cast(2 * 2)); // 27-28 + is.read(reinterpret_cast(&p->elevationNumber_), 2); // 29 + is.read(reinterpret_cast(&p->parameters_[2]), 2); // 30 + is.read(reinterpret_cast(&p->halfwords_[0]), + static_cast(16 * 2)); // 31-46 + is.read(reinterpret_cast(&p->parameters_[3]), + static_cast(7 * 2)); // 47-53 + is.read(reinterpret_cast(&p->version_), 1); // 54 + is.read(reinterpret_cast(&p->spotBlank_), 1); // 54 + is.read(reinterpret_cast(&p->offsetToSymbology_), 4); // 55-56 + is.read(reinterpret_cast(&p->offsetToGraphic_), 4); // 57-58 + is.read(reinterpret_cast(&p->offsetToTabular_), 4); // 59-60 - p->blockDivider_ = ntohs(p->blockDivider_); - p->latitudeOfRadar_ = ntohl(p->latitudeOfRadar_); - p->longitudeOfRadar_ = ntohl(p->longitudeOfRadar_); - p->heightOfRadar_ = ntohs(p->heightOfRadar_); - p->productCode_ = ntohs(p->productCode_); - p->operationalMode_ = ntohs(p->operationalMode_); - p->volumeCoveragePattern_ = ntohs(p->volumeCoveragePattern_); - p->sequenceNumber_ = ntohs(p->sequenceNumber_); - p->volumeScanNumber_ = ntohs(p->volumeScanNumber_); - p->volumeScanDate_ = ntohs(p->volumeScanDate_); + p->blockDivider_ = static_cast(ntohs(p->blockDivider_)); + p->latitudeOfRadar_ = static_cast(ntohl(p->latitudeOfRadar_)); + p->longitudeOfRadar_ = + static_cast(ntohl(p->longitudeOfRadar_)); + p->heightOfRadar_ = static_cast(ntohs(p->heightOfRadar_)); + p->productCode_ = static_cast(ntohs(p->productCode_)); + p->operationalMode_ = ntohs(p->operationalMode_); + p->volumeCoveragePattern_ = ntohs(p->volumeCoveragePattern_); + p->sequenceNumber_ = static_cast(ntohs(p->sequenceNumber_)); + p->volumeScanNumber_ = ntohs(p->volumeScanNumber_); + p->volumeScanDate_ = ntohs(p->volumeScanDate_); p->volumeScanStartTime_ = ntohl(p->volumeScanStartTime_); p->generationDateOfProduct_ = ntohs(p->generationDateOfProduct_); p->generationTimeOfProduct_ = ntohl(p->generationTimeOfProduct_); @@ -826,6 +863,8 @@ bool ProductDescriptionBlock::Parse(std::istream& is) } } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + if (blockValid) { logger_->trace("Product code: {}", p->productCode_); @@ -843,6 +882,8 @@ bool ProductDescriptionBlock::Parse(std::istream& is) std::optional ProductDescriptionBlock::data_level_code(std::uint8_t level) const { + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + switch (p->productCode_) { case 32: @@ -857,6 +898,10 @@ ProductDescriptionBlock::data_level_code(std::uint8_t level) const case 163: case 167: case 168: + case 189: + case 190: + case 191: + case 192: case 195: switch (level) { @@ -1028,11 +1073,11 @@ ProductDescriptionBlock::data_level_code(std::uint8_t level) const if (number_of_levels() <= 16 && level < 16 && !uncodedDataLevelProducts_.contains(p->productCode_)) { - uint16_t th = data_level_threshold(level); + const std::uint16_t th = data_level_threshold(level); if ((th & 0x8000u)) { // If bit 0 is one, then the LSB is coded - uint16_t lsb = th & 0x00ffu; + const std::uint16_t lsb = th & 0x00ffu; switch (lsb) { @@ -1076,6 +1121,8 @@ ProductDescriptionBlock::data_level_code(std::uint8_t level) const } } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + return std::nullopt; } @@ -1094,6 +1141,8 @@ ProductDescriptionBlock::data_value(std::uint8_t level) const std::optional f = std::nullopt; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + // Different products use different scale/offset formulas if (numberOfLevels > 16 || uncodedDataLevelProducts_.contains(p->productCode_)) @@ -1111,26 +1160,27 @@ ProductDescriptionBlock::data_value(std::uint8_t level) const case 174: case 175: case 176: - f = (level - dataOffset) / dataScale; + f = (static_cast(level) - dataOffset) / dataScale; break; case 134: if (level < log_start()) { - f = (level - dataOffset) / dataScale; + f = (static_cast(level) - dataOffset) / dataScale; } else { - f = expf((level - log_offset()) / log_scale()); + f = expf((static_cast(level) - log_offset()) / log_scale()); } break; case 135: level = level & data_mask(); - [[fallthrough]]; + f = static_cast(level) / dataScale - dataOffset; + break; default: - f = level * dataScale + dataOffset; + f = static_cast(level) * dataScale + dataOffset; break; } } @@ -1170,6 +1220,8 @@ ProductDescriptionBlock::data_value(std::uint8_t level) const } } + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) + // Scale for GR compatibility if (f.has_value()) { diff --git a/wxdata/source/scwx/wsr88d/rpg/raster_data_packet.cpp b/wxdata/source/scwx/wsr88d/rpg/raster_data_packet.cpp index c0bf5557..03b00220 100644 --- a/wxdata/source/scwx/wsr88d/rpg/raster_data_packet.cpp +++ b/wxdata/source/scwx/wsr88d/rpg/raster_data_packet.cpp @@ -2,6 +2,7 @@ #include #include +#include #include namespace scwx diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index 47ada181..bc489cb3 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -1,14 +1,20 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.24) project(scwx-data) +include(CheckCXXSymbolExists) + find_package(Boost) find_package(cpr) find_package(LibXml2) +find_package(OpenSSL) +find_package(range-v3) find_package(re2) find_package(spdlog) -if (NOT MSVC) +check_cxx_symbol_exists(_LIBCPP_VERSION version LIBCPP) + +if (LINUX) find_package(TBB) endif() @@ -54,27 +60,39 @@ set(HDR_GR include/scwx/gr/color.hpp set(SRC_GR source/scwx/gr/color.cpp source/scwx/gr/placefile.cpp) set(HDR_NETWORK include/scwx/network/cpr.hpp - include/scwx/network/dir_list.hpp) + include/scwx/network/dir_list.hpp + include/scwx/network/ntp_client.hpp) set(SRC_NETWORK source/scwx/network/cpr.cpp - source/scwx/network/dir_list.cpp) + source/scwx/network/dir_list.cpp + source/scwx/network/ntp_client.cpp) set(HDR_PROVIDER include/scwx/provider/aws_level2_data_provider.hpp + include/scwx/provider/aws_level2_chunks_data_provider.hpp include/scwx/provider/aws_level3_data_provider.hpp include/scwx/provider/aws_nexrad_data_provider.hpp + include/scwx/provider/iem_api_provider.hpp + include/scwx/provider/iem_api_provider.ipp include/scwx/provider/nexrad_data_provider.hpp include/scwx/provider/nexrad_data_provider_factory.hpp include/scwx/provider/warnings_provider.hpp) set(SRC_PROVIDER source/scwx/provider/aws_level2_data_provider.cpp + source/scwx/provider/aws_level2_chunks_data_provider.cpp source/scwx/provider/aws_level3_data_provider.cpp source/scwx/provider/aws_nexrad_data_provider.cpp + source/scwx/provider/iem_api_provider.cpp source/scwx/provider/nexrad_data_provider.cpp source/scwx/provider/nexrad_data_provider_factory.cpp source/scwx/provider/warnings_provider.cpp) +set(HDR_TYPES include/scwx/types/iem_types.hpp + include/scwx/types/ntp_types.hpp) +set(SRC_TYPES source/scwx/types/iem_types.cpp + source/scwx/types/ntp_types.cpp) set(HDR_UTIL include/scwx/util/digest.hpp include/scwx/util/enum.hpp include/scwx/util/environment.hpp include/scwx/util/float.hpp include/scwx/util/hash.hpp include/scwx/util/iterator.hpp + include/scwx/util/json.hpp include/scwx/util/logger.hpp include/scwx/util/map.hpp include/scwx/util/rangebuf.hpp @@ -87,6 +105,7 @@ set(SRC_UTIL source/scwx/util/digest.cpp source/scwx/util/environment.cpp source/scwx/util/float.cpp source/scwx/util/hash.cpp + source/scwx/util/json.cpp source/scwx/util/logger.cpp source/scwx/util/rangebuf.cpp source/scwx/util/streams.cpp @@ -114,6 +133,7 @@ set(HDR_WSR88D_RDA include/scwx/wsr88d/rda/clutter_filter_bypass_map.hpp include/scwx/wsr88d/rda/level2_message_header.hpp include/scwx/wsr88d/rda/performance_maintenance_data.hpp include/scwx/wsr88d/rda/rda_adaptation_data.hpp + include/scwx/wsr88d/rda/rda_prf_data.hpp include/scwx/wsr88d/rda/rda_status_data.hpp include/scwx/wsr88d/rda/rda_types.hpp include/scwx/wsr88d/rda/volume_coverage_pattern_data.hpp) @@ -127,6 +147,7 @@ set(SRC_WSR88D_RDA source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp source/scwx/wsr88d/rda/level2_message_header.cpp source/scwx/wsr88d/rda/performance_maintenance_data.cpp source/scwx/wsr88d/rda/rda_adaptation_data.cpp + source/scwx/wsr88d/rda/rda_prf_data.cpp source/scwx/wsr88d/rda/rda_status_data.cpp source/scwx/wsr88d/rda/volume_coverage_pattern_data.cpp) set(HDR_WSR88D_RPG include/scwx/wsr88d/rpg/ccb_header.hpp @@ -134,6 +155,7 @@ set(HDR_WSR88D_RPG include/scwx/wsr88d/rpg/ccb_header.hpp include/scwx/wsr88d/rpg/cell_trend_volume_scan_times.hpp include/scwx/wsr88d/rpg/digital_precipitation_data_array_packet.hpp include/scwx/wsr88d/rpg/digital_radial_data_array_packet.hpp + include/scwx/wsr88d/rpg/digital_raster_data_array_packet.hpp include/scwx/wsr88d/rpg/general_status_message.hpp include/scwx/wsr88d/rpg/generic_data_packet.hpp include/scwx/wsr88d/rpg/generic_radial_data_packet.hpp @@ -175,6 +197,7 @@ set(SRC_WSR88D_RPG source/scwx/wsr88d/rpg/ccb_header.cpp source/scwx/wsr88d/rpg/cell_trend_volume_scan_times.cpp source/scwx/wsr88d/rpg/digital_precipitation_data_array_packet.cpp source/scwx/wsr88d/rpg/digital_radial_data_array_packet.cpp + source/scwx/wsr88d/rpg/digital_raster_data_array_packet.cpp source/scwx/wsr88d/rpg/general_status_message.cpp source/scwx/wsr88d/rpg/generic_data_packet.cpp source/scwx/wsr88d/rpg/generic_radial_data_packet.cpp @@ -223,6 +246,8 @@ add_library(wxdata OBJECT ${HDR_AWIPS} ${SRC_NETWORK} ${HDR_PROVIDER} ${SRC_PROVIDER} + ${HDR_TYPES} + ${SRC_TYPES} ${HDR_UTIL} ${SRC_UTIL} ${HDR_WSR88D} @@ -243,6 +268,8 @@ source_group("Header Files\\network" FILES ${HDR_NETWORK}) source_group("Source Files\\network" FILES ${SRC_NETWORK}) source_group("Header Files\\provider" FILES ${HDR_PROVIDER}) source_group("Source Files\\provider" FILES ${SRC_PROVIDER}) +source_group("Header Files\\types" FILES ${HDR_TYPES}) +source_group("Source Files\\types" FILES ${SRC_TYPES}) source_group("Header Files\\util" FILES ${HDR_UTIL}) source_group("Source Files\\util" FILES ${SRC_UTIL}) source_group("Header Files\\wsr88d" FILES ${HDR_WSR88D}) @@ -252,6 +279,15 @@ source_group("Source Files\\wsr88d\\rda" FILES ${SRC_WSR88D_RDA}) source_group("Header Files\\wsr88d\\rpg" FILES ${HDR_WSR88D_RPG}) source_group("Source Files\\wsr88d\\rpg" FILES ${SRC_WSR88D_RPG}) + +try_compile(CHRONO_HAS_TIMEZONES_AND_CALENDERS + ${CMAKE_BINARY_DIR} + ${PROJECT_SOURCE_DIR}/cpp-feature-tests/chrono_feature_test.cpp + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF) +message("CHRONO_HAS_TIMEZONES_AND_CALENDERS: ${CHRONO_HAS_TIMEZONES_AND_CALENDERS}") + target_include_directories(wxdata PRIVATE ${Boost_INCLUDE_DIR} ${HSLUV_C_INCLUDE_DIR} ${scwx-data_SOURCE_DIR}/include @@ -282,6 +318,8 @@ target_link_libraries(wxdata PUBLIC aws-cpp-sdk-core aws-cpp-sdk-s3 cpr::cpr LibXml2::LibXml2 + OpenSSL::Crypto + range-v3::range-v3 re2::re2 spdlog::spdlog units::units) @@ -293,9 +331,18 @@ if (WIN32) target_link_libraries(wxdata INTERFACE Ws2_32) endif() -if (NOT MSVC) - target_link_libraries(wxdata PUBLIC date::date-tz - TBB::tbb) +if (NOT CHRONO_HAS_TIMEZONES_AND_CALENDERS) + target_link_libraries(wxdata PUBLIC date::date-tz) +endif() + +if (LINUX) + target_link_libraries(wxdata PUBLIC TBB::tbb) +endif() + +if (LIBCPP) + # Enable support for parallel algorithms + target_compile_options(wxdata PUBLIC -fexperimental-library) + target_link_libraries(wxdata INTERFACE c++experimental) endif() set_target_properties(wxdata PROPERTIES CXX_STANDARD 20