diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..efabb98 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,39 @@ +root = true + +# All files +[*] +charset = utf-8 +end_of_line = crlf +insert_final_newline = true +trim_trailing_whitespace = true + +# C++/CLI source and headers +[*.{cpp,h,hpp}] +indent_style = space +indent_size = 4 + +# CMake +[CMakeLists.txt] +indent_style = space +indent_size = 2 + +[*.cmake] +indent_style = space +indent_size = 2 + +# PowerShell +[*.ps1] +indent_style = space +indent_size = 4 + +# JSON / YAML +[*.{json,yml,yaml}] +indent_style = space +indent_size = 2 + +# Dependencies are managed externally — do not reformat +[dependencies/**] +indent_style = unset +indent_size = unset +trim_trailing_whitespace = false +insert_final_newline = false diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml new file mode 100644 index 0000000..bd8780d --- /dev/null +++ b/.github/workflows/bump-version.yml @@ -0,0 +1,221 @@ +name: Create bump version PR + +on: + workflow_dispatch: + inputs: + version: + description: Version bump type. + required: true + type: choice + options: + - patch + - minor + - major + - prepatch + - preminor + - premajor + - prerelease + preid: + description: Prerelease identifier (e.g., 'alpha', 'beta', 'rc'). Only used with pre* version types. + required: false + type: string + default: 'rc' + +permissions: + contents: write + pull-requests: write + +jobs: + bump-version-pr: + name: Bump version PR + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Create bump script + run: | + cat > bump-version.py << 'PYEOF' + import re, sys, os, subprocess, json + + version_type = sys.argv[1] + preid = sys.argv[2] if len(sys.argv) > 2 and sys.argv[2].strip() else 'rc' + cmake_path = 'CMakeLists.txt' + changelog_path = 'CHANGELOG.md' + + # ── Read current version from CMakeLists.txt ───────────────────────────── + cmake = open(cmake_path).read() + m = re.search(r'set\(MAPLIBRE_NET_VERSION "([^"]+)"\)', cmake) + if not m: + raise RuntimeError(f"Could not find MAPLIBRE_NET_VERSION in {cmake_path}") + current = m.group(1).strip() + print(f"Current version: {current}") + + # ── Parse version (supports X.Y.Z and X.Y.Z-pre.N) ────────────────────── + pre_match = re.match(r'^(\d+)\.(\d+)\.(\d+)-(.+?)\.(\d+)$', current) + rel_match = re.match(r'^(\d+)\.(\d+)\.(\d+)$', current) + + if pre_match: + major, minor, patch = int(pre_match.group(1)), int(pre_match.group(2)), int(pre_match.group(3)) + pre_tag, pre_num = pre_match.group(4), int(pre_match.group(5)) + is_pre = True + elif rel_match: + major, minor, patch = int(rel_match.group(1)), int(rel_match.group(2)), int(rel_match.group(3)) + pre_tag, pre_num = None, 0 + is_pre = False + else: + raise RuntimeError(f"Unrecognised version format: {current}") + + # ── Compute new version ────────────────────────────────────────────────── + if version_type == 'patch': + new_version = f"{major}.{minor}.{patch}" if is_pre else f"{major}.{minor}.{patch + 1}" + elif version_type == 'minor': + new_version = f"{major}.{minor}.0" if is_pre else f"{major}.{minor + 1}.0" + elif version_type == 'major': + new_version = f"{major}.0.0" if is_pre else f"{major + 1}.0.0" + elif version_type == 'prepatch': + new_version = f"{major}.{minor}.{patch + 1}-{preid}.1" + elif version_type == 'preminor': + new_version = f"{major}.{minor + 1}.0-{preid}.1" + elif version_type == 'premajor': + new_version = f"{major + 1}.0.0-{preid}.1" + elif version_type == 'prerelease': + if is_pre: + new_version = f"{major}.{minor}.{patch}-{pre_tag}.{pre_num + 1}" + else: + new_version = f"{major}.{minor}.{patch + 1}-{preid}.1" + else: + raise RuntimeError(f"Unknown version type: {version_type}") + + print(f"New version: {new_version}") + + # ── Update CMakeLists.txt ──────────────────────────────────────────────── + cmake = re.sub( + r'(set\(MAPLIBRE_NET_VERSION ")[^"]+(")', + rf'\g<1>{new_version}\g<2>', + cmake) + open(cmake_path, 'w').write(cmake) + print(f"Updated {cmake_path}") + + # ── Update CHANGELOG.md ────────────────────────────────────────────────── + if not os.path.exists(changelog_path): + print("No CHANGELOG.md found, creating one") + open(changelog_path, 'w').write('# Changelog\n\n') + + changelog = open(changelog_path).read() + + # Collect PRs since last tag that aren't already in the changelog + missing_entries = [] + try: + latest_tag = subprocess.check_output( + ['git', 'describe', '--tags', '--abbrev=0'], text=True).strip() + print(f"Latest tag: {latest_tag}") + commit_range = f"{latest_tag}..HEAD" + except subprocess.CalledProcessError: + print("No previous tags found, using all commits") + commit_range = 'HEAD' + + try: + commits = subprocess.check_output( + ['git', 'log', commit_range, '--oneline'], text=True) + except subprocess.CalledProcessError: + commits = '' + + pr_numbers = re.findall(r'#(\d+)', commits) + missing_prs = [n for n in pr_numbers if f'#{n}' not in changelog] + print(f"Found {len(missing_prs)} new PRs to add to changelog") + + if missing_prs: + try: + remote_url = subprocess.check_output( + ['git', 'config', '--get', 'remote.origin.url'], text=True).strip() + repo_match = re.search(r'github\.com[:/](.+?)(?:\.git)?$', remote_url) + repo_full = repo_match.group(1) if repo_match else None + except Exception: + repo_full = None + + for pr_num in missing_prs: + try: + pr_json = subprocess.check_output( + ['gh', 'pr', 'view', pr_num, '--json', 'title,author,number'], text=True) + pr = json.loads(pr_json) + if 'dependabot' in pr['author']['login']: + continue + pr_url = f"https://github.com/{repo_full}/pull/{pr['number']}" if repo_full else f"#{pr['number']}" + entry = f"- {pr['title']} ([#{pr['number']}]({pr_url})) (@{pr['author']['login']})" + missing_entries.append(entry) + print(f"Added: {entry}") + except Exception as e: + print(f"Could not fetch PR #{pr_num}: {e}") + + # Replace "## master" or "## main" with new version heading + changelog = re.sub(r'^## (?:master|main)\s*$', f'## {new_version}', changelog, flags=re.MULTILINE) + changelog = changelog.replace('- _...Add new stuff here..._\n', '') + + # Prepend fresh master section + master_section = '\n'.join([ + '## master', + '### ✨ Features and improvements', + '- _...Add new stuff here..._', + '', + '### 🐞 Bug fixes', + '- _...Add new stuff here..._', + '', + '', + ]) + + title_match = re.match(r'^(# .+?\n\n)', changelog) + if title_match: + title = title_match.group(1) + rest = changelog[len(title):] + else: + title = '# Changelog\n\n' + rest = changelog + + # Insert missing PR entries into the Bug fixes block of the new version section + if missing_entries: + bug_fix_pattern = re.compile( + r'^(## [^\n]+\n### ✨ Features and improvements\n(?:.*\n)*?### 🐞 Bug fixes\n)', + re.MULTILINE) + bf_match = bug_fix_pattern.search(rest) + if bf_match: + insert_at = bf_match.end() + entries_text = '\n' + '\n'.join(missing_entries) + '\n' + rest = rest[:insert_at] + entries_text + rest[insert_at:] + else: + rest = rest + '\n' + '\n'.join(missing_entries) + '\n' + + changelog = title + master_section + rest + open(changelog_path, 'w').write(changelog) + print("Updated CHANGELOG.md") + + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write(f"version={new_version}\n") + PYEOF + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Bump version and update changelog + id: version-details + run: | + python bump-version.py "${{ inputs.version }}" "${{ inputs.preid }}" + rm bump-version.py + env: + GH_TOKEN: ${{ github.token }} + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + commit-message: Release v${{ steps.version-details.outputs.version }} + branch: release-v${{ steps.version-details.outputs.version }} + title: Release v${{ steps.version-details.outputs.version }} + body: | + Automated version bump to `v${{ steps.version-details.outputs.version }}`. + + Review the `CHANGELOG.md` entries before merging. Once merged, trigger the **Build and Release** workflow to publish the GitHub release. + labels: release diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..560758d --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,97 @@ +name: Docs + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build-docs: + name: Build documentation + runs-on: windows-latest + + steps: + - name: Enable Git long paths + run: git config --global core.longpaths true + + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up MSVC developer tools + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + - name: Get vcpkg commit ID + id: vcpkg-id + shell: pwsh + run: | + $id = git -C dependencies/maplibre-native/platform/windows/vendor/vcpkg rev-parse HEAD + Add-Content -Path $env:GITHUB_OUTPUT -Value "commit_id=$id" + + - name: Cache vcpkg binary cache + uses: actions/cache@v4 + with: + path: dependencies/maplibre-native/platform/windows/vendor/vcpkg/archives + key: vcpkg-x64-windows-${{ steps.vcpkg-id.outputs.commit_id }} + + - name: Bootstrap vcpkg + run: | + & "dependencies\maplibre-native\platform\windows\vendor\vcpkg\bootstrap-vcpkg.bat" -disableMetrics + + - name: Configure CMake + shell: pwsh + env: + VCPKG_BINARY_SOURCES: "clear;files,${{ github.workspace }}\\dependencies\\maplibre-native\\platform\\windows\\vendor\\vcpkg\\archives,readwrite" + run: | + $maxAttempts = 3 + for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { + cmake -B build -G "Visual Studio 17 2022" -A x64 ` + -DMLN_WITH_OPENGL=ON ` + -DMLN_WITH_EGL=OFF ` + -DMLN_WITH_VULKAN=OFF ` + "-DCMAKE_CXX_FLAGS=/wd4267" ` + "-DMAPLIBRE_NET_DOTNET_VERSION=net9.0" + if ($LASTEXITCODE -eq 0) { break } + if ($attempt -lt $maxAttempts) { + Remove-Item -Recurse -Force build -ErrorAction SilentlyContinue + } else { + exit $LASTEXITCODE + } + } + + - name: Build (Release) + run: cmake --build build --target mbgl-dotnet --config Release --parallel + + - name: Install docfx + run: dotnet tool install -g docfx + + - name: Build documentation + run: docfx docs/docfx.json + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/_site + + deploy: + name: Deploy to GitHub Pages + needs: build-docs + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3c920ce --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,221 @@ +name: Build and Release + +on: + workflow_dispatch: + push: + branches: + - main + +permissions: + contents: write + +jobs: + release-check: + name: Check if version is already released + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Get version and check GitHub releases + id: check + run: | + VERSION=$(grep -oP 'set\(MAPLIBRE_NET_VERSION "\K[^"]+' CMakeLists.txt | head -1) + + if echo "$VERSION" | grep -qP '\-(alpha|beta|rc)\.\d+'; then + RELEASE_TYPE="prerelease" + else + RELEASE_TYPE="regular" + fi + + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + "https://api.github.com/repos/${{ github.repository }}/releases/tags/v${VERSION}" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}") + + if [ "$HTTP_STATUS" = "200" ]; then + PUBLISHED="true" + else + PUBLISHED="false" + fi + + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "published=$PUBLISHED" >> "$GITHUB_OUTPUT" + echo "release_type=$RELEASE_TYPE" >> "$GITHUB_OUTPUT" + echo "VERSION: $VERSION" + echo "RELEASE_TYPE: $RELEASE_TYPE" + echo "PUBLISHED: $PUBLISHED" + + outputs: + published: ${{ steps.check.outputs.published }} + version: ${{ steps.check.outputs.version }} + release_type: ${{ steps.check.outputs.release_type }} + + build: + name: Build Windows ${{ matrix.arch }} ${{ matrix.dotnet }} + runs-on: ${{ matrix.runs-on }} + needs: release-check + if: ${{ needs.release-check.outputs.published == 'false' }} + environment: release + strategy: + fail-fast: false + matrix: + dotnet: [net6.0, net8.0, net9.0] + arch: [x64, arm64] + include: + - arch: x64 + runs-on: windows-latest + vcpkg-triplet: x64-windows + msvc-arch: x64 + vs-arch: x64 + - arch: arm64 + runs-on: windows-11-arm + vcpkg-triplet: arm64-windows + msvc-arch: arm64 + vs-arch: ARM64 + + steps: + - name: Enable Git long paths + run: git config --global core.longpaths true + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Install .NET 6 SDK (targeting pack for net6.0 builds) + if: matrix.dotnet == 'net6.0' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + + - name: Set up MSVC developer tools + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ matrix.msvc-arch }} + + - name: Get vcpkg commit ID + id: vcpkg-id + shell: pwsh + run: | + $id = git -C dependencies/maplibre-native/platform/windows/vendor/vcpkg rev-parse HEAD + Add-Content -Path $env:GITHUB_OUTPUT -Value "commit_id=$id" + + - name: Cache vcpkg binary cache + uses: actions/cache@v4 + with: + path: dependencies/maplibre-native/platform/windows/vendor/vcpkg/archives + key: vcpkg-${{ matrix.vcpkg-triplet }}-${{ steps.vcpkg-id.outputs.commit_id }} + + - name: Bootstrap vcpkg + run: | + & "dependencies\maplibre-native\platform\windows\vendor\vcpkg\bootstrap-vcpkg.bat" -disableMetrics + + - name: Configure CMake + shell: pwsh + env: + VCPKG_BINARY_SOURCES: "clear;files,${{ github.workspace }}\\dependencies\\maplibre-native\\platform\\windows\\vendor\\vcpkg\\archives,readwrite" + run: | + $maxAttempts = 3 + for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { + Write-Host "CMake configure attempt $attempt of $maxAttempts" + cmake -B build -G "Visual Studio 17 2022" -A ${{ matrix.vs-arch }} ` + -DMLN_WITH_OPENGL=ON ` + -DMLN_WITH_EGL=OFF ` + -DMLN_WITH_VULKAN=OFF ` + "-DCMAKE_CXX_FLAGS=/wd4267" ` + "-DMAPLIBRE_NET_DOTNET_VERSION=${{ matrix.dotnet }}" + if ($LASTEXITCODE -eq 0) { break } + if ($attempt -lt $maxAttempts) { + Write-Host "Attempt $attempt failed (exit $LASTEXITCODE), cleaning and retrying in 30s..." + Remove-Item -Recurse -Force build -ErrorAction SilentlyContinue + } else { + exit $LASTEXITCODE + } + } + + - name: Build + run: cmake --build build --target mbgl-dotnet --config Release --parallel + + - name: Package release artifacts + shell: pwsh + run: | + $version = "${{ needs.release-check.outputs.version }}" + $arch = "${{ matrix.arch }}" + $dotnet = "${{ matrix.dotnet }}" + $outDir = "build\Release" + + New-Item -ItemType Directory -Path publish -Force + + # Copy the managed assembly and its XML docs + Copy-Item "$outDir\MaplibreNative.NET.dll" publish\ + if (Test-Path "$outDir\MaplibreNative.NET.xml") { + Copy-Item "$outDir\MaplibreNative.NET.xml" publish\ + } + + # Copy any additional DLLs (vcpkg runtime dependencies) + Get-ChildItem "$outDir\*.dll" | + Where-Object { $_.Name -ne "MaplibreNative.NET.dll" } | + Copy-Item -Destination publish\ + + Compress-Archive -Path "publish\*" -DestinationPath "MaplibreNative.NET-v${version}-win-${arch}-${dotnet}.zip" + Write-Host "Packaged: MaplibreNative.NET-v${version}-win-${arch}-${dotnet}.zip" + Get-ChildItem publish\ + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: release-artifacts-${{ matrix.arch }}-${{ matrix.dotnet }} + path: MaplibreNative.NET-v${{ needs.release-check.outputs.version }}-win-${{ matrix.arch }}-${{ matrix.dotnet }}.zip + retention-days: 7 + + release: + name: Publish GitHub Release + runs-on: ubuntu-latest + needs: [release-check, build] + if: ${{ needs.release-check.outputs.published == 'false' }} + environment: release + + steps: + - uses: actions/checkout@v4 + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + pattern: release-artifacts-* + merge-multiple: true + path: ./artifacts + + - name: Extract changelog for version + run: | + VERSION="${{ needs.release-check.outputs.version }}" + if [ -f CHANGELOG.md ]; then + awk -v ver="## $VERSION" \ + 'BEGIN{p=0} $0==ver{p=1; next} /^## / && p==1{exit} p==1{print}' \ + CHANGELOG.md > changelog_for_version.md + else + echo "No changelog entry for v${VERSION}." > changelog_for_version.md + fi + echo "--- Release notes ---" + cat changelog_for_version.md + + - name: Determine release flags + id: flags + run: | + if [[ "${{ needs.release-check.outputs.release_type }}" == 'regular' ]]; then + echo "prerelease=false" >> "$GITHUB_OUTPUT" + else + echo "prerelease=true" >> "$GITHUB_OUTPUT" + fi + + - name: Publish GitHub Release + uses: ncipollo/release-action@v1 + with: + tag: v${{ needs.release-check.outputs.version }} + name: v${{ needs.release-check.outputs.version }} + bodyFile: changelog_for_version.md + artifacts: ./artifacts/MaplibreNative.NET-v${{ needs.release-check.outputs.version }}-win-*.zip + prerelease: ${{ steps.flags.outputs.prerelease }} + allowUpdates: true + draft: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml new file mode 100644 index 0000000..6ba1050 --- /dev/null +++ b/.github/workflows/windows-ci.yml @@ -0,0 +1,100 @@ +name: Windows CI + +on: + pull_request: + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build Windows ${{ matrix.arch }} ${{ matrix.dotnet }} + runs-on: ${{ matrix.runs-on }} + strategy: + fail-fast: false + matrix: + dotnet: [net6.0, net8.0, net9.0] + arch: [x64, arm64] + include: + - arch: x64 + runs-on: windows-latest + vcpkg-triplet: x64-windows + msvc-arch: x64 + vs-arch: x64 + - arch: arm64 + runs-on: windows-11-arm + vcpkg-triplet: arm64-windows + msvc-arch: arm64 + vs-arch: ARM64 + + steps: + - name: Enable Git long paths + run: git config --global core.longpaths true + + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install .NET 6 SDK (targeting pack for net6.0 builds) + if: matrix.dotnet == 'net6.0' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + + - name: Set up MSVC developer tools + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ matrix.msvc-arch }} + + - name: Get vcpkg commit ID + id: vcpkg-id + shell: pwsh + run: | + $id = git -C dependencies/maplibre-native/platform/windows/vendor/vcpkg rev-parse HEAD + Add-Content -Path $env:GITHUB_OUTPUT -Value "commit_id=$id" + + - name: Cache vcpkg binary cache + uses: actions/cache@v4 + with: + path: dependencies/maplibre-native/platform/windows/vendor/vcpkg/archives + key: vcpkg-${{ matrix.vcpkg-triplet }}-${{ steps.vcpkg-id.outputs.commit_id }} + + - name: Bootstrap vcpkg + run: | + & "dependencies\maplibre-native\platform\windows\vendor\vcpkg\bootstrap-vcpkg.bat" -disableMetrics + + - name: Configure CMake + shell: pwsh + env: + VCPKG_BINARY_SOURCES: "clear;files,${{ github.workspace }}\\dependencies\\maplibre-native\\platform\\windows\\vendor\\vcpkg\\archives,readwrite" + run: | + $maxAttempts = 3 + for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { + Write-Host "CMake configure attempt $attempt of $maxAttempts" + cmake -B build -G "Visual Studio 17 2022" -A ${{ matrix.vs-arch }} ` + -DMLN_WITH_OPENGL=ON ` + -DMLN_WITH_EGL=OFF ` + -DMLN_WITH_VULKAN=OFF ` + "-DCMAKE_CXX_FLAGS=/wd4267" ` + "-DMAPLIBRE_NET_DOTNET_VERSION=${{ matrix.dotnet }}" + if ($LASTEXITCODE -eq 0) { break } + if ($attempt -lt $maxAttempts) { + Write-Host "Attempt $attempt failed (exit $LASTEXITCODE), cleaning and retrying in 30s..." + Remove-Item -Recurse -Force build -ErrorAction SilentlyContinue + } else { + exit $LASTEXITCODE + } + } + + - name: Build + run: cmake --build build --target mbgl-dotnet --config Release --parallel + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: ci-build-${{ matrix.arch }}-${{ matrix.dotnet }} + path: build\Release\MaplibreNative.NET.dll + retention-days: 3 diff --git a/.gitignore b/.gitignore index 72fac7e..aa0e352 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,9 @@ build/ .vs/ bin/ obj/ -*.user \ No newline at end of file +*.user + +# DocFX generated output +docs/_site/ +docs/api/ +docs/obj/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b412f7f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,57 @@ +# Changelog + +## master +### ✨ Features and improvements +- _...Add new stuff here..._ + +### 🐞 Bug fixes +- _...Add new stuff here..._ + +## 2.1.0 +### ✨ Features and improvements +- Add Layer property read-back, Style.GetSourceAttributions, Renderer.Q… ([#5](https://github.com/acalcutt/MaplibreNative.NET/pull/5)) (@acalcutt) + +## 2.0.0 +### ✨ Features and improvements +- Add full Layer and Source type wrapping (CircleLayer, FillLayer, LineLayer, SymbolLayer, RasterLayer, HillshadeLayer, HeatmapLayer, BackgroundLayer, FillExtrusionLayer, LocationIndicatorLayer, ColorReliefLayer) +- Add Light, StyleImage, TransitionOptions, and Style image management +- Add full SymbolLayer layout and paint properties; missing Fill, Line, and Circle properties; Layer filter support; `AddLayer` with `beforeLayerId` +- Add remaining layer properties and `ColorReliefLayer.ColorRamp` +- Add `LocationIndicatorLayer`, `GeoJSONOptions`, Source `prefetch`/`overscale` +- Add `LineLayer.Gradient`, `HeatmapLayer.Color`, `ImageSource.SetImage` +- Add `MaplibreNative.NET.WPF`: WPF HwndHost adapter with location indicator, follow-location, and bearing controls +- Add Windows CI workflow for pull requests and pushes to main +- Add DocFX documentation site with auto-deploy to GitHub Pages +- Add `.editorconfig` and `format.ps1`; normalize source formatting + +### 🐞 Bug fixes +- Fix multiple C++/CLI compilation errors (`NativePointerHolder` C3137, generic lambdas C3923, `JSDocument` namespace, `IconPadding`, `HillshadeLayer` types, `marshal_as`) +- Fix CI long-path checkout failure on Windows runners + +## 1.0.3 +### ✨ Features and improvements +- Support multiple .NET target versions via `-DMAPLIBRE_NET_DOTNET_VERSION` CMake option; update examples to net8.0 + +### 🐞 Bug fixes +- Fix `MaplibreNative.NET.WPF` csproj DLL reference path (removed VistumblerCS-specific path) +- Fix release workflow to correctly publish both x64 and arm64 artifacts +- Fix CI build matrix so `dotnet × arch` combinations cross-multiply correctly + +## 1.0.2 +### ✨ Features and improvements +- Add GeoJSON source/layer API to `Style`: `AddGeoJsonSource`, `SetGeoJsonSourceUrl`, `SetGeoJsonSourceData`, `HasSource`, `RemoveSource`, `AddCircleLayer`, `HasLayer`, `RemoveLayer` +- Add `MaplibreNative.NET.WPF` C# class library (`MaplibreMapHost`, `DelegateMapObserver`) with reflection-based GeoJSON layer helpers and double-click zoom + +## 1.0.1 +### ✨ Features and improvements +- Add `MapLibreLogger` to forward internal `mbgl::Log` messages to C# + +### 🐞 Bug fixes +- Fix indentation in `CMakeLists.txt` source list + +## 1.0.0 +### ✨ Features and improvements +- Initial release: C++/CLI .NET 8 wrapper for maplibre-native on Windows +- Add `Map`, `MapObserver`, `Style`, `HeadlessFrontend`, `MapSnapshotter`, `FileSource`, `FileSourceManager`, `Resource`, `Response`, `LatLng`, `BoundOptions`, `MapOptions`, `AnimationOptions`, `CameraOptions`, `Image`, `PremultipliedImage`, `AlphaImage`, `BackendScope`, `RunLoop`, `AsyncRequest`, `Vector` bindings +- Switch build system to Visual Studio 17 2022 generator with `/clr:netcore`; fix `VS_GLOBAL_TargetFramework` to resolve NETSDK1013 + diff --git a/CMakeLists.txt b/CMakeLists.txt index e435290..5112da6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,10 @@ cmake_minimum_required(VERSION 3.10 FATAL_ERROR) set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/dependencies/maplibre-native/platform/windows/custom-toolchain.cmake) +set(MAPLIBRE_NET_VERSION "2.1.0") project("MaplibreNative.NET" LANGUAGES CXX C) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) if(NOT MSVC) return() @@ -12,43 +13,27 @@ endif() add_library(mbgl-dotnet SHARED) +set(MAPLIBRE_NET_DOTNET_VERSION "net8.0" CACHE STRING "Target .NET version for the C++/CLI wrapper (e.g. net6.0, net8.0, net9.0)") + set(ASSEMBLY_NAME "MaplibreNative.NET") set_target_properties( mbgl-dotnet PROPERTIES - COMMON_LANGUAGE_RUNTIME "" - VS_GLOBAL_CLRSupport "true" + COMMON_LANGUAGE_RUNTIME "NetCore" + VS_GLOBAL_CLRSupport "NetCore" + VS_GLOBAL_TargetFramework "${MAPLIBRE_NET_DOTNET_VERSION}" OUTPUT_NAME ${ASSEMBLY_NAME} ) target_compile_options(mbgl-dotnet PRIVATE "/doc" "/GT") -if(CMAKE_GENERATOR STREQUAL "Ninja") - set(_TARGET_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}") - - set_target_properties( - mbgl-dotnet - PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY ${_TARGET_DIRECTORY} - LIBRARY_OUTPUT_DIRECTORY ${_TARGET_DIRECTORY} - RUNTIME_OUTPUT_DIRECTORY ${_TARGET_DIRECTORY} - ) - - string(REPLACE "/RTC1" "" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) - string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) - target_compile_options(mbgl-dotnet PRIVATE "/clr" "/EHa") - - add_custom_command( - TARGET mbgl-dotnet - POST_BUILD - COMMAND xdcmake "..\\*.xdc" /nologo /assembly:"${ASSEMBLY_NAME}" /out:"${ASSEMBLY_NAME}.xml" - WORKING_DIRECTORY ${_TARGET_DIRECTORY} - VERBATIM - ) - - unset(_TARGET_DIRECTORY) -endif() +add_custom_command( + TARGET mbgl-dotnet + POST_BUILD + COMMAND xdcmake "$\\..\\*.xdc" /nologo /assembly:"${ASSEMBLY_NAME}" /out:"$\\${ASSEMBLY_NAME}.xml" + VERBATIM +) target_compile_definitions( mbgl-dotnet @@ -88,6 +73,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/src/LatLng.cpp ${PROJECT_SOURCE_DIR}/src/Map.cpp ${PROJECT_SOURCE_DIR}/src/MapObserver.cpp + ${PROJECT_SOURCE_DIR}/src/MapLibreLogger.cpp ${PROJECT_SOURCE_DIR}/src/MapOptions.cpp ${PROJECT_SOURCE_DIR}/src/OverscaledTileID.cpp ${PROJECT_SOURCE_DIR}/src/PremultipliedImage.cpp @@ -107,6 +93,11 @@ target_sources( ${PROJECT_SOURCE_DIR}/src/ShaderRegistry.cpp ${PROJECT_SOURCE_DIR}/src/Size.cpp ${PROJECT_SOURCE_DIR}/src/Style.cpp + ${PROJECT_SOURCE_DIR}/src/Layer.cpp + ${PROJECT_SOURCE_DIR}/src/Layers.cpp + ${PROJECT_SOURCE_DIR}/src/Light.cpp + ${PROJECT_SOURCE_DIR}/src/Source.cpp + ${PROJECT_SOURCE_DIR}/src/Sources.cpp ${PROJECT_SOURCE_DIR}/src/TileCoordinate.cpp ${PROJECT_SOURCE_DIR}/src/TransformState.cpp ${PROJECT_SOURCE_DIR}/src/UnassociatedImage.cpp diff --git a/Examples/Console/Example.Console/Example.Console.csproj b/Examples/Console/Example.Console/Example.Console.csproj index 3f27b1a..1550b8e 100644 --- a/Examples/Console/Example.Console/Example.Console.csproj +++ b/Examples/Console/Example.Console/Example.Console.csproj @@ -2,7 +2,7 @@ Exe - net7.0-windows + net8.0-windows enable enable true diff --git a/Examples/Console/Example.Console/Program.cs b/Examples/Console/Example.Console/Program.cs index ab5c104..cfe3b93 100644 --- a/Examples/Console/Example.Console/Program.cs +++ b/Examples/Console/Example.Console/Program.cs @@ -18,6 +18,12 @@ static void Main(string[] args) using var map = new Map(frontend, new MapObserver(), new MapOptions().WithSize(size).WithMapMode(MapMode.Static)); map.Style.LoadURL("https://raw.githubusercontent.com/maplibre/demotiles/gh-pages/style.json"); + // ---------------------------------------------------------------- + // Demonstrate the programmatic style API — add a GeoJSON source + // with a circle layer on top of the base style. + // ---------------------------------------------------------------- + DemoProgrammaticStyleApi(map.Style); + byte[]? imageData = null; map.RenderStill(new CameraOptions().WithZoom(0), MapDebugOptions.NoDebug, ex => @@ -54,5 +60,51 @@ static void Main(string[] args) } } } + + /// + /// Demonstrates the programmatic layer and source API: + /// - adds an inline GeoJSON source with a few cities + /// - adds a circle layer that renders those points + /// - inspects existing layers and prints a summary + /// + static void DemoProgrammaticStyleApi(Style style) + { + // Add an inline GeoJSON source containing a handful of cities. + var citiesSource = style.AddGeoJsonSource("demo-cities"); + citiesSource.SetData(""" + { + "type": "FeatureCollection", + "features": [ + { "type": "Feature", "geometry": { "type": "Point", "coordinates": [-87.63, 41.85] }, "properties": { "name": "Chicago" } }, + { "type": "Feature", "geometry": { "type": "Point", "coordinates": [-73.97, 40.66] }, "properties": { "name": "New York" } }, + { "type": "Feature", "geometry": { "type": "Point", "coordinates": [-118.25, 34.05] }, "properties": { "name": "Los Angeles" } }, + { "type": "Feature", "geometry": { "type": "Point", "coordinates": [-0.13, 51.50] }, "properties": { "name": "London" } }, + { "type": "Feature", "geometry": { "type": "Point", "coordinates": [2.35, 48.85] }, "properties": { "name": "Paris" } }, + { "type": "Feature", "geometry": { "type": "Point", "coordinates": [139.69, 35.69] }, "properties": { "name": "Tokyo" } } + ] + } + """); + + // Add a circle layer that renders the city points. + var circleLayer = style.AddCircleLayer("demo-city-circles", "demo-cities"); + circleLayer.Color = "#ff4444"; + circleLayer.Radius = 10f; + circleLayer.Opacity = 0.9f; + circleLayer.StrokeColor = "#ffffff"; + circleLayer.StrokeWidth = 2f; + + // Print a summary of all layers in paint order. + System.Console.WriteLine("--- Layers in style ---"); + foreach (var layer in style.GetLayers()) + { + System.Console.WriteLine($" [{layer.Type,-18}] {layer.Id}"); + } + + // Show that we can retrieve a typed layer back. + if (style.GetLayer("demo-city-circles") is CircleLayer cl) + { + System.Console.WriteLine($"\ndemo-city-circles: color={cl.Color} radius={cl.Radius} visible={cl.Visible}"); + } + } } } \ No newline at end of file diff --git a/Examples/WPF/Example.WPF/Example.WPF.csproj b/Examples/WPF/Example.WPF/Example.WPF.csproj index 1f1c84a..96f8f68 100644 --- a/Examples/WPF/Example.WPF/Example.WPF.csproj +++ b/Examples/WPF/Example.WPF/Example.WPF.csproj @@ -2,7 +2,7 @@ WinExe - net7.0-windows + net8.0-windows enable true diff --git a/MaplibreNative.NET.WPF/DelegateMapObserver.cs b/MaplibreNative.NET.WPF/DelegateMapObserver.cs new file mode 100644 index 0000000..b39590a --- /dev/null +++ b/MaplibreNative.NET.WPF/DelegateMapObserver.cs @@ -0,0 +1,39 @@ +using System; +using MaplibreNative; + +namespace MaplibreNative.WPF; + +/// +/// Simple MapObserver that forwards events to a delegate, useful for logging. +/// An optional callback is invoked whenever +/// the style finishes loading (including after style URL changes). +/// +public class DelegateMapObserver : MapObserver +{ + private readonly Action _log; + private readonly Action? _onStyleLoaded; + + public DelegateMapObserver(Action log, Action? onStyleLoaded = null) + { + _log = log; + _onStyleLoaded = onStyleLoaded; + } + + protected override void onDidFailLoadingMap(MapLoadError type, string description) + => _log("Fail", $"[{type}] {description}"); + + protected override void onDidFinishLoadingMap() + => _log("Finish", "Map loaded"); + + protected override void onWillStartLoadingMap() + => _log("Will", "WillStartLoading"); + + protected override void onDidFinishLoadingStyle() + { + _log("Style", "DidFinishLoadingStyle"); + _onStyleLoaded?.Invoke(); + } + + protected override void onDidFinishRenderingFrame(RenderFrameStatus status) + => _log("Frame", $"mode={status.mode} needsRepaint={status.needsRepaint}"); +} diff --git a/MaplibreNative.NET.WPF/MaplibreMapHost.cs b/MaplibreNative.NET.WPF/MaplibreMapHost.cs new file mode 100644 index 0000000..67268b1 --- /dev/null +++ b/MaplibreNative.NET.WPF/MaplibreMapHost.cs @@ -0,0 +1,953 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Effects; +using System.Windows.Threading; +using MaplibreNative; + +namespace MaplibreNative.WPF; + +/// +/// WPF HwndHost that embeds a MapLibre Native OpenGL map. +/// Handles its own OpenGL context, RunLoop, pan/zoom input, and an +/// optional navigation control overlay (zoom-in, zoom-out, reset-north). +/// +public class MaplibreMapHost : HwndHost +{ + private static readonly string LogPath = + System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "map_log.txt"); + private static void Log(string s) + { + try { System.IO.File.AppendAllText(LogPath, $"[{DateTime.Now:HH:mm:ss.fff}] {s}\n"); } catch { } + } + + // ── Public properties ───────────────────────────────────────────────────── + + public string StyleUrl + { + get => (string)GetValue(StyleUrlProperty); + set => SetValue(StyleUrlProperty, value); + } + public static readonly DependencyProperty StyleUrlProperty = + DependencyProperty.Register(nameof(StyleUrl), typeof(string), typeof(MaplibreMapHost), + new PropertyMetadata("https://demotiles.maplibre.org/style.json", OnStyleUrlChanged)); + + private static void OnStyleUrlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is MaplibreMapHost h && h._map != null && e.NewValue is string url) + h._map.Style.LoadURL(url); + } + + /// + /// Show or hide the built-in navigation control (zoom in/out + north reset). + /// Default is true. + /// + public bool ShowNavigationControls + { + get => (bool)GetValue(ShowNavigationControlsProperty); + set => SetValue(ShowNavigationControlsProperty, value); + } + public static readonly DependencyProperty ShowNavigationControlsProperty = + DependencyProperty.Register(nameof(ShowNavigationControls), typeof(bool), typeof(MaplibreMapHost), + new PropertyMetadata(true, OnShowNavigationControlsChanged)); + + private static void OnShowNavigationControlsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var h = (MaplibreMapHost)d; + if (h._navPopup != null) + h._navPopup.IsOpen = (bool)e.NewValue && h.IsVisible; + } + + // ── Public camera helpers ───────────────────────────────────────────────── + + public void CenterOn(double latitude, double longitude, double zoom = 14.0) + { + if (_map == null) return; + var cam = new CameraOptions(); + cam.Center = new LatLng(latitude, longitude); + cam.Zoom = zoom; + _map.JumpTo(cam); + _renderNeedsUpdate = true; + } + + public void ZoomIn() + { + if (_map == null) return; + _map.ScaleBy(2.0, new System.Nullable()); + _renderNeedsUpdate = true; + } + + public void ZoomOut() + { + if (_map == null) return; + _map.ScaleBy(0.5, new System.Nullable()); + _renderNeedsUpdate = true; + } + + public void ResetNorth() + { + if (_map == null) return; + var cam = new CameraOptions(); + cam.Bearing = 0.0; + _map.JumpTo(cam); + _renderNeedsUpdate = true; + } + + /// Pan to a location without changing the current zoom level. + private void PanTo(double lat, double lon) + { + if (_map == null) return; + var cam = new CameraOptions(); + cam.Center = new LatLng(lat, lon); + _map.JumpTo(cam); + _renderNeedsUpdate = true; + } + + // ── GeoJSON layer management ────────────────────────────────────────────── + // These methods call APIs added to Style in the C++/CLI wrapper. They use + // reflection so the WPF library keeps compiling against older DLL builds + // that may not have those methods yet (pre-release DLL). + + private static System.Reflection.MethodInfo? _miAddGeoJsonSource; + private static System.Reflection.MethodInfo? _miSetGeoJsonSourceUrl; + private static System.Reflection.MethodInfo? _miSetGeoJsonSourceData; + private static System.Reflection.MethodInfo? _miHasSource; + private static System.Reflection.MethodInfo? _miRemoveSource; + private static System.Reflection.MethodInfo? _miAddCircleLayer; + private static System.Reflection.MethodInfo? _miHasLayer; + private static System.Reflection.MethodInfo? _miRemoveLayer; + private static bool _styleApiResolved; + + private static void EnsureStyleApiResolved(object styleObj) + { + if (_styleApiResolved) return; + _styleApiResolved = true; + var t = styleObj.GetType(); + _miAddGeoJsonSource = t.GetMethod("AddGeoJsonSource", [typeof(string), typeof(string)]); + _miSetGeoJsonSourceUrl = t.GetMethod("SetGeoJsonSourceUrl", [typeof(string), typeof(string)]); + _miSetGeoJsonSourceData= t.GetMethod("SetGeoJsonSourceData",[typeof(string), typeof(string)]); + _miHasSource = t.GetMethod("HasSource", [typeof(string)]); + _miRemoveSource = t.GetMethod("RemoveSource", [typeof(string)]); + _miAddCircleLayer = t.GetMethod("AddCircleLayer", [typeof(string), typeof(string), typeof(string), typeof(float), typeof(float), typeof(string)]); + _miHasLayer = t.GetMethod("HasLayer", [typeof(string)]); + _miRemoveLayer = t.GetMethod("RemoveLayer", [typeof(string)]); + } + + private static bool StyleApiAvailable => _miAddGeoJsonSource != null; + + /// + /// Add or update a GeoJSON source that fetches from a URL, with circle + /// layers for open (green), WEP (orange) and secure (red) access points. + /// Requires a DLL build that includes the GeoJSON source/layer APIs. + /// + public void SetWifiGeoJsonLayer(string sourceId, string geoJsonUrl) + { + if (_map == null) return; + var style = _map.Style; + EnsureStyleApiResolved(style); + if (!StyleApiAvailable) { Log("SetWifiGeoJsonLayer: Style API not available in this DLL build"); return; } + + bool hasSource = (bool)_miHasSource!.Invoke(style, [sourceId])!; + if (!hasSource) + { + _miAddGeoJsonSource!.Invoke(style, [sourceId, geoJsonUrl]); + _AddWifiCircleLayers(style, sourceId); + } + else + { + _miSetGeoJsonSourceUrl!.Invoke(style, [sourceId, geoJsonUrl]); + } + _renderNeedsUpdate = true; + } + + /// + /// Update an existing GeoJSON source with inline GeoJSON string data. + /// Use this for live/frequently-updated data to avoid HTTP round-trips. + /// If the source does not exist yet, it is created (with circle layers). + /// Requires a DLL build that includes the GeoJSON source/layer APIs. + /// + public void SetWifiGeoJsonLayerData(string sourceId, string geoJson) + { + if (_map == null) return; + var style = _map.Style; + EnsureStyleApiResolved(style); + if (!StyleApiAvailable) { Log("SetWifiGeoJsonLayerData: Style API not available in this DLL build"); return; } + + bool hasSource = (bool)_miHasSource!.Invoke(style, [sourceId])!; + if (!hasSource) + { + _miAddGeoJsonSource!.Invoke(style, [sourceId, ""]); + _AddWifiCircleLayers(style, sourceId); + } + _miSetGeoJsonSourceData!.Invoke(style, [sourceId, geoJson]); + _renderNeedsUpdate = true; + } + + // ── Location indicator API ──────────────────────────────────────────────── + + /// + /// Show (or update) the user-location "blue dot" indicator at the given position. + /// Creates the LocationIndicatorLayer automatically after the style is ready. + /// Safe to call before the style is loaded — the position is stored and applied + /// once onDidFinishLoadingStyle fires. + /// + public void UpdateLocationIndicator(double lat, double lon, float bearing, float accuracyMeters) + { + if (_map == null) return; + var style = _map.Style; + + // Lazily resolve the Style.AddLocationIndicatorLayer method + if (_miAddLocInd == null) + _miAddLocInd = style.GetType().GetMethod("AddLocationIndicatorLayer", [typeof(string)]); + if (_miAddLocInd == null) + { + Log("UpdateLocationIndicator: API not available in this DLL build"); + return; + } + + // Remember latest position so it can be replayed after a style reload + bool isFirstFix = !_pendingLocInd.HasValue; + _pendingLocInd = new LocIndParams(lat, lon, bearing, Math.Max(5f, accuracyMeters)); + + // Follow mode: zoom to 14 on the first fix, then just pan (preserves user zoom) + if (FollowLocation) + { + if (isFirstFix) CenterOn(lat, lon); + else PanTo(lat, lon); + } + + if (!_styleReady) return; // OnMapStyleLoaded will call us again + + EnsureStyleApiResolved(style); + + if (_locIndObj == null) + { + // Remove a stale layer in case style was reloaded without us noticing + if (_miHasLayer != null && (bool)_miHasLayer.Invoke(style, [LocIndLayerId])!) + _miRemoveLayer?.Invoke(style, [LocIndLayerId]); + + _locIndObj = _miAddLocInd.Invoke(style, [LocIndLayerId]); + if (_locIndObj == null) + { + Log("UpdateLocationIndicator: AddLocationIndicatorLayer returned null"); + return; + } + + // Resolve property infos from the concrete type (done once across all instances) + if (_piLocLoc == null) + { + var t = _locIndObj.GetType(); + _piLocLoc = t.GetProperty("Location"); + _piLocBearing = t.GetProperty("Bearing"); + _piLocAccuracy = t.GetProperty("AccuracyRadius"); + _piLocAccuracyColor = t.GetProperty("AccuracyRadiusColor"); + _piLocAccuracyBorderColor = t.GetProperty("AccuracyRadiusBorderColor"); + } + + // Apply fixed style: semi-transparent blue fill + solid blue border + _piLocAccuracyColor?.SetValue(_locIndObj, "rgba(30,136,229,0.3)"); + _piLocAccuracyBorderColor?.SetValue(_locIndObj, "rgba(30,136,229,0.85)"); + } + + _piLocLoc?.SetValue(_locIndObj, new double[] { lat, lon, 0.0 }); + _piLocBearing?.SetValue(_locIndObj, ShowBearing ? bearing : 0f); + _piLocAccuracy?.SetValue(_locIndObj, _pendingLocInd.Value.AccuracyM); + _renderNeedsUpdate = true; + } + + /// Remove the location indicator layer from the map. + public void ClearLocationIndicator() + { + _pendingLocInd = null; + _locIndObj = null; + if (_map == null || !_styleReady) return; + var style = _map.Style; + EnsureStyleApiResolved(style); + if (_miHasLayer != null && (bool)_miHasLayer.Invoke(style, [LocIndLayerId])!) + _miRemoveLayer?.Invoke(style, [LocIndLayerId]); + _renderNeedsUpdate = true; + } + + // Called by DelegateMapObserver on the observer thread — dispatch to UI thread + private void OnMapStyleLoaded() + { + Dispatcher.BeginInvoke(() => + { + _styleReady = true; + _locIndObj = null; // invalidated by style reload + if (_pendingLocInd.HasValue) + { + var p = _pendingLocInd.Value; + UpdateLocationIndicator(p.Lat, p.Lon, p.Bearing, p.AccuracyM); + } + }); + } + + /// Remove a GeoJSON wifi layer set (3 security-type circle layers + source). + public void RemoveWifiGeoJsonLayer(string sourceId) + { + if (_map == null) return; + var style = _map.Style; + EnsureStyleApiResolved(style); + if (!StyleApiAvailable) return; + + _miRemoveLayer!.Invoke(style, [sourceId + "_open"]); + _miRemoveLayer!.Invoke(style, [sourceId + "_wep"]); + _miRemoveLayer!.Invoke(style, [sourceId + "_secure"]); + _miRemoveSource!.Invoke(style, [sourceId]); + _renderNeedsUpdate = true; + } + + private static void _AddWifiCircleLayers(object style, string sourceId) + { + void AddIfMissing(string lid, string color) + { + bool has = (bool)_miHasLayer!.Invoke(style, [lid])!; + if (!has) _miAddCircleLayer!.Invoke(style, [lid, sourceId, color, 5f, 0.85f, null]); + } + AddIfMissing(sourceId + "_open", "#00802b"); // green + AddIfMissing(sourceId + "_wep", "#cc7a00"); // orange + AddIfMissing(sourceId + "_secure", "#b30000"); // red + } + + // ── Win32 / WGL P/Invoke ────────────────────────────────────────────────── + + [DllImport("user32.dll")] private static extern IntPtr CreateWindowEx( + uint dwExStyle, string lpClassName, string lpWindowName, uint dwStyle, + int x, int y, int nWidth, int nHeight, + IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam); + + [DllImport("user32.dll")] private static extern bool DestroyWindow(IntPtr hWnd); + [DllImport("user32.dll")] private static extern bool SetWindowPos( + IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags); + + [DllImport("user32.dll")] private static extern IntPtr GetDC(IntPtr hWnd); + [DllImport("user32.dll")] private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); + + [DllImport("opengl32.dll")] private static extern IntPtr wglCreateContext(IntPtr hDC); + [DllImport("opengl32.dll")] private static extern bool wglDeleteContext(IntPtr hGLRC); + [DllImport("opengl32.dll")] private static extern bool wglMakeCurrent(IntPtr hDC, IntPtr hGLRC); + [DllImport("opengl32.dll")] private static extern IntPtr wglGetProcAddress(string procName); + [DllImport("opengl32.dll")] private static extern void glViewport(int x, int y, int width, int height); + [DllImport("opengl32.dll")] private static extern uint glGetError(); + [DllImport("opengl32.dll")] private static extern void glClearColor(float r, float g, float b, float a); + [DllImport("opengl32.dll")] private static extern void glClear(uint mask); + + private delegate void glBindFramebufferDelegate(uint target, uint framebuffer); + private glBindFramebufferDelegate? glBindFramebuffer; + + private const uint GL_COLOR_BUFFER_BIT = 0x00004000; + private const uint GL_DEPTH_BUFFER_BIT = 0x00000100; + private const uint GL_STENCIL_BUFFER_BIT = 0x00000400; + private const uint GL_FRAMEBUFFER = 0x8D40; + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate IntPtr WglCreateContextAttribsARBDelegate(IntPtr hDC, IntPtr hShareContext, int[] attribList); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + private struct WNDCLASSEXA + { + public uint cbSize, style; + public IntPtr lpfnWndProc; + public int cbClsExtra, cbWndExtra; + public IntPtr hInstance, hIcon, hCursor, hbrBackground; + [MarshalAs(UnmanagedType.LPStr)] public string? lpszMenuName; + [MarshalAs(UnmanagedType.LPStr)] public string lpszClassName; + public IntPtr hIconSm; + } + + [DllImport("user32.dll", CharSet = CharSet.Ansi)] private static extern ushort RegisterClassExA(ref WNDCLASSEXA wc); + [DllImport("user32.dll")] private static extern IntPtr DefWindowProcA(IntPtr hWnd, uint msg, IntPtr w, IntPtr l); + [DllImport("kernel32.dll")] private static extern IntPtr GetModuleHandleW(IntPtr lpModuleName); + + private const int WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; + private const int WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; + private const int WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; + private const uint CS_OWNDC = 0x0020; + private const string MapLibreWindowClass = "MapLibreGLChild"; + + private static WndProcDelegate? _wndProcKeepAlive; + private static bool _classRegistered; + + private static void EnsureWindowClassRegistered() + { + if (_classRegistered) return; + _wndProcKeepAlive = (hWnd, msg, w, l) => DefWindowProcA(hWnd, msg, w, l); + var wc = new WNDCLASSEXA + { + cbSize = (uint)Marshal.SizeOf(), + style = CS_OWNDC, + lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcKeepAlive), + hInstance = GetModuleHandleW(IntPtr.Zero), + lpszClassName = MapLibreWindowClass, + }; + RegisterClassExA(ref wc); + _classRegistered = true; + } + + [DllImport("gdi32.dll")] private static extern int ChoosePixelFormat(IntPtr hdc, ref PIXELFORMATDESCRIPTOR ppfd); + [DllImport("gdi32.dll")] private static extern bool SetPixelFormat(IntPtr hdc, int iPixelFormat, ref PIXELFORMATDESCRIPTOR ppfd); + [DllImport("gdi32.dll")] private static extern bool SwapBuffers(IntPtr hdc); + + [StructLayout(LayoutKind.Sequential)] + private struct POINT { public int X, Y; } + [DllImport("user32.dll")] private static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint); + [DllImport("user32.dll")] private static extern IntPtr SetCapture(IntPtr hWnd); + [DllImport("user32.dll")] private static extern bool ReleaseCapture(); + + [StructLayout(LayoutKind.Sequential)] + private struct PIXELFORMATDESCRIPTOR + { + public ushort nSize, nVersion; + public uint dwFlags; + public byte iPixelType, cColorBits, cRedBits, cRedShift, cGreenBits, cGreenShift, cBlueBits, cBlueShift; + public byte cAlphaBits, cAlphaShift, cAccumBits, cAccumRedBits, cAccumGreenBits, cAccumBlueBits, cAccumAlphaBits; + public byte cDepthBits, cStencilBits, cAuxBuffers, iLayerType, bReserved; + public uint dwLayerMask, dwVisibleMask, dwDamageMask; + } + + private const uint PFD_DRAW_TO_WINDOW = 0x00000004; + private const uint PFD_SUPPORT_OPENGL = 0x00000020; + private const uint PFD_DOUBLEBUFFER = 0x00000001; + private const uint WS_CHILD = 0x40000000; + private const uint WS_VISIBLE = 0x10000000; + private const uint WS_CLIPCHILDREN = 0x02000000; + private const uint WS_CLIPSIBLINGS = 0x04000000; + + // ── State ───────────────────────────────────────────────────────────────── + + private IntPtr _childHwnd = IntPtr.Zero; + private IntPtr _hDC = IntPtr.Zero; + private IntPtr _hGLRC = IntPtr.Zero; + + private ExternalRenderingContextFrontend? _frontend; + private Map? _map; + private RunLoop? _runLoop; + private DispatcherTimer? _renderTimer; + private bool _glReady; + private bool _initialized; + private bool _renderNeedsUpdate = true; + private float _dpi = 1.0f; + private int _renderTickCount; + + // ── Location indicator state ────────────────────────────────────────────── + + /// When true, each GPS fix also re-centres the map (zoom-preserving after the first fix). + public bool FollowLocation { get; set; } = true; + + /// When false, the bearing arrow is suppressed — the indicator always points north. + public bool ShowBearing { get; set; } = true; + + private const string LocIndLayerId = "vistumbler_location"; + private object? _locIndObj; // LocationIndicatorLayer instance (null until style loads) + private bool _styleReady; // true after first/each onDidFinishLoadingStyle + private record struct LocIndParams(double Lat, double Lon, float Bearing, float AccuracyM); + private LocIndParams? _pendingLocInd; + + // Reflection cache — resolved from the returned LocationIndicatorLayer type on first use + private static System.Reflection.MethodInfo? _miAddLocInd; + private static System.Reflection.PropertyInfo? _piLocLoc; + private static System.Reflection.PropertyInfo? _piLocBearing; + private static System.Reflection.PropertyInfo? _piLocAccuracy; + private static System.Reflection.PropertyInfo? _piLocAccuracyColor; + private static System.Reflection.PropertyInfo? _piLocAccuracyBorderColor; + + // ── Input state ─────────────────────────────────────────────────────────── + + private bool _isDragging; + private short _lastMouseX; + private short _lastMouseY; + + // ── Navigation popup ────────────────────────────────────────────────────── + + private Popup? _navPopup; + private RotateTransform? _compassRotate; + + // ── HwndHost overrides ──────────────────────────────────────────────────── + + protected override HandleRef BuildWindowCore(HandleRef hwndParent) + { + EnsureWindowClassRegistered(); + int initW = Math.Max(1, (int)ActualWidth); + int initH = Math.Max(1, (int)ActualHeight); + _childHwnd = CreateWindowEx( + 0, MapLibreWindowClass, "", + WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + 0, 0, initW, initH, + hwndParent.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + + IsVisibleChanged += OnIsVisibleChanged; + Dispatcher.BeginInvoke(DispatcherPriority.Loaded, (Action)TryInitialize); + + return new HandleRef(this, _childHwnd); + } + + private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) + { + bool visible = (bool)e.NewValue; + if (visible) + { + _renderNeedsUpdate = true; // ensure first frame renders even if size didn't change + Dispatcher.BeginInvoke(DispatcherPriority.Loaded, (Action)TryInitialize); + } + else + _renderTimer?.Stop(); + + if (_navPopup != null) + _navPopup.IsOpen = visible && ShowNavigationControls; + } + + private void TryInitialize() + { + if (_initialized) { _renderTimer?.Start(); return; } + if (!IsVisible || ActualWidth < 2 || ActualHeight < 2 || _childHwnd == IntPtr.Zero) return; + + _dpi = GetDpiScale(); + int physW = Math.Max(1, (int)(ActualWidth * _dpi)); + int physH = Math.Max(1, (int)(ActualHeight * _dpi)); + SetWindowPos(_childHwnd, IntPtr.Zero, 0, 0, physW, physH, 0x0040); + + _initialized = true; + try { InitOpenGl(); Log("InitOpenGl OK"); } catch (Exception ex) { Log($"InitOpenGl EX: {ex}"); throw; } + try { InitMaplibre(); Log("InitMaplibre OK"); } catch (Exception ex) { Log($"InitMaplibre EX: {ex}"); throw; } + try { InitNavPopup(); } catch (Exception ex) { Log($"InitNavPopup EX: {ex}"); } + + // Hide nav popup when the host window loses focus so it doesn't float + // over unrelated windows (WPF Popup creates its own top-level HWND). + var parentWin = System.Windows.Window.GetWindow(this); + if (parentWin != null) + { + parentWin.Deactivated += (_, _) => { if (_navPopup != null) _navPopup.IsOpen = false; }; + parentWin.Activated += (_, _) => { if (_navPopup != null) _navPopup.IsOpen = ShowNavigationControls && IsVisible; }; + } + + _renderTimer?.Start(); + } + + protected override void DestroyWindowCore(HandleRef hwnd) + { + IsVisibleChanged -= OnIsVisibleChanged; + _renderTimer?.Stop(); + _renderTimer = null; + + if (_navPopup != null) { _navPopup.IsOpen = false; _navPopup = null; } + + _styleReady = false; + _locIndObj = null; + _map?.Dispose(); _map = null; + _frontend?.Dispose(); _frontend = null; + _runLoop?.Dispose(); _runLoop = null; + + if (_hGLRC != IntPtr.Zero) { wglMakeCurrent(IntPtr.Zero, IntPtr.Zero); wglDeleteContext(_hGLRC); _hGLRC = IntPtr.Zero; } + if (_hDC != IntPtr.Zero) { ReleaseDC(_childHwnd, _hDC); _hDC = IntPtr.Zero; } + if (_childHwnd != IntPtr.Zero) { DestroyWindow(_childHwnd); _childHwnd = IntPtr.Zero; } + } + + protected override void OnRenderSizeChanged(SizeChangedInfo info) + { + base.OnRenderSizeChanged(info); + + // Guard: WPF collapses the element to 0×0 when Visibility=Collapsed. + // Passing 0-size to MapLibre native crashes the OpenGL renderer. + if (info.NewSize.Width < 1 || info.NewSize.Height < 1) return; + + float dpi = GetDpiScale(); + int wP = Math.Max(1, (int)(info.NewSize.Width * dpi)); + int hP = Math.Max(1, (int)(info.NewSize.Height * dpi)); + + if (_childHwnd != IntPtr.Zero) + SetWindowPos(_childHwnd, IntPtr.Zero, 0, 0, wP, hP, 0x0040); + + if (_frontend != null) + { + _map?.SetSize(new MaplibreNative.Size((uint)info.NewSize.Width, (uint)info.NewSize.Height)); + _frontend.Backend.Size = new MaplibreNative.Size((uint)wP, (uint)hP); + } + _renderNeedsUpdate = true; + + PositionNavPopup(); + + if (!_initialized && IsVisible) + Dispatcher.BeginInvoke(DispatcherPriority.Loaded, (Action)TryInitialize); + } + + // ── Win32 mouse input → MapLibre camera ─────────────────────────────────── + + private const int WM_LBUTTONDOWN = 0x0201; + private const int WM_LBUTTONUP = 0x0202; + private const int WM_LBUTTONDBLCLK = 0x0203; + private const int WM_MOUSEMOVE = 0x0200; + private const int WM_MOUSEWHEEL = 0x020A; + + protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + if (hwnd == _childHwnd && _map != null) + { + long lp = lParam.ToInt64(); + short cx = (short)(lp & 0xFFFF); + short cy = (short)((lp >> 16) & 0xFFFF); + + switch (msg) + { + case WM_LBUTTONDOWN: + _isDragging = true; + _lastMouseX = cx; + _lastMouseY = cy; + SetCapture(hwnd); + handled = true; + break; + + case WM_LBUTTONDBLCLK: + { + // Cancel any drag initiated by the preceding WM_LBUTTONDOWN, + // then zoom in 1 level centred on the cursor. + _isDragging = false; + ReleaseCapture(); + double anchorX = cx / _dpi; + double anchorY = cy / _dpi; + _map.ScaleBy(2.0, new System.Nullable(new PointDouble(anchorX, anchorY))); + _renderNeedsUpdate = true; + handled = true; + break; + } + + case WM_MOUSEMOVE: + if (_isDragging) + { + double dx = (cx - _lastMouseX) / _dpi; + double dy = (cy - _lastMouseY) / _dpi; + _map.MoveBy(new PointDouble(dx, dy)); + _lastMouseX = cx; + _lastMouseY = cy; + _renderNeedsUpdate = true; + handled = true; + } + break; + + case WM_LBUTTONUP: + _isDragging = false; + ReleaseCapture(); + handled = true; + break; + + case WM_MOUSEWHEEL: + { + int wheelDelta = (short)((wParam.ToInt64() >> 16) & 0xFFFF); + var pt = new POINT { X = (short)(lp & 0xFFFF), Y = (short)((lp >> 16) & 0xFFFF) }; + ScreenToClient(hwnd, ref pt); + double anchorX = pt.X / _dpi; + double anchorY = pt.Y / _dpi; + double scale = Math.Pow(1.15, wheelDelta / 120.0); + _map.ScaleBy(scale, new System.Nullable(new PointDouble(anchorX, anchorY))); + _renderNeedsUpdate = true; + handled = true; + break; + } + } + } + return base.WndProc(hwnd, msg, wParam, lParam, ref handled); + } + + // ── Navigation popup ────────────────────────────────────────────────────── + + private void InitNavPopup() + { + // Outer border groups all three buttons with a shared drop-shadow. + var outerBorder = new Border + { + CornerRadius = new CornerRadius(4), + Effect = new DropShadowEffect + { + BlurRadius = 6, + ShadowDepth = 2, + Opacity = 0.25, + Color = Colors.Black, + Direction = 270, + }, + }; + + var panel = new StackPanel { Width = 29 }; + outerBorder.Child = panel; + + // Zoom-in (+) + var zoomInBtn = MakeNavButton("+", ZoomIn); + SetButtonCorners(zoomInBtn, topLeft: 4, topRight: 4, bottomRight: 0, bottomLeft: 0); + panel.Children.Add(zoomInBtn); + + // Divider + panel.Children.Add(new Border + { + Height = 1, + Background = new SolidColorBrush(Color.FromRgb(218, 218, 218)), + }); + + // Zoom-out (−) + var zoomOutBtn = MakeNavButton("\u2212", ZoomOut); + SetButtonCorners(zoomOutBtn, topLeft: 0, topRight: 0, bottomRight: 0, bottomLeft: 0); + panel.Children.Add(zoomOutBtn); + + // Divider + panel.Children.Add(new Border + { + Height = 1, + Background = new SolidColorBrush(Color.FromRgb(218, 218, 218)), + }); + + // Compass / reset-north (↑, rotates with map bearing) + _compassRotate = new RotateTransform { CenterX = 0.5, CenterY = 0.5 }; + var compassIcon = new TextBlock + { + Text = "\u2191", // ↑ + FontSize = 16, + FontWeight = FontWeights.Bold, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + IsHitTestVisible = false, + RenderTransformOrigin = new Point(0.5, 0.5), + RenderTransform = _compassRotate, + }; + var compassBtn = MakeNavButton(null, ResetNorth); + SetButtonCorners(compassBtn, topLeft: 0, topRight: 0, bottomRight: 4, bottomLeft: 4); + compassBtn.Child = compassIcon; + panel.Children.Add(compassBtn); + + _navPopup = new Popup + { + AllowsTransparency = true, + StaysOpen = true, + IsHitTestVisible = true, + PlacementTarget = this, + Placement = PlacementMode.Relative, + Child = outerBorder, + }; + + PositionNavPopup(); + _navPopup.IsOpen = ShowNavigationControls && IsVisible; + } + + /// Creates a 29×29 white nav button with hover effect. + private static Border MakeNavButton(string? text, Action onClick) + { + var btn = new Border + { + Width = 29, + Height = 29, + Background = Brushes.White, + Cursor = Cursors.Hand, + }; + + if (text != null) + { + btn.Child = new TextBlock + { + Text = text, + FontSize = 20, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + IsHitTestVisible = false, + }; + } + + btn.MouseLeftButtonDown += (_, e) => { onClick(); e.Handled = true; }; + btn.MouseEnter += (_, _) => btn.Background = new SolidColorBrush(Color.FromRgb(240, 240, 240)); + btn.MouseLeave += (_, _) => btn.Background = Brushes.White; + return btn; + } + + private static void SetButtonCorners(Border btn, double topLeft, double topRight, double bottomRight, double bottomLeft) + => btn.CornerRadius = new CornerRadius(topLeft, topRight, bottomRight, bottomLeft); + + private void PositionNavPopup() + { + if (_navPopup == null) return; + const double margin = 10; + const double panelWidth = 29; + // Place in the top-right corner (mirrors maplibre-gl-js default). + _navPopup.HorizontalOffset = ActualWidth - panelWidth - margin; + _navPopup.VerticalOffset = margin; + } + + private void UpdateCompassBearing() + { + if (_compassRotate == null || _map == null) return; + double bearing = _map.GetCameraOptions()?.Bearing ?? 0.0; + // Rotate the arrow counter-clockwise to indicate bearing offset from north. + _compassRotate.Angle = -bearing; + } + + // ── Helpers ─────────────────────────────────────────────────────────────── + + private float GetDpiScale() + { + var src = PresentationSource.FromVisual(this); + var m = src?.CompositionTarget?.TransformToDevice.M11; + return (float)(m ?? 1.0); + } + + // ── OpenGL init ─────────────────────────────────────────────────────────── + + private void InitOpenGl() + { + if (_glReady) return; + _hDC = GetDC(_childHwnd); + Log($"InitOpenGl: hDC={_hDC}"); + + var pfd = new PIXELFORMATDESCRIPTOR + { + nSize = (ushort)Marshal.SizeOf(), + nVersion = 1, + dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + iPixelType = 0, + cColorBits = 24, + cDepthBits = 24, + cStencilBits = 8, + }; + + int fmt = ChoosePixelFormat(_hDC, ref pfd); + SetPixelFormat(_hDC, fmt, ref pfd); + Log($"InitOpenGl: format={fmt}"); + + IntPtr tempCtx = wglCreateContext(_hDC); + wglMakeCurrent(_hDC, tempCtx); + + IntPtr pAttribs = wglGetProcAddress("wglCreateContextAttribsARB"); + if (pAttribs != IntPtr.Zero) + { + var createAttribs = Marshal.GetDelegateForFunctionPointer(pAttribs); + int[] attribs = + [ + WGL_CONTEXT_MAJOR_VERSION_ARB, 3, + WGL_CONTEXT_MINOR_VERSION_ARB, 3, + WGL_CONTEXT_PROFILE_MASK_ARB, 0x00000002, // Compatibility Profile + 0, + ]; + _hGLRC = createAttribs(_hDC, IntPtr.Zero, attribs); + wglMakeCurrent(IntPtr.Zero, IntPtr.Zero); + wglDeleteContext(tempCtx); + wglMakeCurrent(_hDC, _hGLRC); + } + else + { + _hGLRC = tempCtx; + } + + IntPtr pBindFb = wglGetProcAddress("glBindFramebuffer"); + if (pBindFb != IntPtr.Zero) + glBindFramebuffer = Marshal.GetDelegateForFunctionPointer(pBindFb); + + _glReady = true; + Log($"InitOpenGl done: hGLRC={_hGLRC}"); + } + + // ── MapLibre init ───────────────────────────────────────────────────────── + + private void InitMaplibre() + { + float dpi = _dpi; + int wL = Math.Max(1, (int)ActualWidth); + int hL = Math.Max(1, (int)ActualHeight); + int wP = Math.Max(1, (int)(ActualWidth * dpi)); + int hP = Math.Max(1, (int)(ActualHeight * dpi)); + + var sizeLogical = new MaplibreNative.Size((uint)wL, (uint)hL); + var sizePhysical = new MaplibreNative.Size((uint)wP, (uint)hP); + + // MapLibre uses libuv for async I/O. A RunLoop on this thread is required + // before creating the Map; RunOnce() is called every timer tick to pump it. + _runLoop = new RunLoop(RunLoop.Type.New); + + _frontend = new ExternalRenderingContextFrontend(_hDC, _hGLRC, sizePhysical, dpi); + _frontend.Updated += _ => { _renderNeedsUpdate = true; }; + Log($"InitMaplibre: logical={wL}x{hL} physical={wP}x{hP} dpi={dpi}"); + + var mapOptions = new MapOptions() + .WithMapMode(MapMode.Continuous) + .WithSize(sizeLogical) + .WithPixelRatio(dpi); + + var resOptions = new ResourceOptions() + .WithCachePath(System.IO.Path.Combine(System.IO.Path.GetTempPath(), "maplibre_cache.db")) + .WithAssetPath(AppDomain.CurrentDomain.BaseDirectory); + + var obs = new MaplibreNative.WPF.DelegateMapObserver( + (type, msg) => Log($"[MapObserver:{type}] {msg}"), + onStyleLoaded: OnMapStyleLoaded); + + _map = new Map(_frontend, obs, mapOptions, resOptions); + Log("Map created"); + + _map.Style.LoadURL(StyleUrl); + Log($"Style.LoadURL: {StyleUrl}"); + + var initCam = new CameraOptions(); + initCam.Center = new LatLng(20.0, 0.0); + initCam.Zoom = 2.0; + _map.JumpTo(initCam); + + _renderTimer = new DispatcherTimer(DispatcherPriority.Render) + { + Interval = TimeSpan.FromMilliseconds(16) + }; + _renderTimer.Tick += OnRenderTick; + } + + private void OnRenderTick(object? sender, EventArgs e) + { + // Sync child window size if it has drifted (e.g. after DPI change). + if (_frontend != null && _map != null && ActualWidth > 1 && ActualHeight > 1) + { + float curDpi = GetDpiScale(); + var sizePhys = new MaplibreNative.Size( + (uint)(ActualWidth * curDpi), (uint)(ActualHeight * curDpi)); + var backendSize = _frontend.Backend.Size; + if (backendSize.Width != sizePhys.Width || backendSize.Height != sizePhys.Height) + { + _map.SetSize(new MaplibreNative.Size((uint)ActualWidth, (uint)ActualHeight)); + _frontend.Backend.Size = sizePhys; + if (_childHwnd != IntPtr.Zero) + SetWindowPos(_childHwnd, IntPtr.Zero, 0, 0, + (int)sizePhys.Width, (int)sizePhys.Height, 0x0040); + _renderNeedsUpdate = true; + } + } + + // Pump libuv so HTTP responses for style/tiles are delivered. + _runLoop?.RunOnce(); + + if (_renderNeedsUpdate && _glReady && _frontend != null) + { + _renderNeedsUpdate = false; + wglMakeCurrent(_hDC, _hGLRC); + + var sz = _frontend.Backend.Size; + glBindFramebuffer?.Invoke(GL_FRAMEBUFFER, 0); + glViewport(0, 0, (int)Math.Max(1, sz.Width), (int)Math.Max(1, sz.Height)); + glClearColor(0f, 0f, 0f, 1f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + try { _frontend.Render(); } catch (Exception ex) { Log($"Render EX: {ex}"); } + + SwapBuffers(_hDC); + + _renderTickCount++; + if (_renderTickCount <= 5 || _renderTickCount % 120 == 0) + Log($"Render #{_renderTickCount}"); + + // Update compass arrow to reflect current bearing. + UpdateCompassBearing(); + } + } +} diff --git a/MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj b/MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj new file mode 100644 index 0000000..4800f6b --- /dev/null +++ b/MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj @@ -0,0 +1,28 @@ + + + + net8.0-windows10.0.19041.0 + enable + true + x64 + + + + + + <_MaplibreNativeDll Condition="Exists('..\build\Release\MaplibreNative.NET.dll')">..\build\Release\MaplibreNative.NET.dll + <_MaplibreNativeDll Condition="'$(_MaplibreNativeDll)' == ''">$(MSBuildThisFileDirectory)..\build\Release\MaplibreNative.NET.dll + + + + + + $(_MaplibreNativeDll) + + + + diff --git a/README.md b/README.md index b532400..d334bc1 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ # MaplibreNative.NET -This is a .NET wrapper to the [maplibre-native](https://github.com/maplibre/maplibre-native). It is meant to be used in Windows and works with .NET Framework and .NET Core. +This is a .NET wrapper to the [maplibre-native](https://github.com/maplibre/maplibre-native). It is meant to be used on Windows and requires **.NET 6.0 or newer** (uses C++/CLI `/clr:netcore`). ## Using in your project Just add a reference to the `MaplibreNative.NET.dll` (see below how to build from sources), and you can immediately use the classes in the `MaplibreNative` namespace. -This is a minimal example using the `HeadlessFrontend` renderer, in a project with .NET 7.0 (Windows) and WPF enabled: +This is a minimal example using the `HeadlessFrontend` renderer, in a project with .NET 6.0+ (Windows) and WPF enabled: ```cs using MaplibreNative; @@ -68,75 +68,298 @@ using (var map = new Map(frontend, new MapObserver(), new MapOptions().WithSize( ``` You can check other examples at the [Examples](https://github.com/tdcosta100/MaplibreNative.NET/tree/main/Examples) directory. -**Attention:** the examples have a reference to `MaplibreNative.NET.dll` located at `build\Release` subdirectory. So you have to build MaplibreNative.NET with `Release` configuration before building the examples. +**Attention:** the examples reference `MaplibreNative.NET.dll` at `build\Release`. Build MaplibreNative.NET with `Release` configuration before building the examples (see *Building the sources* below). -## Building the sources +## WPF map control (`MaplibreNative.NET.WPF`) -### Prerequisites +The `MaplibreNative.NET.WPF/` folder is an optional C# helper library that makes it easy to embed a live, interactive MapLibre map inside a **WPF window**. -As this is a .NET build, `Microsoft Visual Studio` is required. This project was developed using a `Microsoft Visual Studio 2022` environment, but it should work with `Microsoft Visual Studio 2019` as well. +### Why it's a separate project -To install the required Visual Studio components, open Visual Studio Installer and check `Desktop Development with C++` option. Make sure `C++ CMake tools for Windows` is selected in the right pane. If `git` is not already installed, select `Git for Windows` option in `Individual Components`. When Visual Studio finishes the install process, everything is ready to start. +`MaplibreNative.NET.dll` is a C++/CLI assembly compiled for `x64` only. A WPF project that references it directly must also be `x64` and have `true`. Isolating those constraints in their own `.csproj` keeps the consuming app free of those settings and makes the control reusable across multiple WPF apps. -### Downloading sources +### What's in it -Open `x64 Native Tools Command Prompt for VS 2022` and then clone the repository: +| File | Purpose | +|---|---| +| `MaplibreMapHost.cs` | WPF `HwndHost` that creates a native Win32 child window, initialises a MapLibre OpenGL context inside it, and drives the render loop via a `DispatcherTimer`. Exposes a public API for camera control, GeoJSON layers, and the location indicator. | +| `DelegateMapObserver.cs` | Thin `MapObserver` subclass that forwards observer events to `Action` callbacks so you don't need to subclass `MapObserver` yourself. | -```cmd -git clone --recurse-submodules -j8 https://github.com/tdcosta100/MaplibreNative.NET.git -cd MaplibreNative.NET +### Key `MaplibreMapHost` API + +| Member | Description | +|---|---| +| `StyleUrl` | Dependency property — set the map style URL from XAML or code-behind. | +| `ShowNavigationControls` | Show/hide the built-in zoom-in, zoom-out, and reset-north overlay buttons. | +| `FollowLocation` | When `true`, each GPS fix re-centres the map (zoom-14 on first fix, zoom-preserving thereafter). | +| `ShowBearing` | When `true`, the location indicator arrow follows the GPS track heading; when `false` it always points north. | +| `CenterOn(lat, lon, zoom)` | Jump to a position at a given zoom level. | +| `ZoomIn()` / `ZoomOut()` | Zoom by one level. | +| `ResetNorth()` | Snap bearing back to 0°. | +| `SetWifiGeoJsonLayerData(sourceId, geoJson)` | Push or update a GeoJSON source of wifi AP circles (three security-type circle sub-layers). | +| `SetWifiGeoJsonLayer(sourceId, url)` | Fetch and display a GeoJSON layer from a URL. | +| `RemoveWifiGeoJsonLayer(sourceId)` | Remove the source and all associated layers. | +| `UpdateLocationIndicator(lat, lon, bearing, accuracyMeters)` | Show or move the blue "user location" dot. Deferred safely until the style is ready. | +| `ClearLocationIndicator()` | Remove the location dot. | + +All style-related API calls (`AddLocationIndicatorLayer`, etc.) are resolved at runtime via reflection so that the WPF library continues to compile against older DLL builds that may not yet have those methods. + +### XAML usage + +```xml + + + + ``` -### Configuring +### Referencing the project -Configure the build with the following command: +The `.csproj` looks for `MaplibreNative.NET.dll` at `../build/Release/MaplibreNative.NET.dll` (the local CMake build output). If you vendor a pre-built DLL, override `_MaplibreNativeDll` in a `Directory.Build.props` in your repo: -```cmd -cmake . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release +```xml + + <_MaplibreNativeDll>$(MSBuildThisFileDirectory)..\lib\x64\MaplibreNative.NET.dll + ``` -It will take some time to build and install all components on which the project depends. +## Programmatic style API + +After loading a style, you can add or modify sources and layers at runtime using the typed style API. + +### Adding sources + +```cs +// GeoJSON source from a URL +var geojsonSource = map.Style.AddGeoJsonSourceFromUrl("my-geojson", "https://example.com/data.geojson"); + +// GeoJSON source with inline data +var inlineSource = map.Style.AddGeoJsonSource("my-inline"); +inlineSource.SetData(""" + { + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "geometry": { "type": "Point", "coordinates": [0, 0] } + }] + } + """); + +// Vector tile source +var vectorSource = map.Style.AddVectorSource("my-vector", "https://example.com/tiles.json"); -### Alternative configure commands +// Raster tile source (512 px tiles) +var rasterSource = map.Style.AddRasterSource("my-raster", "https://example.com/raster.json", 512); -To configure build with EGL support (ANGLE libraries will be build), use the following command: +// Raster-DEM elevation source +var demSource = map.Style.AddRasterDemSource("my-dem", "https://example.com/dem.json", 512); -```cmd -cmake . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DMLN_WITH_EGL=ON +// Image source (four geographic corners: TL, TR, BR, BL) +var imageSource = map.Style.AddImageSource("my-image", "https://example.com/overlay.png", +[ + new LatLng(46.0, -76.0), // top-left + new LatLng(46.0, -72.0), // top-right + new LatLng(44.0, -72.0), // bottom-right + new LatLng(44.0, -76.0), // bottom-left +]); ``` -To configure build with OSMesa (software rendering), use the following command: +### Adding layers +Each `AddXxxLayer` call returns a typed wrapper whose properties you can set immediately: + +```cs +// Fill layer (polygon fill) +var fillLayer = map.Style.AddFillLayer("my-fill", "my-vector"); +fillLayer.SourceLayer = "water"; +fillLayer.Color = "#4488ff"; +fillLayer.Opacity = 0.6f; + +// Line layer +var lineLayer = map.Style.AddLineLayer("my-line", "my-vector"); +lineLayer.SourceLayer = "roads"; +lineLayer.Color = "#ff6600"; +lineLayer.Width = 3f; + +// Circle layer +var circleLayer = map.Style.AddCircleLayer("my-circles", "my-geojson"); +circleLayer.Color = "#ff0000"; +circleLayer.Radius = 8f; +circleLayer.Opacity = 0.9f; +circleLayer.StrokeColor = "#ffffff"; +circleLayer.StrokeWidth = 1.5f; + +// Symbol layer +var symbolLayer = map.Style.AddSymbolLayer("my-labels", "my-vector"); +symbolLayer.SourceLayer = "places"; +symbolLayer.TextSize = 14f; +symbolLayer.TextColor = "#333333"; +symbolLayer.TextHaloColor = "#ffffff"; +symbolLayer.TextHaloWidth = 1.5f; + +// Raster layer +var rasterLayer = map.Style.AddRasterLayer("my-raster-layer", "my-raster"); +rasterLayer.Opacity = 0.8f; +rasterLayer.Saturation = -0.4f; + +// Background layer +var bgLayer = map.Style.AddBackgroundLayer("my-bg"); +bgLayer.Color = "#e8e0d8"; +bgLayer.Opacity = 1f; + +// Heatmap layer +var heatmap = map.Style.AddHeatmapLayer("my-heatmap", "my-geojson"); +heatmap.Radius = 20f; +heatmap.Intensity = 1.2f; +heatmap.Opacity = 0.85f; + +// Hillshade layer (requires a raster-DEM source) +var hillshade = map.Style.AddHillshadeLayer("my-hillshade", "my-dem"); +hillshade.Exaggeration = 0.6f; +hillshade.IlluminationDirection = 315f; + +// Fill extrusion layer (3-D buildings) +var extrusion = map.Style.AddFillExtrusionLayer("my-buildings", "my-vector"); +extrusion.SourceLayer = "buildings"; +extrusion.Color = "#aaaaaa"; +extrusion.Height = 50f; +extrusion.Base = 0f; +extrusion.VerticalGradient = true; + +// Color relief layer (requires a raster-DEM source — maplibre-native PR #3965) +var colorRelief = map.Style.AddColorReliefLayer("my-relief", "my-dem"); +colorRelief.Opacity = 0.8f; ``` -cmake . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DMLN_WITH_OSMESA=ON + +### Inspecting existing layers and sources + +```cs +// Get a strongly-typed layer back from the style +if (map.Style.GetLayer("my-fill") is FillLayer fill) +{ + System.Console.WriteLine($"Fill color: {fill.Color}"); +} + +// Iterate all layers +foreach (var layer in map.Style.GetLayers()) +{ + System.Console.WriteLine($"{layer.Type,-18} {layer.Id} visible={layer.Visible}"); +} + +// Get a typed source +if (map.Style.GetSource("my-geojson") is GeoJSONSource gjs) +{ + gjs.SetData(newGeoJsonString); +} ``` -**WARNING:** as OSMesa doesn't have static libraries, it's necessary to copy `libglapi.dll`, `libGLESv2.dll` and `osmesa.dll` from `dependencies\maplibre-native\platform\windows\vendor\mesa3d\` to executable/dll directory you want to use, otherwise it won't run. +### Base layer / source properties -### Building +All layers share common properties inherited from `Layer`: -Finally, build the project with the following command: +| Property | Type | Description | +|---|---|---| +| `Id` | `string` | Read-only identifier | +| `SourceId` | `string` | Source the layer reads from | +| `SourceLayer` | `string` | Vector tile layer name | +| `Visible` | `bool` | `true` = rendered, `false` = hidden | +| `MinZoom` / `MaxZoom` | `float` | Zoom range for rendering | +| `Type` | `string` | Type string (e.g. `"fill"`, `"line"`) | -```cmd -cmake --build build +All sources share properties from `Source`: + +| Property | Type | Description | +|---|---|---| +| `Id` | `string` | Read-only identifier | +| `Type` | `string` | e.g. `"geojson"`, `"vector"`, `"raster"` | +| `Attribution` | `string` | Attribution text (if provided) | +| `IsVolatile` | `bool` | Whether tiles may be evicted from cache | + +## Building the sources + +### Prerequisites + +- **Visual Studio 2022** with the **Desktop Development with C++** workload. + In Visual Studio Installer, confirm that these individual components are selected: + - `C++ CMake tools for Windows` + - `Git for Windows` (if git is not already installed) +- **.NET 6 SDK** (or newer) — required for the C++/CLI `/clr:netcore` compilation. +- **PowerShell** (included with Windows). + +### Downloading sources + +```powershell +git clone --config core.longpaths=true --recurse-submodules -j8 https://github.com/tdcosta100/MaplibreNative.NET.git +cd MaplibreNative.NET +``` + +### Installing vendor packages (vcpkg) + +This step downloads and builds all native dependencies via vcpkg. It only needs to be done once (results are cached). + +```powershell +Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned + +# Bootstrap vcpkg (only needed once) +.\dependencies\maplibre-native\platform\windows\vendor\vcpkg\bootstrap-vcpkg.bat -disableMetrics + +# Install packages (~20–40 min on first run) +.\dependencies\maplibre-native\platform\windows\Get-VendorPackages.ps1 -Triplet x64-windows -Renderer OpenGL -With-ICU ``` -### Building with Microsoft Visual Studio +### Configuring + +The CMake toolchain reads `VSCMD_ARG_TGT_ARCH` to select the vcpkg triplet. Either run the following from a **Developer PowerShell for VS 2022** (which sets this automatically), or set it manually: + +```powershell +$cmake = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" + +$env:VSCMD_ARG_TGT_ARCH = "x64" +$env:VCPKG_BINARY_SOURCES = "clear;files,$PWD\dependencies\maplibre-native\platform\windows\vendor\vcpkg\archives,readwrite" + +& $cmake -B build -G "Visual Studio 17 2022" -A x64 ` + -DMLN_WITH_OPENGL=ON ` + -DMLN_WITH_EGL=OFF ` + -DMLN_WITH_VULKAN=OFF ` + "-DCMAKE_CXX_FLAGS=/wd4267" +``` + +To target a specific .NET version (default is `net8.0`; supported values: `net6.0`, `net8.0`, `net9.0`): + +```powershell +& $cmake -B build -G "Visual Studio 17 2022" -A x64 ` + -DMLN_WITH_OPENGL=ON -DMLN_WITH_EGL=OFF -DMLN_WITH_VULKAN=OFF ` + "-DCMAKE_CXX_FLAGS=/wd4267" ` + "-DMAPLIBRE_NET_DOTNET_VERSION=net6.0" +``` + +### Building -Just omit the `-G Ninja` option from the configure command: +Build only the managed wrapper (fastest): -```cmd -cmake . -B build +```powershell +& $cmake --build build --target mbgl-dotnet --config Release --parallel ``` -The same can be done with alternative configure commands: +Or open `build\MaplibreNative.NET.sln` in Visual Studio, set configuration to `Release`, and build the `mbgl-dotnet` target. + +The output DLL will be at `build\Release\MaplibreNative.NET.dll`. -```cmd -cmake . -B build -DMLN_WITH_EGL=ON +### Alternative renderers + +To configure with EGL support (ANGLE): + +```powershell +& $cmake -B build -G "Visual Studio 17 2022" -A x64 -DMLN_WITH_EGL=ON "-DCMAKE_CXX_FLAGS=/wd4267" ``` -or -```cmd -cmake . -B build -DMLN_WITH_OSMESA=ON + +To configure with OSMesa (software rendering): + +```powershell +& $cmake -B build -G "Visual Studio 17 2022" -A x64 -DMLN_WITH_OSMESA=ON "-DCMAKE_CXX_FLAGS=/wd4267" ``` -Once configure is done, open the file `build\MaplibreNative.NET.sln`. Build the target `ALL_BUILD` to build all targets, or pick a specific target. Don't forget to pick a build configuration (`Release`, `RelWithDebInfo`, `MinSizeRel` or `Debug`), otherwise the project will be built with default configuration (`Debug`). +**Note for OSMesa:** copy `libglapi.dll`, `libGLESv2.dll`, and `osmesa.dll` from `dependencies\maplibre-native\platform\windows\vendor\mesa3d\` next to your application's executable. diff --git a/dependencies/maplibre-native b/dependencies/maplibre-native index a37c8b8..d4bde4c 160000 --- a/dependencies/maplibre-native +++ b/dependencies/maplibre-native @@ -1 +1 @@ -Subproject commit a37c8b8f73de8d357f1847f103646b8e514abd58 +Subproject commit d4bde4c93eb15650d0ddb6822d24351f792ea649 diff --git a/docs/docfx.json b/docs/docfx.json new file mode 100644 index 0000000..ffd3491 --- /dev/null +++ b/docs/docfx.json @@ -0,0 +1,43 @@ +{ + "metadata": [ + { + "src": [ + { + "src": "../", + "files": ["build/Release/MaplibreNative.NET.dll"] + } + ], + "dest": "api", + "includePrivateMembers": false, + "disableGitFeatures": false, + "disableDefaultFilter": false + } + ], + "build": { + "content": [ + { + "files": ["**/*.yml", "**/*.md"], + "src": ".", + "exclude": ["_site/**", "obj/**"] + } + ], + "resource": [ + { + "files": ["images/**"] + } + ], + "output": "_site", + "template": ["default", "modern"], + "globalMetadata": { + "_appName": "MaplibreNative.NET", + "_appTitle": "MaplibreNative.NET", + "_appFooter": "MaplibreNative.NET — .NET wrapper for maplibre-native", + "_enableSearch": true + }, + "fileMetadata": {}, + "postProcessors": [], + "keepFileLink": false, + "cleanupCacheHistory": false, + "disableGitFeatures": false + } +} diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..41aac53 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,25 @@ +# MaplibreNative.NET + +A .NET 8/9 wrapper for [maplibre-native](https://github.com/maplibre/maplibre-native), written in C++/CLI. + +## Quick Start + +Install the NuGet package and add a map to your WPF or WinForms application: + +```csharp +using MaplibreNative; + +var map = new Map(backend, rendererFrontend, observer, resourceOptions); +map.LoadStyleJSON(styleJson); +map.SetCameraOptions(new CameraOptions { Center = new LatLng(51.5, -0.09), Zoom = 13 }); +``` + +## API Reference + +See the [API Reference](xref:MaplibreNative) for full documentation of all classes and methods. + +## Requirements + +- Windows x64 or ARM64 +- .NET 8 or .NET 9 +- Visual C++ Redistributable 2022 diff --git a/docs/toc.yml b/docs/toc.yml new file mode 100644 index 0000000..db5f212 --- /dev/null +++ b/docs/toc.yml @@ -0,0 +1,4 @@ +- name: Home + href: index.md +- name: API Reference + href: api/ diff --git a/format.ps1 b/format.ps1 new file mode 100644 index 0000000..d648702 --- /dev/null +++ b/format.ps1 @@ -0,0 +1,83 @@ +<# +.SYNOPSIS + Normalizes indentation in all C++/CLI source and header files under + src/ and include/. + +.DESCRIPTION + For each .cpp and .h file found under src/ and include/: + - Converts leading tabs to 4 spaces (per indent level) + - Strips trailing whitespace from every line + - Ensures the file ends with a single newline + - Writes files only when changes are needed (avoids unnecessary + git diffs) + + The dependencies/ directory is never touched. + +.PARAMETER DryRun + Print which files would be changed without writing anything. + +.EXAMPLE + .\format.ps1 + .\format.ps1 -DryRun +#> +param( + [switch]$DryRun +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$root = $PSScriptRoot +$dirs = @( + Join-Path $root 'src' + Join-Path $root 'include' +) + +$changed = 0 +$unchanged = 0 + +foreach ($dir in $dirs) { + if (-not (Test-Path $dir)) { continue } + + Get-ChildItem -Path $dir -Recurse -Include '*.cpp', '*.h' | ForEach-Object { + $file = $_.FullName + + # Read as raw text to preserve line endings during analysis + $raw = [System.IO.File]::ReadAllText($file) + + # Normalise CRLF -> LF for processing, then restore at the end + $lines = $raw -replace "`r`n", "`n" -replace "`r", "`n" -split "`n" + + $newLines = $lines | ForEach-Object { + $line = $_ + + # Expand leading tabs: replace each tab with 4 spaces + # (handles mixed indent correctly by expanding one tab at a time) + while ($line -match "^( *)`t") { + $line = $line -replace "^( *)`t", ('$1' + ' ') + } + + # Strip trailing whitespace + $line.TrimEnd() + } + + # Ensure single trailing newline + $newContent = ($newLines -join "`r`n").TrimEnd() + "`r`n" + + if ($newContent -ne $raw) { + if ($DryRun) { + Write-Host "Would format: $($_.Name)" + } else { + [System.IO.File]::WriteAllText($file, $newContent, [System.Text.Encoding]::UTF8) + Write-Host "Formatted: $($_.Name)" + } + $changed++ + } else { + $unchanged++ + } + } +} + +$action = if ($DryRun) { 'Would change' } else { 'Changed' } +Write-Host "" +Write-Host "$action $changed file(s), $unchanged already clean." diff --git a/include/AnimationOptions.h b/include/AnimationOptions.h index d67bbeb..3bc265f 100644 --- a/include/AnimationOptions.h +++ b/include/AnimationOptions.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include #include @@ -17,7 +17,7 @@ namespace DOTNET_NAMESPACE using TransitionFrameFunction_ = TransitionFrameFunction; using TransitionFinishFunction_ = TransitionFinishFunction; - + class NativeTransitionFunctionsHelper { public: @@ -56,14 +56,14 @@ namespace DOTNET_NAMESPACE /// /// Average velocity of a flyTo() transition, measured in screenfuls per /// second, assuming a linear timing curve. - /// + /// /// A screenful is the visible span in pixels. It does not correspond /// to a fixed physical distance but rather varies by zoom level. /// property System::Nullable Velocity { System::Nullable get(); System::Void set(System::Nullable value); } /// - /// Zero-based zoom level at the peak of the flyTo() transitions flight + /// Zero-based zoom level at the peak of the flyTo() transition�s flight /// path. /// property System::Nullable MinZoom { System::Nullable get(); System::Void set(System::Nullable value); } diff --git a/include/BoundOptions.h b/include/BoundOptions.h index f34d8ad..311201b 100644 --- a/include/BoundOptions.h +++ b/include/BoundOptions.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace mbgl @@ -39,7 +39,7 @@ namespace DOTNET_NAMESPACE /// /// BoundOptions^ WithMaxZoom(System::Double z); - + /// /// Sets the minimum pitch /// diff --git a/include/CameraOptions.h b/include/CameraOptions.h index 3925ff0..2d3ff96 100644 --- a/include/CameraOptions.h +++ b/include/CameraOptions.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include "Point.h" @@ -11,7 +11,7 @@ namespace DOTNET_NAMESPACE { ref class EdgeInsets; ref class LatLng; - + /// /// Various options for describing the viewpoint of a map. All properties are /// optional. @@ -25,11 +25,14 @@ namespace DOTNET_NAMESPACE ~CameraOptions(); CameraOptions^ WithCenter(LatLng^ o); + CameraOptions^ WithCenterAltitude(System::Nullable o); CameraOptions^ WithPadding(EdgeInsets^ p); CameraOptions^ WithAnchor(System::Nullable o); CameraOptions^ WithZoom(System::Nullable o); CameraOptions^ WithBearing(System::Nullable o); CameraOptions^ WithPitch(System::Nullable o); + CameraOptions^ WithRoll(System::Nullable o); + CameraOptions^ WithFov(System::Nullable o); /// /// Coordinate at the center of the map. @@ -64,6 +67,21 @@ namespace DOTNET_NAMESPACE /// property System::Nullable Pitch { System::Nullable get(); System::Void set(System::Nullable value); } + /// + /// Camera roll, measured in degrees. + /// + property System::Nullable Roll { System::Nullable get(); System::Void set(System::Nullable value); } + + /// + /// Camera vertical field of view, measured in degrees. + /// + property System::Nullable Fov { System::Nullable get(); System::Void set(System::Nullable value); } + + /// + /// Altitude of the center of the map, in meters above sea level. + /// + property System::Nullable CenterAltitude { System::Nullable get(); System::Void set(System::Nullable value); } + static System::Boolean operator==(CameraOptions^ a, CameraOptions^ b); static System::Boolean operator!=(CameraOptions^ a, CameraOptions^ b); internal: diff --git a/include/Convert.h b/include/Convert.h index 0ed2be2..0c8b3d4 100644 --- a/include/Convert.h +++ b/include/Convert.h @@ -57,11 +57,11 @@ namespace DOTNET_NAMESPACE } template - static std::chrono::time_point ToTimePoint(System::DateTime^ dateTime) + static std::chrono::time_point ToTimePoint(System::DateTime dateTime) { return std::chrono::time_point_cast( std::chrono::time_point( - std::chrono::nanoseconds((dateTime->Ticks - epochTicks) * 100) + std::chrono::nanoseconds((dateTime.Ticks - epochTicks) * 100) ) ); } diff --git a/include/EdgeInsets.h b/include/EdgeInsets.h index cc5905a..fe24d6f 100644 --- a/include/EdgeInsets.h +++ b/include/EdgeInsets.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include "Point.h" @@ -47,7 +47,7 @@ namespace DOTNET_NAMESPACE System::Void operator+=(EdgeInsets^ o); EdgeInsets^ operator+(EdgeInsets^ o); - + static System::Boolean operator==(EdgeInsets^ a, EdgeInsets^ b); static System::Boolean operator!=(EdgeInsets^ a, EdgeInsets^ b); internal: diff --git a/include/Enums.h b/include/Enums.h index bab6453..0dbc233 100644 --- a/include/Enums.h +++ b/include/Enums.h @@ -14,6 +14,40 @@ namespace DOTNET_NAMESPACE None, HeightOnly, WidthAndHeight, + /// + /// Constrain the map so that the screen bounds match the lat/lng bounds set via BoundOptions. + /// + Screen, + }; + + /// + /// Selects the tile LOD (level of detail) algorithm used when rendering at high pitch. + /// + public enum class TileLodMode : System::Byte + { + /// + /// Default tile LOD algorithm (center-of-screen based). + /// + Default, + /// + /// Distance-based tile LOD algorithm (tiles closer to the camera are rendered at higher detail). + /// + Distance, + }; + + /// + /// The type of a map style source. + /// + public enum class SourceType : System::Byte + { + Vector, + Raster, + RasterDEM, + GeoJSON, + Video, + Annotations, + Image, + CustomVector, }; public enum class ViewportMode : System::UInt32 @@ -49,4 +83,114 @@ namespace DOTNET_NAMESPACE /// ResourceLoader }; + + // ========================================================================= + // Style layer property enums + // ========================================================================= + + public enum class LineCapType : System::Byte + { + Round, + Butt, + Square, + }; + + public enum class LineJoinType : System::Byte + { + Miter, + Bevel, + Round, + }; + + /// Controls the frame of reference for line/fill/circle translate. + public enum class TranslateAnchorType : System::Byte + { + Map, + Viewport, + }; + + /// Controls how the circle is translated relative to the camera pitch. + public enum class CirclePitchScaleType : System::Byte + { + Map, + Viewport, + }; + + public enum class SymbolPlacementType : System::Byte + { + Point, + Line, + LineCenter, + }; + + public enum class SymbolZOrderType : System::Byte + { + Auto, + ViewportY, + Source, + }; + + /// Alignment type used for icon/text pitch and rotation alignment. + public enum class AlignmentType : System::Byte + { + Map, + Viewport, + Auto, + }; + + public enum class TextJustifyType : System::Byte + { + Auto, + Center, + Left, + Right, + }; + + public enum class SymbolAnchorType : System::Byte + { + Center, + Left, + Right, + Top, + Bottom, + TopLeft, + TopRight, + BottomLeft, + BottomRight, + }; + + public enum class TextTransformType : System::Byte + { + None, + Uppercase, + Lowercase, + }; + + public enum class IconTextFitType : System::Byte + { + None, + Both, + Width, + Height, + }; + + public enum class TextWritingModeType : System::Byte + { + Horizontal, + Vertical, + }; + + /// Resampling method for raster tiles when scaling. + public enum class RasterResamplingType : System::Byte + { + Linear, + Nearest, + }; + + /// Frame of reference for the hillshade illumination direction. + public enum class HillshadeIlluminationAnchorType : System::Byte + { + Map, + Viewport, + }; } diff --git a/include/ExternalRenderingContextFrontend.h b/include/ExternalRenderingContextFrontend.h index 1fc328d..698c09b 100644 --- a/include/ExternalRenderingContextFrontend.h +++ b/include/ExternalRenderingContextFrontend.h @@ -1,6 +1,7 @@ #pragma once #include "NativeWrapper.h" #include "RendererFrontend.h" +#include #include #include #include @@ -66,6 +67,7 @@ namespace DOTNET_NAMESPACE void setObserver(mbgl::RendererObserver& observer) override; void update(std::shared_ptr parameters) override; void update(std::shared_ptr parameters, bool callUpdatedHandler); + const mbgl::TaggedScheduler& getThreadPool() const override { return _Backend->getThreadPool(); } void render(); NativeExternalRenderingContextBackend* getBackend(); mbgl::Renderer* getRenderer(); diff --git a/include/FileSource.h b/include/FileSource.h index c7124f7..dcd15d5 100644 --- a/include/FileSource.h +++ b/include/FileSource.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include #include @@ -22,7 +22,7 @@ namespace DOTNET_NAMESPACE using ClientOptions_ = ClientOptions; using ResourceOptions_ = ResourceOptions; - + public ref class FileSource : NativeWrapper> { public: @@ -30,7 +30,7 @@ namespace DOTNET_NAMESPACE ~FileSource(); !FileSource(); - + /// /// Request a resource. The callback will be called asynchronously, in the /// same thread as the request was made. This thread must have an active @@ -73,7 +73,7 @@ namespace DOTNET_NAMESPACE /// /// Pause file request activity. - /// + /// /// If pause is called then no revalidation or network request activity /// will occur. /// @@ -82,7 +82,7 @@ namespace DOTNET_NAMESPACE /// /// Resume file request activity. - /// + /// /// Calling resume will unpause the file source and process any tasks that /// expired while the file source was paused. /// diff --git a/include/FileSourceManager.h b/include/FileSourceManager.h index 7e0bd23..c6b5351 100644 --- a/include/FileSourceManager.h +++ b/include/FileSourceManager.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace DOTNET_NAMESPACE @@ -6,12 +6,12 @@ namespace DOTNET_NAMESPACE ref class ClientOptions; ref class FileSource; ref class ResourceOptions; - + /// /// A singleton class responsible for managing file sources. - /// + /// /// The FileSourceManager provides following functionality: - /// + /// /// - provides access to file sources of a specific type and configuration /// - caches previously created file sources of a (type, configuration) tuples /// @@ -42,4 +42,4 @@ namespace DOTNET_NAMESPACE FileSourceManager(); ~FileSourceManager(); }; -} \ No newline at end of file +} diff --git a/include/FreeCameraOptions.h b/include/FreeCameraOptions.h index 43872ed..19f1caa 100644 --- a/include/FreeCameraOptions.h +++ b/include/FreeCameraOptions.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace mbgl @@ -33,7 +33,7 @@ namespace DOTNET_NAMESPACE /// /// System::Void LookAtPoint(LatLng^ location); - + /// /// Helper function for setting orientation of the camera by defining a /// focus point on the map. Up vector is required in certain scenarios where @@ -44,13 +44,14 @@ namespace DOTNET_NAMESPACE System::Void LookAtPoint(LatLng^ location, Vec3^ upVector); /// - /// Helper function for setting the orientation of the camera as a pitch and - /// a bearing.Both values are in degrees + /// Helper function for setting the orientation of the camera as a roll, pitch and + /// a bearing. All values are in degrees. /// + /// /// /// /// - System::Void SetPitchBearing(System::Double pitch, System::Double bearing); + System::Void SetRollPitchBearing(System::Double roll, System::Double pitch, System::Double bearing); /// /// Position of the camera in slightly modified web mercator coordinates @@ -68,9 +69,8 @@ namespace DOTNET_NAMESPACE /// The default pose of the camera is such that the forward vector is /// looking up the -Z axis and the up vector is aligned with north /// orientation of the map: forward [0, 0, -1], up [0, -1, 0], right [1, 0, 0] - /// + /// /// Orientation can be set freely but certain constraints still apply - /// - Orientation must be representable with only pitch and bearing. /// - Pitch has an upper limit /// property Vec4^ Orientation { Vec4^ get(); System::Void set(Vec4^ value); } diff --git a/include/GeoJSON.h b/include/GeoJSON.h index 9f961fd..5218db5 100644 --- a/include/GeoJSON.h +++ b/include/GeoJSON.h @@ -4,6 +4,11 @@ namespace DOTNET_NAMESPACE { + /// + /// Wraps a parsed GeoJSON value (geometry, feature, or feature collection). + /// Instances are returned by and used when passing + /// geometry data to map sources. + /// public ref class GeoJSON : NativeWrapper { public: diff --git a/include/Image.h b/include/Image.h index d97095d..5c30bd1 100644 --- a/include/Image.h +++ b/include/Image.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Convert.h" #include "NativeWrapper.h" #include "Size.h" @@ -36,18 +36,18 @@ namespace DOTNET_NAMESPACE public ref class ImageBase : NativeWrapper> { public: - ImageBase() : NativeWrapper(CreateImage()) + ImageBase() : NativeWrapper>(CreateImage()) { } - ImageBase(Size_^ size) : NativeWrapper(CreateImage(size)) + ImageBase(Size_^ size) : NativeWrapper>(CreateImage(size)) { } - - ImageBase(Size_^ size, cli::array^ srcData) : NativeWrapper(CreateImage(size, srcData)) + + ImageBase(Size_^ size, cli::array^ srcData) : NativeWrapper>(CreateImage(size, srcData)) { } - + virtual ~ImageBase() { } @@ -57,20 +57,20 @@ namespace DOTNET_NAMESPACE System::Void Fill(System::Byte value) { - NativePointer->fill(value); + this->NativePointer->fill(value); } System::Void Resize(Size_^ size) { - NativePointer->resize(*size->NativePointer); + this->NativePointer->resize(*size->NativePointer); } cli::array^ GetData() { - cli::array^ result = gcnew cli::array(static_cast(NativePointer->bytes())); + cli::array^ result = gcnew cli::array(static_cast(this->NativePointer->bytes())); + + System::Runtime::InteropServices::Marshal::Copy(System::IntPtr(this->NativePointer->data.get()), result, 0, result->Length); - System::Runtime::InteropServices::Marshal::Copy(System::IntPtr(NativePointer->data.get()), result, 0, result->Length); - return result; } @@ -78,7 +78,7 @@ namespace DOTNET_NAMESPACE { System::IntPtr get() { - return System::IntPtr(NativePointer->data.get()); + return System::IntPtr(this->NativePointer->data.get()); } } @@ -86,7 +86,7 @@ namespace DOTNET_NAMESPACE { System::Boolean get() { - return NativePointer->valid(); + return this->NativePointer->valid(); } } @@ -94,7 +94,7 @@ namespace DOTNET_NAMESPACE { System::UInt64 get() { - return NativePointer->stride(); + return this->NativePointer->stride(); } } @@ -102,7 +102,7 @@ namespace DOTNET_NAMESPACE { System::UInt64 get() { - return NativePointer->bytes(); + return this->NativePointer->bytes(); } } @@ -110,7 +110,7 @@ namespace DOTNET_NAMESPACE { Size_^ get() { - return gcnew Size_(Size_::CreateNativePointerHolder(NativePointer->size)); + return gcnew Size_(Size_::CreateNativePointerHolder(this->NativePointer->size)); } } @@ -118,18 +118,18 @@ namespace DOTNET_NAMESPACE { System::Int64 get() { - return NativePointer->channels; + return this->NativePointer->channels; } } System::Boolean operator==(ImageBase^ rhs) { - return *NativePointer == *rhs->NativePointer; + return *this->NativePointer == *rhs->NativePointer; } System::Boolean operator!=(ImageBase^ rhs) { - return *NativePointer != *rhs->NativePointer; + return *this->NativePointer != *rhs->NativePointer; } /// @@ -162,7 +162,7 @@ namespace DOTNET_NAMESPACE template ::Type> static System::Void Copy(T^ srcImg, T^ dstImg, PointUInt srcPt, PointUInt dstPt, Size_^ size); internal: - ImageBase(NativePointerHolder>^ nativePointerHolder) : NativeWrapper(nativePointerHolder) + ImageBase(NativePointerHolder>^ nativePointerHolder) : NativeWrapper>(nativePointerHolder) { } private: diff --git a/include/LatLng.h b/include/LatLng.h index 59b98c6..852106c 100644 --- a/include/LatLng.h +++ b/include/LatLng.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace mbgl @@ -12,7 +12,7 @@ namespace DOTNET_NAMESPACE { ref class CanonicalTileID; ref class UnwrappedTileID; - + public ref class LatLng : NativeWrapper { public: diff --git a/include/Layer.h b/include/Layer.h new file mode 100644 index 0000000..662138b --- /dev/null +++ b/include/Layer.h @@ -0,0 +1,97 @@ +#pragma once + +namespace mbgl { +namespace style { +class Layer; +} // namespace style +} // namespace mbgl + +namespace DOTNET_NAMESPACE +{ + /// + /// Non-owning wrapper for an mbgl::style::Layer. + /// Lifetime is tied to the Style that owns the native layer — do not hold + /// references after the Style is disposed. + /// + public ref class Layer + { + internal: + mbgl::style::Layer* _layer; // raw, non-owning + Layer(mbgl::style::Layer* layer); + + public: + /// Unique identifier for the layer. + property System::String^ Id { System::String^ get(); } + + /// + /// Source identifier this layer reads from. + /// Empty string for background layers. + /// + property System::String^ SourceId + { + System::String^ get(); + System::Void set(System::String^); + } + + /// + /// Source-layer name used when drawing from a vector tile source. + /// + property System::String^ SourceLayer + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Whether this layer is rendered (true = visible, false = none). + property bool Visible + { + bool get(); + System::Void set(bool); + } + + /// Minimum zoom level at which this layer is rendered. + property float MinZoom + { + float get(); + System::Void set(float); + } + + /// Maximum zoom level at which this layer is rendered. + property float MaxZoom + { + float get(); + System::Void set(float); + } + + /// + /// Layer type string as reported by the native library + /// (e.g. "fill", "line", "circle", "symbol", "raster", "background", + /// "heatmap", "hillshade", "fill-extrusion"). + /// + property System::String^ Type { System::String^ get(); } + + /// + /// Gets the current filter expression as a JSON string, + /// or an empty string if no filter is set. + /// + System::String^ GetFilter(); + + /// + /// Sets a filter expression from a MapLibre filter JSON string, + /// e.g. ["==", "class", "motorway"] or a full expression array. + /// Pass null or an empty string to clear the filter. + /// Throws if the JSON cannot be parsed as a valid filter expression. + /// + System::Void SetFilter(System::String^ filterJson); + + /// + /// Returns the JSON-encoded value of a paint property, or an empty string if not set. + /// + System::String^ GetPaintProperty(System::String^ name); + + /// + /// Returns the JSON-encoded value of a layout property, or an empty string if not set. + /// + System::String^ GetLayoutProperty(System::String^ name); + }; +} diff --git a/include/Layers.h b/include/Layers.h new file mode 100644 index 0000000..37050a9 --- /dev/null +++ b/include/Layers.h @@ -0,0 +1,913 @@ +#pragma once +#include "Layer.h" +#include "Enums.h" + +// Forward-declare native layer types +namespace mbgl { +namespace style { +class FillLayer; +class LineLayer; +class CircleLayer; +class SymbolLayer; +class RasterLayer; +class BackgroundLayer; +class HeatmapLayer; +class HillshadeLayer; +class FillExtrusionLayer; +class ColorReliefLayer; +class LocationIndicatorLayer; +} // namespace style +} // namespace mbgl + +namespace DOTNET_NAMESPACE +{ + // ========================================================================= + // FillLayer + // ========================================================================= + + /// Wraps an mbgl::style::FillLayer. + public ref class FillLayer : public Layer + { + internal: + FillLayer(mbgl::style::FillLayer* layer); + + private: + mbgl::style::FillLayer* Impl(); + + public: + /// + /// Fill color as a CSS hex string (e.g. "#rrggbb"). + /// Returns the current constant value, or empty string for expressions. + /// + property System::String^ Color + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Fill opacity (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + + /// + /// Outline color as a CSS hex string. + /// Defaults to the fill color if not set. + /// + property System::String^ OutlineColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Whether to anti-alias the fill edges. + property bool Antialias + { + bool get(); + System::Void set(bool); + } + + /// Sort key for feature rendering order within the layer. + property float SortKey { float get(); System::Void set(float); } + + /// Sprite image name for the fill pattern. Empty string = no pattern. + property System::String^ Pattern { System::String^ get(); System::Void set(System::String^); } + + /// X/Y translation of the fill layer in pixels as [x, y]. + property cli::array^ Translate { cli::array^ get(); System::Void set(cli::array^); } + + /// Frame of reference for Translate: Map or Viewport. + property TranslateAnchorType TranslateAnchor { TranslateAnchorType get(); System::Void set(TranslateAnchorType); } + }; + + // ========================================================================= + // LineLayer + // ========================================================================= + + /// Wraps an mbgl::style::LineLayer. + public ref class LineLayer : public Layer + { + internal: + LineLayer(mbgl::style::LineLayer* layer); + + private: + mbgl::style::LineLayer* Impl(); + + public: + /// Stroke color as a CSS hex string. + property System::String^ Color + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Stroke opacity (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + + /// Stroke width in pixels. + property float Width + { + float get(); + System::Void set(float); + } + + /// Line blur applied in pixels. + property float Blur + { + float get(); + System::Void set(float); + } + + /// Gap width in pixels (draws a gap between two parallel lines). + property float GapWidth + { + float get(); + System::Void set(float); + } + + /// Offset in pixels (positive = right of direction, negative = left). + property float Offset + { + float get(); + System::Void set(float); + } + + /// Cap style at the ends of lines. + property LineCapType Cap { LineCapType get(); System::Void set(LineCapType); } + + /// Join style at corners of lines. + property LineJoinType Join { LineJoinType get(); System::Void set(LineJoinType); } + + /// Limit at which a miter join is converted to a bevel join. Default: 2. + property float MiterLimit { float get(); System::Void set(float); } + + /// Limit at which a round join is converted to a bevel join. Default: 1.05. + property float RoundLimit { float get(); System::Void set(float); } + + /// Sort key for feature rendering order within the layer. + property float SortKey { float get(); System::Void set(float); } + + /// + /// Dash pattern for the line as alternating gap/dash lengths (in line widths). + /// Returns an empty array when unset (solid line). + /// + property cli::array^ Dasharray { cli::array^ get(); System::Void set(cli::array^); } + + /// Sprite image name for a repeating line pattern. Empty string = no pattern. + property System::String^ Pattern { System::String^ get(); System::Void set(System::String^); } + + /// X/Y translation of the line layer in pixels as [x, y]. + property cli::array^ Translate { cli::array^ get(); System::Void set(cli::array^); } + + /// Frame of reference for Translate: Map or Viewport. + property TranslateAnchorType TranslateAnchor { TranslateAnchorType get(); System::Void set(TranslateAnchorType); } + + /// + /// Line gradient as a MapLibre expression JSON string (requires lineMetrics=true on the source). + /// Maps line progress (0–1) to a color. Returns empty string when unset. + /// Throws ArgumentException on invalid expression JSON. + /// + property System::String^ Gradient + { + System::String^ get(); + System::Void set(System::String^); + } + }; + // ========================================================================= + + /// Wraps an mbgl::style::CircleLayer. + public ref class CircleLayer : public Layer + { + internal: + CircleLayer(mbgl::style::CircleLayer* layer); + + private: + mbgl::style::CircleLayer* Impl(); + + public: + /// Circle fill color as a CSS hex string. + property System::String^ Color + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Circle opacity (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + + /// Circle radius in pixels. + property float Radius + { + float get(); + System::Void set(float); + } + + /// Stroke width in pixels. + property float StrokeWidth + { + float get(); + System::Void set(float); + } + + /// Stroke color as a CSS hex string. + property System::String^ StrokeColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Stroke opacity (0.0 – 1.0). + property float StrokeOpacity + { + float get(); + System::Void set(float); + } + + /// Blur applied to the circle (pixels). + property float Blur + { + float get(); + System::Void set(float); + } + + /// Sort key for feature rendering order within the layer. + property float SortKey { float get(); System::Void set(float); } + + /// Controls the scaling of circle radius with camera pitch. + property CirclePitchScaleType PitchScale { CirclePitchScaleType get(); System::Void set(CirclePitchScaleType); } + + /// Orientation of the circle relative to map or viewport. + property AlignmentType PitchAlignment { AlignmentType get(); System::Void set(AlignmentType); } + + /// X/Y translation of circles in pixels as [x, y]. + property cli::array^ Translate { cli::array^ get(); System::Void set(cli::array^); } + + /// Frame of reference for Translate: Map or Viewport. + property TranslateAnchorType TranslateAnchor { TranslateAnchorType get(); System::Void set(TranslateAnchorType); } + }; + + // ========================================================================= + // SymbolLayer + // ========================================================================= + + /// Wraps an mbgl::style::SymbolLayer. + public ref class SymbolLayer : public Layer + { + internal: + SymbolLayer(mbgl::style::SymbolLayer* layer); + + private: + mbgl::style::SymbolLayer* Impl(); + + public: + // ---- Layout: Symbol placement ---- + + /// How symbol icons and text are placed relative to its geometry. Default: Point. + property SymbolPlacementType SymbolPlacement { SymbolPlacementType get(); System::Void set(SymbolPlacementType); } + + /// Distance between two symbol anchors when placement is Line. Default: 250. + property float SymbolSpacing { float get(); System::Void set(float); } + + /// If true, symbols will not cross tile edges to avoid gaps or overlaps. + property bool SymbolAvoidEdges { bool get(); System::Void set(bool); } + + /// Sort key for overlap resolution (lower = drawn below higher values). + property float SymbolSortKey { float get(); System::Void set(float); } + + /// Determines whether overlapping symbols will be rendered in the order they appear in the data source. Default: Auto. + property SymbolZOrderType SymbolZOrder { SymbolZOrderType get(); System::Void set(SymbolZOrderType); } + + // ---- Layout: Icon ---- + + /// + /// Name of a sprite image to use for the icon. + /// Returns the first section's image id for constant Formatted values, empty string otherwise. + /// + property System::String^ IconImage { System::String^ get(); System::Void set(System::String^); } + + /// Scale factor for the icon image. Default: 1. + property float IconSize { float get(); System::Void set(float); } + + /// Part of icon to anchor to its placement position. Default: Center. + property SymbolAnchorType IconAnchor { SymbolAnchorType get(); System::Void set(SymbolAnchorType); } + + /// Rotates the icon clockwise by this angle in degrees. + property float IconRotate { float get(); System::Void set(float); } + + /// Offset distance of icon from its anchor in [x, y] ems. + property cli::array^ IconOffset { cli::array^ get(); System::Void set(cli::array^); } + + /// Extra padding around icon bounding box used to detect symbol collisions. Default: 2. + property float IconPadding { float get(); System::Void set(float); } + + /// If true, the icon may be flipped to prevent upside-down text. Default: false. + property bool IconKeepUpright { bool get(); System::Void set(bool); } + + /// If true, other symbols can be visible even if they collide with the icon. Default: false. + property bool IconAllowOverlap { bool get(); System::Void set(bool); } + + /// If true, the icon will be visible even if it collides with other symbols. Default: false. + property bool IconIgnorePlacement { bool get(); System::Void set(bool); } + + /// If true, text will be displayed without icon if icon is not available. Default: false. + property bool IconOptional { bool get(); System::Void set(bool); } + + /// In combination with SymbolPlacement, determines orientation of icon. Default: Auto. + property AlignmentType IconRotationAlignment { AlignmentType get(); System::Void set(AlignmentType); } + + /// Orientation of icon when map is pitched. Default: Auto. + property AlignmentType IconPitchAlignment { AlignmentType get(); System::Void set(AlignmentType); } + + // ---- Layout: Text ---- + + /// + /// Text content to display (plain string; for rich text use the style JSON directly). + /// + property System::String^ TextField { System::String^ get(); System::Void set(System::String^); } + + /// + /// Ordered list of font stack names. E.g. { "Open Sans Bold", "Arial Unicode MS Bold" }. + /// Returns an empty array if not set. + /// + property cli::array^ TextFont { cli::array^ get(); System::Void set(cli::array^); } + + /// Font size in pixels. Default: 16. + property float TextSize { float get(); System::Void set(float); } + + /// Maximum line width for text wrapping in ems. Default: 10. + property float TextMaxWidth { float get(); System::Void set(float); } + + /// Text leading value for multi-line text. Default: 1.2. + property float TextLineHeight { float get(); System::Void set(float); } + + /// Spacing between letters in ems. Default: 0. + property float TextLetterSpacing { float get(); System::Void set(float); } + + /// Text justification. Default: Center. + property TextJustifyType TextJustify { TextJustifyType get(); System::Void set(TextJustifyType); } + + /// Radial offset in ems from the anchor (used instead of TextOffset for line/circle placement). + property float TextRadialOffset { float get(); System::Void set(float); } + + /// Part of text to anchor to the symbol placement position. Default: Center. + property SymbolAnchorType TextAnchor { SymbolAnchorType get(); System::Void set(SymbolAnchorType); } + + /// Maximum angle between adjacent glyph segments for line placement in degrees. + property float TextMaxAngle { float get(); System::Void set(float); } + + /// Rotates the text clockwise by this angle in degrees. + property float TextRotate { float get(); System::Void set(float); } + + /// Extra padding around text bounding box. Default: 2. + property float TextPadding { float get(); System::Void set(float); } + + /// If true, the text may be flipped vertically to prevent upside-down text. Default: true. + property bool TextKeepUpright { bool get(); System::Void set(bool); } + + /// Specifies how to capitalize text. Default: None. + property TextTransformType TextTransform { TextTransformType get(); System::Void set(TextTransformType); } + + /// Offset distance of text from its anchor in [x, y] ems. + property cli::array^ TextOffset { cli::array^ get(); System::Void set(cli::array^); } + + /// If true, other symbols can be visible even if they collide with the text. + property bool TextAllowOverlap { bool get(); System::Void set(bool); } + + /// If true, the text will be visible even if it collides with other symbols. + property bool TextIgnorePlacement { bool get(); System::Void set(bool); } + + /// If true, icons will be shown without text if text is not available. + property bool TextOptional { bool get(); System::Void set(bool); } + + /// In combination with SymbolPlacement, determines orientation of text. Default: Auto. + property AlignmentType TextRotationAlignment { AlignmentType get(); System::Void set(AlignmentType); } + + /// Orientation of text when map is pitched. Default: Auto. + property AlignmentType TextPitchAlignment { AlignmentType get(); System::Void set(AlignmentType); } + + // ---- Paint: Text ---- + + /// Text color as a CSS hex string. + property System::String^ TextColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Text opacity (0.0 – 1.0). + property float TextOpacity + { + float get(); + System::Void set(float); + } + + /// Halo color around text glyphs as a CSS hex string. + property System::String^ TextHaloColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Halo width in pixels. + property float TextHaloWidth + { + float get(); + System::Void set(float); + } + + /// Halo blur applied to text glyphs in pixels. + property float TextHaloBlur { float get(); System::Void set(float); } + + // ---- Paint: Icon ---- + + /// Icon opacity (0.0 – 1.0). + property float IconOpacity + { + float get(); + System::Void set(float); + } + + /// Icon color (used for SDF icons) as a CSS hex string. + property System::String^ IconColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Icon halo color as a CSS hex string. + property System::String^ IconHaloColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Icon halo width in pixels. + property float IconHaloWidth + { + float get(); + System::Void set(float); + } + + /// Icon halo blur in pixels. + property float IconHaloBlur { float get(); System::Void set(float); } + + /// + /// Scales the icon to fit around the associated text. + /// None = icon not scaled, Both = icon scaled to fit text in both axes, + /// Width/Height = scaled in one axis only. + /// + property IconTextFitType IconTextFit { IconTextFitType get(); System::Void set(IconTextFitType); } + + /// + /// Padding applied to each side of the icon bounding box when using IconTextFit + /// as [top, right, bottom, left] in ems. Returns {0,0,0,0} when unset. + /// + property cli::array^ IconTextFitPadding { cli::array^ get(); System::Void set(cli::array^); } + + /// X/Y translation of icons in pixels as [x, y]. + property cli::array^ IconTranslate { cli::array^ get(); System::Void set(cli::array^); } + + /// Frame of reference for IconTranslate: Map or Viewport. + property TranslateAnchorType IconTranslateAnchor { TranslateAnchorType get(); System::Void set(TranslateAnchorType); } + + /// X/Y translation of text in pixels as [x, y]. + property cli::array^ TextTranslate { cli::array^ get(); System::Void set(cli::array^); } + + /// Frame of reference for TextTranslate: Map or Viewport. + property TranslateAnchorType TextTranslateAnchor { TranslateAnchorType get(); System::Void set(TranslateAnchorType); } + }; + + // ========================================================================= + // RasterLayer + // ========================================================================= + + /// Wraps an mbgl::style::RasterLayer. + public ref class RasterLayer : public Layer + { + internal: + RasterLayer(mbgl::style::RasterLayer* layer); + + private: + mbgl::style::RasterLayer* Impl(); + + public: + /// Raster opacity (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + + /// Minimum brightness (0.0 – 1.0). + property float BrightnessMin + { + float get(); + System::Void set(float); + } + + /// Maximum brightness (0.0 – 1.0). + property float BrightnessMax + { + float get(); + System::Void set(float); + } + + /// Contrast adjustment (-1.0 – 1.0). + property float Contrast + { + float get(); + System::Void set(float); + } + + /// Hue rotation in degrees (0 – 360). + property float HueRotate + { + float get(); + System::Void set(float); + } + + /// Saturation adjustment (-1.0 – 1.0). + property float Saturation + { + float get(); + System::Void set(float); + } + + /// Fade duration when a new tile is added (milliseconds). + property float FadeDuration + { + float get(); + System::Void set(float); + } + + /// Resampling method used when scaling raster tiles. Default: Linear. + property RasterResamplingType Resampling { RasterResamplingType get(); System::Void set(RasterResamplingType); } + }; + + // ========================================================================= + // BackgroundLayer + // ========================================================================= + + /// Wraps an mbgl::style::BackgroundLayer (no source). + public ref class BackgroundLayer : public Layer + { + internal: + BackgroundLayer(mbgl::style::BackgroundLayer* layer); + + private: + mbgl::style::BackgroundLayer* Impl(); + + public: + /// Background fill color as a CSS hex string. + property System::String^ Color + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Background opacity (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + + /// Sprite image name for a repeating background pattern. Empty string = solid color. + property System::String^ Pattern { System::String^ get(); System::Void set(System::String^); } + }; + + // ========================================================================= + // HeatmapLayer + // ========================================================================= + + /// Wraps an mbgl::style::HeatmapLayer. + public ref class HeatmapLayer : public Layer + { + internal: + HeatmapLayer(mbgl::style::HeatmapLayer* layer); + + private: + mbgl::style::HeatmapLayer* Impl(); + + public: + /// Global opacity of the heatmap (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + + /// Radius of influence of one heatmap point in pixels. + property float Radius + { + float get(); + System::Void set(float); + } + + /// Intensity multiplier that controls the rate of increase. + property float Intensity + { + float get(); + System::Void set(float); + } + + /// Contribution of each individual point to the heatmap. + property float Weight + { + float get(); + System::Void set(float); + } + + /// + /// Color ramp as a MapLibre expression JSON string that maps heatmap density (0–1) to a color. + /// Returns empty string when unset (uses the default rainbow ramp). + /// Throws ArgumentException on invalid expression JSON. + /// + property System::String^ Color + { + System::String^ get(); + System::Void set(System::String^); + } + }; + + // ========================================================================= + // HillshadeLayer + // ========================================================================= + + /// Wraps an mbgl::style::HillshadeLayer. + public ref class HillshadeLayer : public Layer + { + internal: + HillshadeLayer(mbgl::style::HillshadeLayer* layer); + + private: + mbgl::style::HillshadeLayer* Impl(); + + public: + /// Intensity of the hillshade (0.0 – 1.0). + property float Exaggeration + { + float get(); + System::Void set(float); + } + + /// Direction of the light source as an azimuth (0 – 360 degrees). + property float IlluminationDirection + { + float get(); + System::Void set(float); + } + + /// Shadow color as a CSS hex string. + property System::String^ ShadowColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Highlight color as a CSS hex string. + property System::String^ HighlightColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Accent color for vert faces as a CSS hex string. + property System::String^ AccentColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// + /// Frame of reference for the illumination direction: Map keeps the + /// light direction fixed as the map rotates; Viewport keeps it fixed + /// relative to the viewport. + /// + property HillshadeIlluminationAnchorType IlluminationAnchor + { + HillshadeIlluminationAnchorType get(); + System::Void set(HillshadeIlluminationAnchorType); + } + }; + + // ========================================================================= + // FillExtrusionLayer + // ========================================================================= + + /// Wraps an mbgl::style::FillExtrusionLayer. + public ref class FillExtrusionLayer : public Layer + { + internal: + FillExtrusionLayer(mbgl::style::FillExtrusionLayer* layer); + + private: + mbgl::style::FillExtrusionLayer* Impl(); + + public: + /// Extrusion color as a CSS hex string. + property System::String^ Color + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Extrusion opacity (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + + /// Height of the extrusion in meters. + property float Height + { + float get(); + System::Void set(float); + } + + /// Base of the extrusion in meters (for raising the bottom). + property float Base + { + float get(); + System::Void set(float); + } + + /// Whether to apply a gradient effect along the sides of the extrusion. + property bool VerticalGradient + { + bool get(); + System::Void set(bool); + } + + /// Sprite image name for a repeating fill-extrusion pattern. Empty string = solid color. + property System::String^ Pattern { System::String^ get(); System::Void set(System::String^); } + + /// X/Y translation of the fill extrusion in pixels as [x, y]. + property cli::array^ Translate { cli::array^ get(); System::Void set(cli::array^); } + + /// Frame of reference for Translate: Map or Viewport. + property TranslateAnchorType TranslateAnchor { TranslateAnchorType get(); System::Void set(TranslateAnchorType); } + }; + + // ========================================================================= + // ColorReliefLayer + // ========================================================================= + + /// Wraps an mbgl::style::ColorReliefLayer. + public ref class ColorReliefLayer : public Layer + { + internal: + ColorReliefLayer(mbgl::style::ColorReliefLayer* layer); + + private: + mbgl::style::ColorReliefLayer* Impl(); + + public: + /// Opacity of the color-relief layer (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + + /// + /// Color ramp as a MapLibre expression JSON string that maps elevation + /// (raster-value) to a color. Example: + /// ["interpolate",["linear"],["raster-value"],0,"#000080",3000,"#ffffff"] + /// Returns an empty string when unset. + /// Throws ArgumentException on invalid expression JSON. + /// + property System::String^ ColorRamp + { + System::String^ get(); + System::Void set(System::String^); + } + }; + + // ========================================================================= + // LocationIndicatorLayer + // ========================================================================= + + /// + /// Wraps an mbgl::style::LocationIndicatorLayer (the "blue dot" user-location + /// indicator layer). No source is required. + /// + public ref class LocationIndicatorLayer : public Layer + { + internal: + LocationIndicatorLayer(mbgl::style::LocationIndicatorLayer* layer); + + private: + mbgl::style::LocationIndicatorLayer* Impl(); + + public: + // ---- Layout properties ------------------------------------------ + + /// Name of the image to use as the bearing arrow (sprite id). + property System::String^ BearingImage + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Name of the image to use as the shadow beneath the indicator (sprite id). + property System::String^ ShadowImage + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Name of the image to use as the top of the location indicator (sprite id). + property System::String^ TopImage + { + System::String^ get(); + System::Void set(System::String^); + } + + // ---- Paint properties ------------------------------------------- + + /// Radius of the accuracy circle in metres. + property float AccuracyRadius + { + float get(); + System::Void set(float); + } + + /// Border color of the accuracy circle. + property System::String^ AccuracyRadiusBorderColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Fill color of the accuracy circle. + property System::String^ AccuracyRadiusColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Bearing of the location indicator in degrees (0 = north, clockwise). + property float Bearing + { + float get(); + System::Void set(float); + } + + /// Scale factor of the BearingImage (default 1.0). + property float BearingImageSize + { + float get(); + System::Void set(float); + } + + /// Displacement of the top image along the tilt in pixels (default 0). + property float ImageTiltDisplacement + { + float get(); + System::Void set(float); + } + + /// + /// Geographic position of the indicator as [latitude, longitude, altitude]. + /// Returns an array of three doubles. + /// + property cli::array^ Location + { + cli::array^ get(); + System::Void set(cli::array^); + } + + /// Perspective compensation factor (0 = no compensation, 1 = full; default 0.85). + property float PerspectiveCompensation + { + float get(); + System::Void set(float); + } + + /// Scale factor of the ShadowImage (default 1.0). + property float ShadowImageSize + { + float get(); + System::Void set(float); + } + + /// Scale factor of the TopImage (default 1.0). + property float TopImageSize + { + float get(); + System::Void set(float); + } + }; +} diff --git a/include/Light.h b/include/Light.h new file mode 100644 index 0000000..861964e --- /dev/null +++ b/include/Light.h @@ -0,0 +1,82 @@ +#pragma once +#include "NativeWrapper.h" + +namespace mbgl { +namespace style { + class Light; +} +} + +namespace DOTNET_NAMESPACE +{ + /// + /// Specifies the anchor reference for the light source. + /// + public enum class LightAnchor : System::Byte + { + /// The light is fixed relative to the map. + Map, + /// The light rotates with the viewport. + Viewport + }; + + /// + /// Spherical position of the light source. + /// Coordinates are [radial, azimuthal, polar] where: + /// radial = distance from the center (0.0–infinity) + /// azimuthal = azimuthal angle in degrees (0–360) + /// polar = polar angle in degrees (0–90) + /// + public value class LightPosition + { + public: + float Radial; + float Azimuthal; + float Polar; + + LightPosition(float radial, float azimuthal, float polar) + : Radial(radial), Azimuthal(azimuthal), Polar(polar) {} + }; + + /// + /// Wraps an mbgl::style::Light object (non-owning pointer). + /// Obtain an instance via . + /// + public ref class Light + { + public: + /// Gets or sets the anchor type. + property LightAnchor Anchor + { + LightAnchor get(); + System::Void set(LightAnchor value); + } + + /// Gets or sets the light color as a CSS string (e.g. "#ffffff"). + property System::String^ Color + { + System::String^ get(); + System::Void set(System::String^ value); + } + + /// Gets or sets the intensity in [0, 1]. + property System::Single Intensity + { + System::Single get(); + System::Void set(System::Single value); + } + + /// Gets or sets the spherical light position. + property LightPosition Position + { + LightPosition get(); + System::Void set(LightPosition value); + } + + internal: + Light(mbgl::style::Light* light); + + private: + mbgl::style::Light* _light; + }; +} diff --git a/include/Map.h b/include/Map.h index bff89fb..f683c52 100644 --- a/include/Map.h +++ b/include/Map.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Enums.h" #include "NativeWrapper.h" #include "Point.h" @@ -35,7 +35,7 @@ namespace DOTNET_NAMESPACE using ProjectionMode_ = ProjectionMode; using Style_ = Style; using TransformState_ = TransformState; - + public enum class MapDebugOptions : System::UInt32 { NoDebug = 0, @@ -47,7 +47,7 @@ namespace DOTNET_NAMESPACE StencilClip = 1 << 6, DepthBuffer = 1 << 7, }; - + public ref class Map : NativeWrapper { public: @@ -87,49 +87,51 @@ namespace DOTNET_NAMESPACE System::Void PitchBy(System::Double pitch, AnimationOptions^ animation); System::Void RotateBy(ScreenCoordinate first, ScreenCoordinate second); System::Void RotateBy(ScreenCoordinate first, ScreenCoordinate second, AnimationOptions^ animation); - + CameraOptions^ CameraForLatLngBounds( LatLngBounds^ bounds, EdgeInsets^ padding ); - + CameraOptions^ CameraForLatLngBounds( LatLngBounds^ bounds, EdgeInsets^ padding, System::Nullable bearing ); - + CameraOptions^ CameraForLatLngBounds( LatLngBounds^ bounds, EdgeInsets^ padding, System::Nullable bearing, System::Nullable pitch ); - + CameraOptions^ CameraForLatLngs( System::Collections::Generic::IEnumerable^ latLngs, EdgeInsets^ padding ); - + CameraOptions^ CameraForLatLngs( System::Collections::Generic::IEnumerable^ latLngs, EdgeInsets^ padding, System::Nullable bearing ); - + CameraOptions^ CameraForLatLngs( System::Collections::Generic::IEnumerable^ latLngs, EdgeInsets^ padding, System::Nullable bearing, System::Nullable pitch ); - + LatLngBounds^ LatLngBoundsForCamera(CameraOptions^ camera); LatLngBounds^ LatLngBoundsForCameraUnwrapped(CameraOptions^ camera); System::Void SetNorthOrientation(NorthOrientation orientation); System::Void SetConstrainMode(ConstrainMode mode); System::Void SetViewportMode(ViewportMode mode); System::Void SetSize(Size^ size); + System::Void SetFrustumOffset(EdgeInsets^ offset); + EdgeInsets^ GetFrustumOffset(); ScreenCoordinate PixelForLatLng(LatLng^ latLng); LatLng^ LatLngForPixel(ScreenCoordinate pixel); System::Collections::Generic::IEnumerable^ PixelsForLatLngs(System::Collections::Generic::IEnumerable^ latLngs); @@ -159,8 +161,20 @@ namespace DOTNET_NAMESPACE /// property System::Byte PrefetchZoomDelta { System::Byte get(); System::Void set(System::Byte value); } property MapDebugOptions Debug { MapDebugOptions get(); System::Void set(MapDebugOptions value); } + property System::Boolean IsRenderingStatsViewEnabled { System::Boolean get(); System::Void set(System::Boolean value); } property System::Boolean IsFullyLoaded { System::Boolean get(); } - + + /// + /// The number of map tile requests can be reduced by using a lower level of details away from + /// the camera. This can improve performance, particularly when the camera pitch is high. + /// See the maplibre-native documentation for details on the LOD heuristic parameters. + /// + property System::Double TileLodMinRadius { System::Double get(); System::Void set(System::Double value); } + property System::Double TileLodScale { System::Double get(); System::Void set(System::Double value); } + property System::Double TileLodPitchThreshold { System::Double get(); System::Void set(System::Double value); } + property System::Double TileLodZoomShift { System::Double get(); System::Void set(System::Double value); } + property TileLodMode TileLodMode_ { TileLodMode get(); System::Void set(TileLodMode value); } + /// /// FreeCameraOptions provides more direct access to the underlying camera /// entity. For backwards compatibility the state set using this API must be diff --git a/include/MapLibreLogger.h b/include/MapLibreLogger.h new file mode 100644 index 0000000..4b747a4 --- /dev/null +++ b/include/MapLibreLogger.h @@ -0,0 +1,41 @@ +#pragma once + +namespace DOTNET_NAMESPACE +{ + public enum class LogEventSeverity : System::Byte + { + Debug = 0, + Info, + Warning, + Error, + }; + + public enum class LogEvent : System::Byte + { + General = 0, + Setup, + Shader, + ParseStyle, + ParseTile, + Render, + Style, + Database, + HttpRequest, + Sprite, + Image, + OpenGL, + JNI, + Android, + Crash, + Glyph, + Timing, + }; + + public delegate void MapLibreLogHandler(LogEventSeverity severity, LogEvent eventType, System::Int64 code, System::String^ message); + + public ref class MapLibreLogger + { + public: + static void SetObserver(MapLibreLogHandler^ handler); + }; +} diff --git a/include/MapObserver.h b/include/MapObserver.h index eb4fb75..6d16f9a 100644 --- a/include/MapObserver.h +++ b/include/MapObserver.h @@ -1,7 +1,10 @@ -#pragma once +#pragma once #include "Convert.h" +#include "Enums.h" #include "NativeWrapper.h" #include +#include +#include #include #include @@ -58,11 +61,10 @@ namespace DOTNET_NAMESPACE delegate System::Void WillStartRenderingMapHandler(); delegate System::Void DidFinishRenderingMapHandler(RenderMode mode); delegate System::Void DidFinishLoadingStyleHandler(); - // TODO: implement the managed version - //delegate System::Void SourceChangedHandler(Source^ source); + delegate System::Void SourceChangedHandler(System::String^ id, SourceType type); delegate System::Void DidBecomeIdleHandler(); delegate System::Void StyleImageMissingHandler(System::String^ id); - + /// /// This event handler should return true if unused image can be removed, /// false otherwise. By default, unused image will be removed. @@ -70,6 +72,13 @@ namespace DOTNET_NAMESPACE delegate System::Boolean CanRemoveUnusedStyleImageHandler(System::String^ id); delegate System::Void RegisterShadersHandler(ShaderRegistry^ shaderRegistry); + /// Fired when a shader begins compiling. + delegate System::Void PreCompileShaderHandler(System::UInt32 shaderType, System::Byte backendType, System::String^ defines); + /// Fired when a shader finishes compiling. + delegate System::Void PostCompileShaderHandler(System::UInt32 shaderType, System::Byte backendType, System::String^ defines); + /// Fired when a shader fails to compile. + delegate System::Void ShaderCompileFailedHandler(System::UInt32 shaderType, System::Byte backendType, System::String^ defines); + event CameraWillChangeHandler^ CameraWillChange; event CameraIsChangingHandler^ CameraIsChanging; event CameraDidChangeHandler^ CameraDidChange; @@ -81,8 +90,7 @@ namespace DOTNET_NAMESPACE event WillStartRenderingMapHandler^ WillStartRenderingMap; event DidFinishRenderingMapHandler^ DidFinishRenderingMap; event DidFinishLoadingStyleHandler^ DidFinishLoadingStyle; - // TODO: implement the managed version - //event SourceChangedHandler^ SourceChanged; + event SourceChangedHandler^ SourceChanged; event DidBecomeIdleHandler^ DidBecomeIdle; event StyleImageMissingHandler^ StyleImageMissing; event CanRemoveUnusedStyleImageHandler^ CanRemoveUnusedStyleImage; @@ -93,6 +101,13 @@ namespace DOTNET_NAMESPACE /// event RegisterShadersHandler^ RegisterShaders; + /// Fired when a shader begins compiling. + event PreCompileShaderHandler^ PreCompileShader; + /// Fired when a shader finishes compiling successfully. + event PostCompileShaderHandler^ PostCompileShader; + /// Fired when a shader fails to compile. + event ShaderCompileFailedHandler^ ShaderCompileFailed; + MapObserver(); ~MapObserver(); @@ -108,7 +123,7 @@ namespace DOTNET_NAMESPACE virtual System::Void onWillStartRenderingMap(); virtual System::Void onDidFinishRenderingMap(RenderMode mode); virtual System::Void onDidFinishLoadingStyle(); - //virtual System::Void onSourceChanged(Source^ source); + virtual System::Void onSourceChanged(System::String^ id, SourceType type); virtual System::Void onDidBecomeIdle(); virtual System::Void onStyleImageMissing(System::String^ id); @@ -120,6 +135,9 @@ namespace DOTNET_NAMESPACE /// virtual System::Boolean onCanRemoveUnusedStyleImage(System::String^ id); virtual System::Void onRegisterShaders(ShaderRegistry^ shaderRegistry); + virtual System::Void onPreCompileShader(System::UInt32 shaderType, System::Byte backendType, System::String^ defines); + virtual System::Void onPostCompileShader(System::UInt32 shaderType, System::Byte backendType, System::String^ defines); + virtual System::Void onShaderCompileFailed(System::UInt32 shaderType, System::Byte backendType, System::String^ defines); }; class NativeMapObserver : public mbgl::MapObserver @@ -135,7 +153,7 @@ namespace DOTNET_NAMESPACE void onDidFinishLoadingMap() override; void onDidFailLoadingMap(mbgl::MapLoadError type, const std::string& description) override; void onWillStartRenderingFrame() override; - void onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus status) override; + void onDidFinishRenderingFrame(const mbgl::MapObserver::RenderFrameStatus& status) override; void onWillStartRenderingMap() override; void onDidFinishRenderingMap(mbgl::MapObserver::RenderMode mode) override; void onDidFinishLoadingStyle() override; @@ -144,6 +162,9 @@ namespace DOTNET_NAMESPACE void onStyleImageMissing(const std::string& id) override; bool onCanRemoveUnusedStyleImage(const std::string& id) override; void onRegisterShaders(mbgl::gfx::ShaderRegistry& shaderRegistry) override; + void onPreCompileShader(mbgl::shaders::BuiltIn, mbgl::gfx::Backend::Type, const std::string&) override; + void onPostCompileShader(mbgl::shaders::BuiltIn, mbgl::gfx::Backend::Type, const std::string&) override; + void onShaderCompileFailed(mbgl::shaders::BuiltIn, mbgl::gfx::Backend::Type, const std::string&) override; msclr::gcroot CameraWillChangeHandler; msclr::gcroot CameraIsChangingHandler; @@ -156,10 +177,13 @@ namespace DOTNET_NAMESPACE msclr::gcroot WillStartRenderingMapHandler; msclr::gcroot DidFinishRenderingMapHandler; msclr::gcroot DidFinishLoadingStyleHandler; - //msclr::gcroot SourceChangedHandler; + msclr::gcroot SourceChangedHandler; msclr::gcroot DidBecomeIdleHandler; msclr::gcroot StyleImageMissingHandler; msclr::gcroot CanRemoveUnusedStyleImageHandler; msclr::gcroot RegisterShadersHandler; + msclr::gcroot PreCompileShaderHandler; + msclr::gcroot PostCompileShaderHandler; + msclr::gcroot ShaderCompileFailedHandler; }; } diff --git a/include/NativePointerHolder.h b/include/NativePointerHolder.h index 61c3641..e3594da 100644 --- a/include/NativePointerHolder.h +++ b/include/NativePointerHolder.h @@ -32,7 +32,7 @@ namespace DOTNET_NAMESPACE { } - NativePointerHolder(T* nativePointer) : NativePointer(nativePointer, true) + NativePointerHolder(T* nativePointer) : NativePointerHolder(nativePointer, true) { } diff --git a/include/OverscaledTileID.h b/include/OverscaledTileID.h index 4d5c7ef..d2e98a7 100644 --- a/include/OverscaledTileID.h +++ b/include/OverscaledTileID.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace mbgl @@ -10,7 +10,7 @@ namespace DOTNET_NAMESPACE { ref class CanonicalTileID; ref class UnwrappedTileID; - + /// /// Has integer z/x/y coordinates /// overscaledZ describes the zoom level this tile is intented to represent, e.g. diff --git a/include/Point.h b/include/Point.h index 6f530ec..b163403 100644 --- a/include/Point.h +++ b/include/Point.h @@ -11,7 +11,7 @@ namespace DOTNET_NAMESPACE Y = y; } - PointDouble(mbgl::Point& point) + PointDouble(const mbgl::Point& point) { X = point.x; Y = point.y; @@ -130,7 +130,7 @@ namespace DOTNET_NAMESPACE Y = y; } - PointShort(mbgl::Point& point) + PointShort(const mbgl::Point& point) { X = point.x; Y = point.y; diff --git a/include/PremultipliedImage.h b/include/PremultipliedImage.h index c2fa5dc..ec1667d 100644 --- a/include/PremultipliedImage.h +++ b/include/PremultipliedImage.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Image.h" namespace DOTNET_NAMESPACE @@ -39,7 +39,7 @@ namespace DOTNET_NAMESPACE /// move data within a single Image. /// static System::Void Copy(PremultipliedImage^ srcImg, PremultipliedImage^ dstImg, PointUInt srcPt, PointUInt dstPt, Size_^ size); - + static PremultipliedImage^ DecodeImage(System::String^ data); static cli::array^ EncodePNG(PremultipliedImage^ image); internal: diff --git a/include/Projection.h b/include/Projection.h index 0e4e362..6727586 100644 --- a/include/Projection.h +++ b/include/Projection.h @@ -1,49 +1,49 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include "LatLng.h" #include "Point.h" namespace mbgl { - class ProjectedMeters; + class ProjectedMeters; } namespace DOTNET_NAMESPACE { - ref class ProjectedMeters; - - public ref class Projection abstract sealed - { - public: - /// - /// Map pixel width at given scale. - /// - /// - /// - static System::Double WorldSize(System::Double scale); + ref class ProjectedMeters; - static System::Double GetMetersPerPixelAtLatitude(System::Double lat, System::Double zoom); - static ProjectedMeters^ ProjectedMetersForLatLng(LatLng^ latLng); - static LatLng^ LatLngForProjectedMeters(ProjectedMeters^ projectedMeters); - static PointDouble Project(LatLng^ latLng, System::Double scale); - static PointDouble Project(LatLng^ latLng, System::Int32 zoom); - static LatLng^ Unproject(PointDouble p, System::Double scale); - static LatLng^ Unproject(PointDouble p, System::Double scale, LatLng::WrapMode wrapMode); - }; + public ref class Projection abstract sealed + { + public: + /// + /// Map pixel width at given scale. + /// + /// + /// + static System::Double WorldSize(System::Double scale); - public ref class ProjectedMeters : NativeWrapper - { - public: - ProjectedMeters(System::Double northing); - ProjectedMeters(System::Double northing, System::Double easting); - ~ProjectedMeters(); + static System::Double GetMetersPerPixelAtLatitude(System::Double lat, System::Double zoom); + static ProjectedMeters^ ProjectedMetersForLatLng(LatLng^ latLng); + static LatLng^ LatLngForProjectedMeters(ProjectedMeters^ projectedMeters); + static PointDouble Project(LatLng^ latLng, System::Double scale); + static PointDouble Project(LatLng^ latLng, System::Int32 zoom); + static LatLng^ Unproject(PointDouble p, System::Double scale); + static LatLng^ Unproject(PointDouble p, System::Double scale, LatLng::WrapMode wrapMode); + }; - property System::Double Northing { System::Double get(); } - property System::Double Easting { System::Double get(); } + public ref class ProjectedMeters : NativeWrapper + { + public: + ProjectedMeters(System::Double northing); + ProjectedMeters(System::Double northing, System::Double easting); + ~ProjectedMeters(); - static System::Boolean operator==(ProjectedMeters^ a, ProjectedMeters^ b); - static System::Boolean operator!=(ProjectedMeters^ a, ProjectedMeters^ b); - internal: - ProjectedMeters(NativePointerHolder^ nativePointerHolder); - }; -} \ No newline at end of file + property System::Double Northing { System::Double get(); } + property System::Double Easting { System::Double get(); } + + static System::Boolean operator==(ProjectedMeters^ a, ProjectedMeters^ b); + static System::Boolean operator!=(ProjectedMeters^ a, ProjectedMeters^ b); + internal: + ProjectedMeters(NativePointerHolder^ nativePointerHolder); + }; +} diff --git a/include/Renderer.h b/include/Renderer.h index 6bb1c12..25f9b5f 100644 --- a/include/Renderer.h +++ b/include/Renderer.h @@ -23,6 +23,22 @@ namespace DOTNET_NAMESPACE System::Void DumpDebugLogs(); System::Void ReduceMemoryUse(); System::Void ClearData(); + + /// + /// Query rendered features at a screen point. Returns a GeoJSON FeatureCollection JSON string. + /// Pass null or an empty array for layerIds to query all layers. + /// + System::String^ QueryRenderedFeaturesAtPoint( + double x, double y, + System::Collections::Generic::IEnumerable^ layerIds); + + /// + /// Query rendered features within a screen bounding box. Returns a GeoJSON FeatureCollection JSON string. + /// Pass null or an empty array for layerIds to query all layers. + /// + System::String^ QueryRenderedFeaturesInBox( + double x1, double y1, double x2, double y2, + System::Collections::Generic::IEnumerable^ layerIds); internal: Renderer(NativePointerHolder^ nativePointerHolder); }; diff --git a/include/RendererBackend.h b/include/RendererBackend.h index a423915..0c62c56 100644 --- a/include/RendererBackend.h +++ b/include/RendererBackend.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace mbgl @@ -12,7 +12,7 @@ namespace mbgl namespace DOTNET_NAMESPACE { ref class RendererBackend; - + public interface class IRendererBackend { System::IntPtr GetNativePointer(); diff --git a/include/RenderingStats.h b/include/RenderingStats.h index 998c258..a7b778a 100644 --- a/include/RenderingStats.h +++ b/include/RenderingStats.h @@ -18,14 +18,47 @@ namespace DOTNET_NAMESPACE ~RenderingStats(); property System::Boolean Zero { System::Boolean get(); } + + // --- Frame timing --- + property System::Double EncodingTime { System::Double get(); } + property System::Double RenderingTime { System::Double get(); } + property System::Int32 NumFrames { System::Int32 get(); } + + // --- Draw calls --- property System::Int32 NumDrawCalls { System::Int32 get(); System::Void set(System::Int32 value); } + property System::Int32 TotalDrawCalls { System::Int32 get(); } + + // --- Textures --- property System::Int32 NumActiveTextures { System::Int32 get(); System::Void set(System::Int32 value); } property System::Int32 NumCreatedTextures { System::Int32 get(); System::Void set(System::Int32 value); } + property System::Int32 NumTextureBindings { System::Int32 get(); } + property System::Int32 NumTextureUpdates { System::Int32 get(); } + property System::Int64 TextureUpdateBytes { System::Int64 get(); } + + // --- Buffers --- property System::Int32 NumBuffers { System::Int32 get(); System::Void set(System::Int32 value); } property System::Int32 NumFrameBuffers { System::Int32 get(); System::Void set(System::Int32 value); } + property System::Int32 NumIndexBuffers { System::Int32 get(); } + property System::Int64 IndexUpdateBytes { System::Int64 get(); } + property System::Int32 NumVertexBuffers { System::Int32 get(); } + property System::Int64 VertexUpdateBytes { System::Int64 get(); } + property System::Int32 NumUniformBuffers { System::Int32 get(); } + property System::Int32 NumUniformUpdates { System::Int32 get(); } + property System::Int64 UniformUpdateBytes { System::Int64 get(); } + property System::Int64 TotalBuffers { System::Int64 get(); } + property System::Int64 BufferUpdates { System::Int64 get(); } + property System::Int64 BufferUpdateBytes { System::Int64 get(); } + + // --- Memory --- property System::Int32 MemTextures { System::Int32 get(); System::Void set(System::Int32 value); } + property System::Int32 MemBuffers { System::Int32 get(); } property System::Int32 MemIndexBuffers { System::Int32 get(); System::Void set(System::Int32 value); } property System::Int32 MemVertexBuffers { System::Int32 get(); System::Void set(System::Int32 value); } + property System::Int32 MemUniformBuffers { System::Int32 get(); } + + // --- Stencil --- + property System::Int32 StencilClears { System::Int32 get(); } + property System::Int32 StencilUpdates { System::Int32 get(); } internal: RenderingStats(NativePointerHolder^ nativePointerHolder); }; diff --git a/include/Resource.h b/include/Resource.h index 0141abe..23312ae 100644 --- a/include/Resource.h +++ b/include/Resource.h @@ -1,11 +1,11 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include namespace DOTNET_NAMESPACE { using FontStack = System::Collections::Generic::IEnumerable; - + public ref class Resource : NativeWrapper { public: @@ -69,7 +69,7 @@ namespace DOTNET_NAMESPACE static Resource^ Style(System::String^ url); static Resource^ Source(System::String^ url); - + static Resource^ Tile( System::String^ urlTemplate, System::Single pixelRatio, diff --git a/include/ResourceOptions.h b/include/ResourceOptions.h index 49e599e..98ff07b 100644 --- a/include/ResourceOptions.h +++ b/include/ResourceOptions.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace mbgl @@ -29,7 +29,7 @@ namespace DOTNET_NAMESPACE /// Mapbox access token. /// ResourceOptions for chaining options together. ResourceOptions^ WithApiKey(System::String^ token); - + // TODO: implement the managed version //ResourceOptions^ WithTileServerOptions(TileServerOptions tileServerOptions); @@ -71,7 +71,7 @@ namespace DOTNET_NAMESPACE /// Gets the previously set (or default) Mapbox access token. /// property System::String^ ApiKey { System::String^ get(); } - + // TODO: implement the managed version //property TileServerOptions TileServerOptions { TileServerOptions get(); } diff --git a/include/ResourceTransform.h b/include/ResourceTransform.h index 042a135..6094bb3 100644 --- a/include/ResourceTransform.h +++ b/include/ResourceTransform.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include "Resource.h" #include @@ -10,7 +10,7 @@ namespace DOTNET_NAMESPACE { public delegate System::Void ResourceTransformFinishedCallback(System::String^ url); public delegate System::Void ResourceTransformCallback(Resource::ResourceKind kind, System::String^ url, ResourceTransformFinishedCallback^ finishedCallback); - + class ResourceTransformNativeCallbackHelper; public ref class ResourceTransform : NativeWrapper @@ -36,7 +36,7 @@ namespace DOTNET_NAMESPACE ~ResourceTransformNativeCallbackHelper(); void NativeTransformCallbackHandler(mbgl::Resource::Kind kind, const std::string& url, mbgl::ResourceTransform::FinishedCallback callback); - + mbgl::ResourceTransform::FinishedCallback FinishedCallback; private: msclr::gcroot _ResourceTransform; diff --git a/include/Size.h b/include/Size.h index fd4567f..38b9707 100644 --- a/include/Size.h +++ b/include/Size.h @@ -8,20 +8,32 @@ namespace mbgl namespace DOTNET_NAMESPACE { + /// Represents a width and height pair, in pixels. public ref class Size : NativeWrapper { public: + /// Initializes a new with zero width and height. Size(); + /// Initializes a new with the specified width and height. + /// Width in pixels. + /// Height in pixels. Size(System::UInt32 width, System::UInt32 height); ~Size(); + /// Gets or sets the width in pixels. property System::UInt32 Width { System::UInt32 get(); System::Void set(System::UInt32 value); } + /// Gets or sets the height in pixels. property System::UInt32 Height { System::UInt32 get(); System::Void set(System::UInt32 value); } + /// Gets the area (Width * Height) in square pixels. property System::UInt32 Area { System::UInt32 get(); } + /// Gets the aspect ratio (Width / Height). Returns 0 if Height is zero. property System::Single AspectRatio { System::Single get(); } + /// Gets whether either dimension is zero. property System::Boolean IsEmpty { System::Boolean get(); } + /// Returns if both dimensions are equal. static System::Boolean operator==(Size^ a, Size^ b); + /// Returns if either dimension differs. static System::Boolean operator!=(Size^ a, Size^ b); internal: Size(NativePointerHolder^ nativePointerHolder); diff --git a/include/Source.h b/include/Source.h new file mode 100644 index 0000000..3309e55 --- /dev/null +++ b/include/Source.h @@ -0,0 +1,64 @@ +#pragma once + +namespace mbgl { +namespace style { +class Source; +} // namespace style +} // namespace mbgl + +namespace DOTNET_NAMESPACE +{ + /// + /// Non-owning wrapper for an mbgl::style::Source. + /// Lifetime is tied to the Style that owns the native source — do not hold + /// references after the Style is disposed. + /// + public ref class Source + { + internal: + mbgl::style::Source* _source; // raw, non-owning + Source(mbgl::style::Source* source); + + public: + /// Unique identifier for the source. + property System::String^ Id { System::String^ get(); } + + /// + /// Source type string as reported by the native library + /// (e.g. "geojson", "vector", "raster", "raster-dem", "image"). + /// + property System::String^ Type { System::String^ get(); } + + /// Optional attribution text for this source. + property System::String^ Attribution { System::String^ get(); } + + /// + /// Whether the source's data is volatile (can be evicted from the cache). + /// + property bool IsVolatile + { + bool get(); + System::Void set(bool); + } + + /// + /// Number of zoom levels to prefetch when requesting tiles. + /// Pass a negative value to clear the override and use the global setting. + /// + property System::Int32 PrefetchZoomDelta + { + System::Int32 get(); + System::Void set(System::Int32); + } + + /// + /// Maximum overscale factor for parent tiles used when a tile is not yet + /// available. Pass a negative value to clear the override. + /// + property System::Int32 MaxOverscaleFactorForParentTiles + { + System::Int32 get(); + System::Void set(System::Int32); + } + }; +} diff --git a/include/Sources.h b/include/Sources.h new file mode 100644 index 0000000..1795003 --- /dev/null +++ b/include/Sources.h @@ -0,0 +1,198 @@ +#pragma once +#include "Source.h" +#include "LatLng.h" +#include "PremultipliedImage.h" + +namespace DOTNET_NAMESPACE +{ + /// + /// Options that control tiling and clustering behaviour of a GeoJSONSource. + /// All options must be specified at construction time via + /// Style::AddGeoJsonSource(sourceId, options). + /// + public ref class GeoJSONOptions + { + public: + /// Minimum zoom at which tiles are generated (default 0). + property System::Byte MinZoom; + /// Maximum zoom at which tiles are generated (default 18). + property System::Byte MaxZoom; + /// Tile size in pixels (default 512). + property System::UInt16 TileSize; + /// Tile buffer size in pixels (default 128). + property System::UInt16 Buffer; + /// Douglas-Peucker simplification tolerance (default 0.375). + property System::Double Tolerance; + /// Whether to calculate line distance metrics (default false). + property System::Boolean LineMetrics; + + /// Whether to cluster point features (default false). + property System::Boolean Cluster; + /// Cluster radius in pixels (default 50). + property System::UInt16 ClusterRadius; + /// Maximum zoom at which clusters are generated (default 17). + property System::Byte ClusterMaxZoom; + + GeoJSONOptions() + { + MinZoom = 0; + MaxZoom = 18; + TileSize = 512; + Buffer = 128; + Tolerance = 0.375; + LineMetrics = false; + Cluster = false; + ClusterRadius = 50; + ClusterMaxZoom = 17; + } + }; +} + +// Forward-declare native source types +namespace mbgl { +namespace style { +class GeoJSONSource; +class VectorSource; +class RasterSource; +class RasterDEMSource; +class ImageSource; +} // namespace style +} // namespace mbgl + +namespace DOTNET_NAMESPACE +{ + // ========================================================================= + // GeoJSONSource + // ========================================================================= + + /// Wraps an mbgl::style::GeoJSONSource. + public ref class GeoJSONSource : public Source + { + internal: + GeoJSONSource(mbgl::style::GeoJSONSource* source); + + private: + mbgl::style::GeoJSONSource* Impl(); + + public: + /// + /// Update the source to load data from the specified URL. + /// The URL may be a remote https:// URL or a local file:// URL. + /// + /// URL pointing to a GeoJSON document. + System::Void SetUrl(System::String^ url); + + /// + /// Update the source with inline GeoJSON data provided as a string. + /// + /// A valid GeoJSON string. + System::Void SetData(System::String^ geoJson); + }; + + // ========================================================================= + // VectorSource + // ========================================================================= + + /// Wraps an mbgl::style::VectorSource. + public ref class VectorSource : public Source + { + internal: + VectorSource(mbgl::style::VectorSource* source); + + private: + mbgl::style::VectorSource* Impl(); + + public: + /// + /// The TileJSON or Mapbox-style URL for the vector tile set, + /// or empty string if the source was constructed from an inline Tileset. + /// + property System::String^ Url { System::String^ get(); } + }; + + // ========================================================================= + // RasterSource + // ========================================================================= + + /// Wraps an mbgl::style::RasterSource. + public ref class RasterSource : public Source + { + internal: + RasterSource(mbgl::style::RasterSource* source); + + private: + mbgl::style::RasterSource* Impl(); + + public: + /// + /// The TileJSON or Mapbox-style URL for the raster tile set, + /// or empty string if the source was constructed from an inline Tileset. + /// + property System::String^ Url { System::String^ get(); } + + /// Tile size in pixels (default 512). + property System::UInt16 TileSize { System::UInt16 get(); } + }; + + // ========================================================================= + // RasterDEMSource + // ========================================================================= + + /// Wraps an mbgl::style::RasterDEMSource (elevation / terrain). + public ref class RasterDEMSource : public Source + { + internal: + RasterDEMSource(mbgl::style::RasterDEMSource* source); + + private: + mbgl::style::RasterDEMSource* Impl(); + + public: + /// + /// The TileJSON URL for the raster-DEM tile set, + /// or empty string if the source was constructed from an inline Tileset. + /// + property System::String^ Url { System::String^ get(); } + + /// Tile size in pixels (default 512). + property System::UInt16 TileSize { System::UInt16 get(); } + }; + + // ========================================================================= + // ImageSource + // ========================================================================= + + /// Wraps an mbgl::style::ImageSource. + public ref class ImageSource : public Source + { + internal: + ImageSource(mbgl::style::ImageSource* source); + + private: + mbgl::style::ImageSource* Impl(); + + public: + /// URL of the image to display. + property System::String^ Url + { + System::String^ get(); + System::Void set(System::String^); + } + + /// + /// The four geographic corners of the image, in order: + /// top-left, top-right, bottom-right, bottom-left. + /// + property array^ Coordinates + { + array^ get(); + System::Void set(array^); + } + + /// + /// Update the pixel data of the image source at runtime. + /// The image is cloned internally; the caller retains ownership of . + /// + System::Void SetImage(PremultipliedImage^ image); + }; +} diff --git a/include/Style.h b/include/Style.h index 0cdbca6..aff140d 100644 --- a/include/Style.h +++ b/include/Style.h @@ -1,5 +1,13 @@ #pragma once #include "NativeWrapper.h" +#include "Layer.h" +#include "Layers.h" +#include "Source.h" +#include "Sources.h" +#include "LatLng.h" +#include "Light.h" +#include "StyleImage.h" +#include "TransitionOptions.h" namespace mbgl { @@ -13,7 +21,7 @@ namespace DOTNET_NAMESPACE { ref class CameraOptions; ref class FileSource; - ref class TransitionOptions; + ref class PremultipliedImage; public ref class Style : NativeWrapper { @@ -27,40 +35,171 @@ namespace DOTNET_NAMESPACE System::String^ GetURL(); System::String^ GetName(); - /* - CameraOptions^ GetDefaultCamera(); + // ------------------------------------------------------------------------- + // Layer retrieval + // ------------------------------------------------------------------------- + + /// + /// Returns the layer with the given id, cast to its concrete type, or + /// nullptr if no layer with that id exists. + /// + Layer^ GetLayer(System::String^ layerId); + + /// Returns all layers in paint order (bottom to top). + System::Collections::Generic::List^ GetLayers(); + + // ------------------------------------------------------------------------- + // Source retrieval + // ------------------------------------------------------------------------- + + /// + /// Returns the source with the given id, cast to its concrete type, or + /// nullptr if no source with that id exists. + /// + Source^ GetSource(System::String^ sourceId); + + /// Returns all sources. + System::Collections::Generic::List^ GetSources(); + + // ------------------------------------------------------------------------- + // Add layers + // ------------------------------------------------------------------------- + + FillLayer^ AddFillLayer(System::String^ layerId, System::String^ sourceId); + LineLayer^ AddLineLayer(System::String^ layerId, System::String^ sourceId); + CircleLayer^ AddCircleLayer(System::String^ layerId, System::String^ sourceId); + SymbolLayer^ AddSymbolLayer(System::String^ layerId, System::String^ sourceId); + RasterLayer^ AddRasterLayer(System::String^ layerId, System::String^ sourceId); + BackgroundLayer^ AddBackgroundLayer(System::String^ layerId); + HeatmapLayer^ AddHeatmapLayer(System::String^ layerId, System::String^ sourceId); + HillshadeLayer^ AddHillshadeLayer(System::String^ layerId, System::String^ sourceId); + FillExtrusionLayer^ AddFillExtrusionLayer(System::String^ layerId, System::String^ sourceId); + ColorReliefLayer^ AddColorReliefLayer(System::String^ layerId, System::String^ sourceId); + LocationIndicatorLayer^ AddLocationIndicatorLayer(System::String^ layerId); + + /// Same as the two-arg overloads but inserts the layer before . + FillLayer^ AddFillLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + LineLayer^ AddLineLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + CircleLayer^ AddCircleLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + SymbolLayer^ AddSymbolLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + RasterLayer^ AddRasterLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + BackgroundLayer^ AddBackgroundLayer(System::String^ layerId, System::String^ beforeLayerId); + HeatmapLayer^ AddHeatmapLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + HillshadeLayer^ AddHillshadeLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + FillExtrusionLayer^ AddFillExtrusionLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + ColorReliefLayer^ AddColorReliefLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + LocationIndicatorLayer^ AddLocationIndicatorLayer(System::String^ layerId, System::String^ beforeLayerId); + + /// Remove a layer by id. Returns true if removed. + System::Boolean RemoveLayer(System::String^ layerId); + /// Returns true if a layer with the given id exists. + System::Boolean HasLayer(System::String^ layerId); + + // ------------------------------------------------------------------------- + // Add sources + // ------------------------------------------------------------------------- + + GeoJSONSource^ AddGeoJsonSource(System::String^ sourceId); + /// Add a GeoJSON source with explicit tile/cluster options. + GeoJSONSource^ AddGeoJsonSource(System::String^ sourceId, GeoJSONOptions^ options); + /// Add a GeoJSON source that immediately loads from a URL. + GeoJSONSource^ AddGeoJsonSourceFromUrl(System::String^ sourceId, System::String^ url); + /// Add a GeoJSON source from a URL with explicit tile/cluster options. + GeoJSONSource^ AddGeoJsonSourceFromUrl(System::String^ sourceId, System::String^ url, GeoJSONOptions^ options); + VectorSource^ AddVectorSource(System::String^ sourceId, System::String^ url); + RasterSource^ AddRasterSource(System::String^ sourceId, System::String^ url, System::UInt16 tileSize); + RasterDEMSource^ AddRasterDemSource(System::String^ sourceId, System::String^ url, System::UInt16 tileSize); + ImageSource^ AddImageSource(System::String^ sourceId, System::String^ url, array^ coordinates); + + /// Remove a source by id. Returns true if removed. + System::Boolean RemoveSource(System::String^ sourceId); + /// Returns true if a source with the given id exists. + System::Boolean HasSource(System::String^ sourceId); + + // ------------------------------------------------------------------------- // Light + // -------------------------------------------------------------------- + + /// + /// Returns a non-owning wrapper around the style's current light. + /// Mutating the returned object immediately affects the rendered map. + /// + Light^ GetLight(); + + // -------------------------------------------------------------------- + // Transition options + // -------------------------------------------------------------------- + + /// Gets the current global transition options. TransitionOptions^ GetTransitionOptions(); + + /// Sets the global transition options. System::Void SetTransitionOptions(TransitionOptions^ options); - Light* getLight(); - const Light* getLight() const; - void setLight(std::unique_ptr); + // -------------------------------------------------------------------- + // Default camera + // -------------------------------------------------------------------- + + /// + /// Returns the default camera position defined in the style JSON + /// (the "center", "zoom", "bearing" and "pitch" root fields). + /// Fields not set in the style will be unset (null) on the returned options. + /// + CameraOptions^ GetDefaultCamera(); + + // -------------------------------------------------------------------- + // Sprite / icon images + // -------------------------------------------------------------------- + + /// + /// Add a sprite image to the style. + /// + /// Unique sprite id. + /// Premultiplied RGBA pixel data. + /// Device pixel ratio the image was created at (usually 1 or 2). + /// True if the image should be treated as a signed distance field. + System::Void AddImage(System::String^ id, PremultipliedImage^ pixels, + System::Single pixelRatio, System::Boolean sdf); + + /// Add a standard (non-SDF) sprite image. + System::Void AddImage(System::String^ id, PremultipliedImage^ pixels, + System::Single pixelRatio); + + /// + /// Returns metadata for the image with the given id, or nullptr if not found. + /// + StyleImage^ GetImage(System::String^ id); - // Images - optional getImage(const std::string&) const; - void addImage(std::unique_ptr); - void removeImage(const std::string&); + /// Returns true if an image with the given id exists in the style. + System::Boolean HasImage(System::String^ id); - // Sources - std::vector< Source*> getSources(); - std::vector getSources() const; + /// Removes the image with the given id from the style. + System::Void RemoveImage(System::String^ id); - Source* getSource(const std::string&); - const Source* getSource(const std::string&) const; + // -------------------------------------------------------------------- // Legacy helpers (kept for backward compatibility) + // ------------------------------------------------------------------------- - void addSource(std::unique_ptr); - std::unique_ptr removeSource(const std::string& sourceID); + /// Add a GeoJSON source that fetches its data from a URL. + [System::Obsolete("Use AddGeoJsonSourceFromUrl instead.")] + System::Void AddGeoJsonSource(System::String^ sourceId, System::String^ url); + /// Update the URL of an existing GeoJSON source. + System::Void SetGeoJsonSourceUrl(System::String^ sourceId, System::String^ url); + /// Set the data of an existing GeoJSON source from an inline GeoJSON string. + System::Void SetGeoJsonSourceData(System::String^ sourceId, System::String^ geojsonString); - // Layers - std::vector< Layer*> getLayers(); - std::vector getLayers() const; + /// + /// Add a circle layer above all existing layers (legacy overload with inline style params). + /// color: CSS hex string e.g. "#ff0000". filterJson is reserved and currently ignored. + /// + [System::Obsolete("Use AddCircleLayer and configure properties on the returned CircleLayer.")] + System::Void AddCircleLayer(System::String^ layerId, System::String^ sourceId, + System::String^ color, float radius, float opacity, + System::String^ filterJson); - Layer* getLayer(const std::string&); - const Layer* getLayer(const std::string&) const; + /// + /// Returns a deduplicated list of non-empty attribution strings from all loaded sources. + /// + System::Collections::Generic::List^ GetSourceAttributions(); - void addLayer(std::unique_ptr, const optional& beforeLayerID = {}); - std::unique_ptr removeLayer(const std::string& layerID); - */ internal: Style(NativePointerHolder^ nativePointerHolder); }; diff --git a/include/StyleImage.h b/include/StyleImage.h new file mode 100644 index 0000000..6c75db7 --- /dev/null +++ b/include/StyleImage.h @@ -0,0 +1,35 @@ +#pragma once + +namespace DOTNET_NAMESPACE +{ + /// + /// Metadata for a style sprite image obtained from . + /// + public ref class StyleImage + { + public: + /// The image id (sprite name). + property System::String^ Id; + /// Width in pixels. + property System::UInt32 Width; + /// Height in pixels. + property System::UInt32 Height; + /// Pixel ratio (device pixel ratio the image was created at). + property System::Single PixelRatio; + /// Whether the image is a signed-distance-field icon. + property System::Boolean Sdf; + + StyleImage() {} + + internal: + StyleImage(System::String^ id, System::UInt32 width, System::UInt32 height, + System::Single pixelRatio, System::Boolean sdf) + { + Id = id; + Width = width; + Height = height; + PixelRatio = pixelRatio; + Sdf = sdf; + } + }; +} diff --git a/include/TransformState.h b/include/TransformState.h index abfec06..4c17c0b 100644 --- a/include/TransformState.h +++ b/include/TransformState.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Enums.h" #include "LatLng.h" #include "NativeWrapper.h" @@ -29,7 +29,7 @@ namespace DOTNET_NAMESPACE using NorthOrientation_ = NorthOrientation; using Size_ = Size; using ViewportMode_ = ViewportMode; - + public ref class TransformStateProperties : NativeWrapper { public: diff --git a/include/TransitionOptions.h b/include/TransitionOptions.h new file mode 100644 index 0000000..fd7bab9 --- /dev/null +++ b/include/TransitionOptions.h @@ -0,0 +1,43 @@ +#pragma once + +namespace DOTNET_NAMESPACE +{ + /// + /// Options that control the timing of transitions between style property values. + /// + public ref class TransitionOptions + { + public: + /// + /// Duration of the transition in milliseconds. + /// Null means no explicit duration (the default will apply). + /// + property System::Nullable DurationMilliseconds; + + /// + /// Delay before the transition starts, in milliseconds. + /// Null means no explicit delay. + /// + property System::Nullable DelayMilliseconds; + + /// + /// Whether placement transitions (symbol fade-in/out) are enabled. + /// Defaults to true. + /// + property System::Boolean EnablePlacementTransitions; + + TransitionOptions() + { + EnablePlacementTransitions = true; + } + + TransitionOptions(System::Nullable durationMs, + System::Nullable delayMs, + System::Boolean enablePlacementTransitions) + { + DurationMilliseconds = durationMs; + DelayMilliseconds = delayMs; + EnablePlacementTransitions = enablePlacementTransitions; + } + }; +} diff --git a/include/UnwrappedTileID.h b/include/UnwrappedTileID.h index 9b963fb..0cf0ada 100644 --- a/include/UnwrappedTileID.h +++ b/include/UnwrappedTileID.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace mbgl @@ -10,7 +10,7 @@ namespace DOTNET_NAMESPACE { ref class CanonicalTileID; ref class OverscaledTileID; - + /// /// Has integer z/x/y coordinates /// wrap describes tiles that are left/right of the main tile pyramid, e.g. when diff --git a/include/Value.h b/include/Value.h index 96e6c2f..77d5f95 100644 --- a/include/Value.h +++ b/include/Value.h @@ -4,34 +4,61 @@ namespace DOTNET_NAMESPACE { + /// + /// A variant type representing a JSON-like value: null, boolean, string, signed or unsigned + /// integer, double, array, or object. Used for feature properties and style expression evaluation. + /// public ref class Value : NativeWrapper { public: + /// Initializes a null . Value(); + /// Initializes a boolean . Value(System::Boolean v); + /// Initializes a string . Value(System::String^ str); + /// Initializes a signed 8-bit integer . Value(System::SByte n); + /// Initializes a signed 16-bit integer . Value(System::Int16 n); + /// Initializes a signed 32-bit integer . Value(System::Int32 n); + /// Initializes a signed 64-bit integer . Value(System::Int64 n); + /// Initializes an unsigned 8-bit integer . Value(System::Byte n); + /// Initializes an unsigned 16-bit integer . Value(System::UInt16 n); + /// Initializes an unsigned 32-bit integer . Value(System::UInt32 n); + /// Initializes an unsigned 64-bit integer . Value(System::UInt64 n); + /// Initializes a single-precision float (stored as double). Value(System::Single n); + /// Initializes a double-precision float . Value(System::Double n); + /// Initializes an array from a sequence of values. Value(System::Collections::Generic::IEnumerable^ a); + /// Initializes an object from a string-keyed dictionary. Value(System::Collections::Generic::IDictionary^ o); ~Value(); + /// Returns the value as a signed 64-bit integer, or if the held type is not a signed integer. System::Nullable GetInt(); + /// Returns the value as an unsigned 64-bit integer, or if the held type is not an unsigned integer. System::Nullable GetUInt(); + /// Returns the value as a boolean, or if the held type is not a boolean. System::Nullable GetBool(); + /// Returns the value as a double, or if the held type is not a number. System::Nullable GetDouble(); + /// Returns the array elements, or if the held type is not an array. System::Collections::Generic::IEnumerable^ GetArray(); + /// Returns the object members, or if the held type is not an object. System::Collections::Generic::IDictionary^ GetObject(); + /// Returns the value as a string, or if the held type is not a string. System::String^ GetString(); + /// Returns when the value is non-null and non-zero. explicit operator bool(); internal: Value(NativePointerHolder^ nativePointerHolder); diff --git a/include/Vector.h b/include/Vector.h index 022f099..b3f5899 100644 --- a/include/Vector.h +++ b/include/Vector.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include #include @@ -6,75 +6,117 @@ namespace DOTNET_NAMESPACE { ref class Mat4; - + + /// A 3-component column vector of doubles. Implements for index access. public ref class Vec3 : NativeWrapper, System::Collections::Generic::IReadOnlyList { public: + /// Initializes a zero . Vec3(); ~Vec3(); virtual System::Collections::Generic::IEnumerator^ GetEnumerator(); virtual System::Collections::IEnumerator^ GetEnumeratorObject() = System::Collections::IEnumerable::GetEnumerator; + /// Gets or sets the component at the specified index (0, 1, or 2). property System::Double default[System::Int32] { virtual System::Double get(System::Int32 index); System::Void set(System::Int32 index, System::Double value); }; + /// Gets the number of components (always 3). property System::Int32 Count { virtual System::Int32 get(); } internal: Vec3(NativePointerHolder^ nativePointerHolder); }; + /// A 4-component column vector of doubles. Implements for index access. public ref class Vec4 : NativeWrapper, System::Collections::Generic::IReadOnlyList { public: + /// Initializes a zero . Vec4(); ~Vec4(); virtual System::Collections::Generic::IEnumerator^ GetEnumerator(); virtual System::Collections::IEnumerator^ GetEnumeratorObject() = System::Collections::IEnumerable::GetEnumerator; + /// Gets or sets the component at the specified index (0, 1, 2, or 3). property System::Double default[System::Int32] { virtual System::Double get(System::Int32 index); System::Void set(System::Int32 index, System::Double value); }; + /// Gets the number of components (always 4). property System::Int32 Count { virtual System::Int32 get(); } + /// Transforms in-place by matrix . static System::Void TransformMat4(Vec4^ out, Mat4^ m); + /// Transforms vector by matrix and stores the result in . static System::Void TransformMat4(Vec4^ out, Vec4^ a, Mat4^ m); internal: Vec4(NativePointerHolder^ nativePointerHolder); }; + /// + /// A 4x4 column-major matrix of doubles. Implements for flat + /// index access (16 elements, column-major order). + /// public ref class Mat4 : NativeWrapper, System::Collections::Generic::IReadOnlyList { public: + /// Initializes an identity . Mat4(); + /// Initializes a by copying . Mat4(Mat4^ source); ~Mat4(); virtual System::Collections::Generic::IEnumerator^ GetEnumerator(); virtual System::Collections::IEnumerator^ GetEnumeratorObject() = System::Collections::IEnumerable::GetEnumerator; + /// Gets or sets the element at the specified flat index (0–15, column-major). property System::Double default[System::Int32] { virtual System::Double get(System::Int32 index); System::Void set(System::Int32 index, System::Double value); }; + /// Gets the number of elements (always 16). property System::Int32 Count { virtual System::Int32 get(); } + /// Sets to the identity matrix. static System::Void Identity(Mat4^ out); + /// Inverts in-place. Returns if the matrix is singular. static System::Boolean Invert(Mat4^ out); + /// Inverts and stores the result in . Returns if the matrix is singular. static System::Boolean Invert(Mat4^ out, Mat4^ a); + /// Generates an orthographic projection matrix into . static System::Void Ortho(Mat4^ out, System::Double left, System::Double right, System::Double bottom, System::Double top, System::Double near, System::Double far); + /// Generates a perspective projection matrix into . + /// Vertical field of view in radians. + /// Aspect ratio (width / height). static System::Void Perspective(Mat4^ out, System::Double fovy, System::Double aspect, System::Double near, System::Double far); + /// Copies matrix into . static System::Void Copy(Mat4^ out, Mat4^ a); + /// Translates in-place by (x, y, z). static System::Void Translate(Mat4^ out, System::Double x, System::Double y, System::Double z); + /// Translates matrix by (x, y, z) and stores the result in . static System::Void Translate(Mat4^ out, Mat4^ a, System::Double x, System::Double y, System::Double z); + /// Rotates in-place around the X axis by radians. static System::Void RotateX(Mat4^ out, System::Double rad); + /// Rotates matrix around the X axis and stores the result in . static System::Void RotateX(Mat4^ out, Mat4^ a, System::Double rad); + /// Rotates in-place around the Y axis by radians. static System::Void RotateY(Mat4^ out, System::Double rad); + /// Rotates matrix around the Y axis and stores the result in . static System::Void RotateY(Mat4^ out, Mat4^ a, System::Double rad); + /// Rotates in-place around the Z axis by radians. static System::Void RotateZ(Mat4^ out, System::Double rad); + /// Rotates matrix around the Z axis and stores the result in . static System::Void RotateZ(Mat4^ out, Mat4^ a, System::Double rad); + /// Scales in-place by (x, y, z). static System::Void Scale(Mat4^ out, System::Double x, System::Double y, System::Double z); + /// Scales matrix by (x, y, z) and stores the result in . static System::Void Scale(Mat4^ out, Mat4^ a, System::Double x, System::Double y, System::Double z); + /// Post-multiplies in-place by . static System::Void Multiply(Mat4^ out, Mat4^ a); + /// Multiplies by and stores the result in . static System::Void Multiply(Mat4^ out, Mat4^ a, Mat4^ b); internal: Mat4(NativePointerHolder^ nativePointerHolder); }; + /// + /// Extension methods for and that expose the same matrix + /// operations as instance-style calls (e.g. mat.Multiply(other)). + /// [System::Runtime::CompilerServices::ExtensionAttribute] public ref class MatrixExtensions abstract sealed { diff --git a/src/AnimationOptions.cpp b/src/AnimationOptions.cpp index 4bf8792..9df4864 100644 --- a/src/AnimationOptions.cpp +++ b/src/AnimationOptions.cpp @@ -1,4 +1,4 @@ -#include "AnimationOptions.h" +#include "AnimationOptions.h" #include "Convert.h" #include "UnitBezier.h" #include @@ -49,7 +49,7 @@ namespace DOTNET_NAMESPACE { return System::Nullable(NativePointer->velocity.value()); } - + return System::Nullable(); } diff --git a/src/BoundOptions.cpp b/src/BoundOptions.cpp index aa762ac..1009ede 100644 --- a/src/BoundOptions.cpp +++ b/src/BoundOptions.cpp @@ -1,4 +1,4 @@ -#include "BoundOptions.h" +#include "BoundOptions.h" #include "LatLng.h" #include @@ -52,7 +52,7 @@ namespace DOTNET_NAMESPACE { return gcnew LatLngBounds(LatLngBounds::CreateNativePointerHolder(NativePointer->bounds.value())); } - + return nullptr; } diff --git a/src/CameraOptions.cpp b/src/CameraOptions.cpp index 37bfbac..dd76731 100644 --- a/src/CameraOptions.cpp +++ b/src/CameraOptions.cpp @@ -1,4 +1,4 @@ -#include "CameraOptions.h" +#include "CameraOptions.h" #include "EdgeInsets.h" #include "LatLng.h" #include "Point.h" @@ -28,7 +28,13 @@ namespace DOTNET_NAMESPACE { NativePointer->withCenter(std::nullopt); } - + + return this; + } + + CameraOptions^ CameraOptions::WithCenterAltitude(System::Nullable o) + { + NativePointer->withCenterAltitude(o.HasValue ? std::optional(o.Value) : std::nullopt); return this; } @@ -42,7 +48,7 @@ namespace DOTNET_NAMESPACE { NativePointer->withPadding(std::nullopt); } - + return this; } @@ -70,7 +76,7 @@ namespace DOTNET_NAMESPACE { NativePointer->withZoom(std::nullopt); } - + return this; } @@ -102,6 +108,54 @@ namespace DOTNET_NAMESPACE return this; } + CameraOptions^ CameraOptions::WithRoll(System::Nullable o) + { + NativePointer->withRoll(o.HasValue ? std::optional(o.Value) : std::nullopt); + return this; + } + + CameraOptions^ CameraOptions::WithFov(System::Nullable o) + { + NativePointer->withFov(o.HasValue ? std::optional(o.Value) : std::nullopt); + return this; + } + + System::Nullable CameraOptions::CenterAltitude::get() + { + if (NativePointer->centerAltitude.has_value()) + return System::Nullable(NativePointer->centerAltitude.value()); + return System::Nullable(); + } + + System::Void CameraOptions::CenterAltitude::set(System::Nullable value) + { + NativePointer->centerAltitude = value.HasValue ? std::optional(value.Value) : std::nullopt; + } + + System::Nullable CameraOptions::Roll::get() + { + if (NativePointer->roll.has_value()) + return System::Nullable(NativePointer->roll.value()); + return System::Nullable(); + } + + System::Void CameraOptions::Roll::set(System::Nullable value) + { + NativePointer->roll = value.HasValue ? std::optional(value.Value) : std::nullopt; + } + + System::Nullable CameraOptions::Fov::get() + { + if (NativePointer->fov.has_value()) + return System::Nullable(NativePointer->fov.value()); + return System::Nullable(); + } + + System::Void CameraOptions::Fov::set(System::Nullable value) + { + NativePointer->fov = value.HasValue ? std::optional(value.Value) : std::nullopt; + } + LatLng^ CameraOptions::Center::get() { if (!_Center && NativePointer->center.has_value()) @@ -112,7 +166,7 @@ namespace DOTNET_NAMESPACE { _Center = nullptr; } - + return _Center; } @@ -176,7 +230,7 @@ namespace DOTNET_NAMESPACE { return System::Nullable(NativePointer->zoom.value()); } - + return System::Nullable(); } diff --git a/src/ClientOptions.cpp b/src/ClientOptions.cpp index 9dbc8f9..15098ed 100644 --- a/src/ClientOptions.cpp +++ b/src/ClientOptions.cpp @@ -1,4 +1,4 @@ -#include "Convert.h" +#include "Convert.h" #include "ClientOptions.h" #include @@ -31,7 +31,7 @@ namespace DOTNET_NAMESPACE ClientOptions^ ClientOptions::WithVersion(System::String^ version) { NativePointer->withVersion(Convert::ToStdString(version)); - + return this; } diff --git a/src/FileSource.cpp b/src/FileSource.cpp index d8ddc3b..76d2d3d 100644 --- a/src/FileSource.cpp +++ b/src/FileSource.cpp @@ -9,18 +9,26 @@ #include #include +// Free helper functions — lambdas are only forbidden inside managed class +// member functions (C3923), not inside regular free functions. namespace { using namespace ::DOTNET_NAMESPACE; - - void RequestCallback(mbgl::Response response, FileSource::Callback^ callback) + + mbgl::FileSource::Callback MakeRequestCallback(FileSource::Callback^ callback) { - callback->Invoke(gcnew Response(Response::CreateNativePointerHolder(response))); + auto ref = msclr::gcroot(callback); + return [ref](mbgl::Response response) mutable { + ref->Invoke(gcnew Response(Response::CreateNativePointerHolder(response))); + }; } - void ForwardCallback(System::Action^ callback) + std::function MakeForwardCallback(System::Action^ callback) { - callback->Invoke(); + auto ref = msclr::gcroot(callback); + return [ref]() mutable { + ref->Invoke(); + }; } } @@ -45,7 +53,7 @@ namespace DOTNET_NAMESPACE AsyncRequest::CreateNativePointerHolder( NativePointer->get()->request( *resource->NativePointer, - std::bind(&RequestCallback, std::placeholders::_1, msclr::gcroot(callback)) + MakeRequestCallback(callback) ) .release() ) @@ -57,7 +65,7 @@ namespace DOTNET_NAMESPACE NativePointer->get()->forward( *resource->NativePointer, *response->NativePointer, - std::bind(&ForwardCallback, msclr::gcroot(callback)) + MakeForwardCallback(callback) ); } diff --git a/src/FileSourceManager.cpp b/src/FileSourceManager.cpp index 7bb83b0..25b614a 100644 --- a/src/FileSourceManager.cpp +++ b/src/FileSourceManager.cpp @@ -1,4 +1,4 @@ -#include "ClientOptions.h" +#include "ClientOptions.h" #include "Enums.h" #include "FileSource.h" #include "FileSourceManager.h" @@ -40,4 +40,4 @@ namespace DOTNET_NAMESPACE ) ); } -} \ No newline at end of file +} diff --git a/src/FreeCameraOptions.cpp b/src/FreeCameraOptions.cpp index e0ee0d0..07a6563 100644 --- a/src/FreeCameraOptions.cpp +++ b/src/FreeCameraOptions.cpp @@ -1,4 +1,4 @@ -#include "FreeCameraOptions.h" +#include "FreeCameraOptions.h" #include "LatLng.h" #include "Vector.h" #include @@ -27,9 +27,9 @@ namespace DOTNET_NAMESPACE NativePointer->lookAtPoint(*location->NativePointer, *upVector->NativePointer); } - System::Void FreeCameraOptions::SetPitchBearing(System::Double pitch, System::Double bearing) + System::Void FreeCameraOptions::SetRollPitchBearing(System::Double roll, System::Double pitch, System::Double bearing) { - NativePointer->setPitchBearing(pitch, bearing); + NativePointer->setRollPitchBearing(roll, pitch, bearing); } Vec3^ FreeCameraOptions::Position::get() @@ -60,7 +60,7 @@ namespace DOTNET_NAMESPACE { return gcnew Vec4(Vec4::CreateNativePointerHolder(NativePointer->orientation.value())); } - + return nullptr; } diff --git a/src/HeadlessFrontend.cpp b/src/HeadlessFrontend.cpp index c38df56..83cc7ed 100644 --- a/src/HeadlessFrontend.cpp +++ b/src/HeadlessFrontend.cpp @@ -1,4 +1,4 @@ -#include "CameraOptions.h" +#include "CameraOptions.h" #include "Convert.h" #include "HeadlessFrontend.h" #include "LatLng.h" @@ -21,11 +21,11 @@ namespace DOTNET_NAMESPACE HeadlessFrontend::HeadlessFrontend() : HeadlessFrontend(gcnew Size_(512, 512)) { } - + HeadlessFrontend::HeadlessFrontend(Size_^ size) : HeadlessFrontend(size, 1.0f) { } - + HeadlessFrontend::HeadlessFrontend(Size_^ size, System::Single pixelRatio) : NativeWrapper(new mbgl::HeadlessFrontend(*size->NativePointer, pixelRatio)) { @@ -120,7 +120,7 @@ namespace DOTNET_NAMESPACE { NativePointer->setSize(*value->NativePointer); } - + Renderer_^ HeadlessFrontend::Renderer::get() { return gcnew Renderer_(Renderer_::CreateNativePointerHolder(NativePointer->getRenderer())); diff --git a/src/Image.cpp b/src/Image.cpp index fca8f96..4e44aa6 100644 --- a/src/Image.cpp +++ b/src/Image.cpp @@ -1,4 +1,4 @@ -#include "Image.h" +#include "Image.h" #include "AlphaImage.h" #include "PremultipliedImage.h" #include "UnassociatedImage.h" @@ -9,7 +9,7 @@ namespace DOTNET_NAMESPACE template T^ ImageBase::Clone() { - return gcnew T(T::CreateNativePointerHolder(NativePointer->clone())); + return gcnew T(T::CreateNativePointerHolder(this->NativePointer->clone())); } template @@ -39,11 +39,11 @@ namespace DOTNET_NAMESPACE template AlphaImage^ BaseAlphaImage::Clone(); template System::Void BaseAlphaImage::Clear(AlphaImage^ dstImg, PointUInt pt, Size_^ size); template System::Void BaseAlphaImage::Copy(AlphaImage^ srcImg, AlphaImage^ dstImg, PointUInt srcPt, PointUInt dstPt, Size_^ size); - + template PremultipliedImage^ BasePremultipliedImage::Clone(); template System::Void BasePremultipliedImage::Clear(PremultipliedImage^ dstImg, PointUInt pt, Size_^ size); template System::Void BasePremultipliedImage::Copy(PremultipliedImage^ srcImg, PremultipliedImage^ dstImg, PointUInt srcPt, PointUInt dstPt, Size_^ size); - + template UnassociatedImage^ BaseUnassociatedImage::Clone(); template System::Void BaseUnassociatedImage::Clear(UnassociatedImage^ dstImg, PointUInt pt, Size_^ size); template System::Void BaseUnassociatedImage::Copy(UnassociatedImage^ srcImg, UnassociatedImage^ dstImg, PointUInt srcPt, PointUInt dstPt, Size_^ size); diff --git a/src/Layer.cpp b/src/Layer.cpp new file mode 100644 index 0000000..4524780 --- /dev/null +++ b/src/Layer.cpp @@ -0,0 +1,211 @@ +#include "Layer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace +{ + // ----------------------------------------------------------------------- + // Forward declaration + // ----------------------------------------------------------------------- + static void WriteValue(rapidjson::Writer& writer, + const mbgl::Value& value); + + struct ValueWriter + { + rapidjson::Writer& writer; + + void operator()(const mbgl::NullValue&) const + { + writer.Null(); + } + + void operator()(bool v) const + { + writer.Bool(v); + } + + void operator()(uint64_t v) const + { + writer.Uint64(v); + } + + void operator()(int64_t v) const + { + writer.Int64(v); + } + + void operator()(double v) const + { + writer.Double(v); + } + + void operator()(const std::string& v) const + { + writer.String(v.c_str(), static_cast(v.size())); + } + + void operator()(const mapbox::util::recursive_wrapper>& rw) const + { + writer.StartArray(); + for (const auto& item : rw.get()) + WriteValue(writer, item); + writer.EndArray(); + } + + void operator()(const mapbox::util::recursive_wrapper>& rw) const + { + writer.StartObject(); + for (const auto& kv : rw.get()) + { + writer.Key(kv.first.c_str(), static_cast(kv.first.size())); + WriteValue(writer, kv.second); + } + writer.EndObject(); + } + }; + + static void WriteValue(rapidjson::Writer& writer, + const mbgl::Value& value) + { + mbgl::Value::visit(value, ValueWriter{ writer }); + } + + static std::string ValueToJsonString(const mbgl::Value& value) + { + rapidjson::StringBuffer buf; + rapidjson::Writer writer(buf); + WriteValue(writer, value); + return buf.GetString(); + } +} + +namespace DOTNET_NAMESPACE +{ + Layer::Layer(mbgl::style::Layer* layer) + : _layer(layer) + { + } + + System::String^ Layer::Id::get() + { + return msclr::interop::marshal_as(_layer->getID()); + } + + System::String^ Layer::SourceId::get() + { + return msclr::interop::marshal_as(_layer->getSourceID()); + } + + System::Void Layer::SourceId::set(System::String^ value) + { + _layer->setSourceID(msclr::interop::marshal_as(value)); + } + + System::String^ Layer::SourceLayer::get() + { + return msclr::interop::marshal_as(_layer->getSourceLayer()); + } + + System::Void Layer::SourceLayer::set(System::String^ value) + { + _layer->setSourceLayer(msclr::interop::marshal_as(value)); + } + + bool Layer::Visible::get() + { + return _layer->getVisibility() == mbgl::style::VisibilityType::Visible; + } + + System::Void Layer::Visible::set(bool value) + { + _layer->setVisibility(value + ? mbgl::style::VisibilityType::Visible + : mbgl::style::VisibilityType::None); + } + + float Layer::MinZoom::get() + { + return _layer->getMinZoom(); + } + + System::Void Layer::MinZoom::set(float value) + { + _layer->setMinZoom(value); + } + + float Layer::MaxZoom::get() + { + return _layer->getMaxZoom(); + } + + System::Void Layer::MaxZoom::set(float value) + { + _layer->setMaxZoom(value); + } + + System::String^ Layer::Type::get() + { + const auto* info = _layer->getTypeInfo(); + return info ? msclr::interop::marshal_as(std::string(info->type)) : System::String::Empty; + } + + System::String^ Layer::GetFilter() + { + const mbgl::style::Filter& filter = _layer->getFilter(); + if (!filter) + return System::String::Empty; + mbgl::Value serialized = filter.serialize(); + return msclr::interop::marshal_as(ValueToJsonString(serialized)); + } + + System::Void Layer::SetFilter(System::String^ filterJson) + { + if (filterJson == nullptr || filterJson->Length == 0) + { + _layer->setFilter(mbgl::style::Filter{}); + return; + } + + std::string json = msclr::interop::marshal_as(filterJson); + mbgl::JSDocument doc; + doc.Parse(json.c_str()); + if (doc.HasParseError()) + throw gcnew System::ArgumentException("Invalid filter JSON: parse error"); + + mbgl::style::conversion::Error error; + const mbgl::JSValue& jsRoot = doc; + auto filter = mbgl::style::conversion::convert( + mbgl::style::conversion::Convertible(&jsRoot), error); + if (!filter) + throw gcnew System::ArgumentException( + gcnew System::String(("Invalid filter expression: " + error.message).c_str())); + + _layer->setFilter(*filter); + } + + System::String^ Layer::GetPaintProperty(System::String^ name) + { + auto prop = _layer->getProperty(msclr::interop::marshal_as(name)); + if (prop.getKind() == mbgl::style::StyleProperty::Kind::Undefined) + return System::String::Empty; + return msclr::interop::marshal_as(ValueToJsonString(prop.getValue())); + } + + System::String^ Layer::GetLayoutProperty(System::String^ name) + { + auto prop = _layer->getProperty(msclr::interop::marshal_as(name)); + if (prop.getKind() == mbgl::style::StyleProperty::Kind::Undefined) + return System::String::Empty; + return msclr::interop::marshal_as(ValueToJsonString(prop.getValue())); + } +} diff --git a/src/Layers.cpp b/src/Layers.cpp new file mode 100644 index 0000000..04c686a --- /dev/null +++ b/src/Layers.cpp @@ -0,0 +1,1979 @@ +#include "Layers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace +{ + // ------------------------------------------------------------------------- + // Helper: convert mbgl::Color -> System::String^ (CSS rgba string) + // ------------------------------------------------------------------------- + inline System::String^ ColorToString(const mbgl::Color& c) + { + return msclr::interop::marshal_as(c.stringify()); + } + + // ------------------------------------------------------------------------- + // Helper: parse a CSS color string into mbgl::Color; returns black on failure + // ------------------------------------------------------------------------- + inline mbgl::Color ParseColor(System::String^ s) + { + auto parsed = mbgl::Color::parse(msclr::interop::marshal_as(s)); + return parsed ? *parsed : mbgl::Color::black(); + } + + // ------------------------------------------------------------------------- + // Helper: extract constant float from PropertyValue; default on expression + // ------------------------------------------------------------------------- + inline float GetFloat(const mbgl::style::PropertyValue& pv, float def = 0.0f) + { + return pv.isConstant() ? pv.asConstant() : def; + } + + // ------------------------------------------------------------------------- + // Helper: extract constant bool from PropertyValue; default on expression + // ------------------------------------------------------------------------- + inline bool GetBool(const mbgl::style::PropertyValue& pv, bool def = false) + { + return pv.isConstant() ? pv.asConstant() : def; + } + + // ------------------------------------------------------------------------- + // Helper: extract constant Color from PropertyValue; default on expression + // ------------------------------------------------------------------------- + inline System::String^ GetColor(const mbgl::style::PropertyValue& pv, + System::String^ def) + { + return pv.isConstant() ? ColorToString(pv.asConstant()) : def; + } + + // ------------------------------------------------------------------------- + // Version-agnostic color getter: handles both PropertyValue and + // PropertyValue> (newer maplibre-native builds) + // ------------------------------------------------------------------------- + template + inline System::String^ GetColorAgnostic(const PropVal& pv, System::String^ def) + { + using T = std::decay_t; + if constexpr (std::is_same_v) + return GetColor(pv, def); + else // std::vector + { + if (pv.isConstant() && !pv.asConstant().empty()) + return ColorToString(pv.asConstant()[0]); + return def; + } + } + + // ------------------------------------------------------------------------- + // Version-agnostic getters/setters extracted from managed member functions + // (C++/CLI forbids generic lambdas — local template types — in managed fns) + // ------------------------------------------------------------------------- + + // SymbolLayer::IconPadding — float (old) or mbgl::Padding (new) + template + inline float GetIconPadding(const PropVal& pv) + { + using T = std::decay_t; + if constexpr (std::is_same_v) + return GetFloat(pv, 2.0f); + else // mbgl::Padding + return pv.isConstant() ? pv.asConstant().top : 2.0f; + } + + template + inline void SetIconPadding(LayerImpl* impl, float value) + { + using T = std::decay_tgetIconPadding().asConstant())>; + if constexpr (std::is_same_v) + impl->setIconPadding(mbgl::style::PropertyValue(value)); + else // mbgl::Padding — uniform padding on all sides + impl->setIconPadding(mbgl::style::PropertyValue(T{value, value, value, value})); + } + + // HillshadeLayer::IlluminationDirection — float (old) or vector (new) + template + inline float GetIlluminationDirection(const PropVal& pv) + { + using T = std::decay_t; + if constexpr (std::is_same_v) + return GetFloat(pv, 335.0f); + else // std::vector + return (pv.isConstant() && !pv.asConstant().empty()) ? pv.asConstant()[0] : 335.0f; + } + + template + inline void SetIlluminationDirection(LayerImpl* impl, float value) + { + using T = std::decay_tgetHillshadeIlluminationDirection().asConstant())>; + if constexpr (std::is_same_v) + impl->setHillshadeIlluminationDirection(mbgl::style::PropertyValue(value)); + else // std::vector + impl->setHillshadeIlluminationDirection(mbgl::style::PropertyValue({value})); + } + + // HillshadeLayer::ShadowColor — Color (old) or vector (new) + template + inline void SetHillshadeShadowColor(LayerImpl* impl, mbgl::Color c) + { + using T = std::decay_tgetHillshadeShadowColor().asConstant())>; + if constexpr (std::is_same_v) + impl->setHillshadeShadowColor(mbgl::style::PropertyValue(c)); + else // std::vector + impl->setHillshadeShadowColor(mbgl::style::PropertyValue({c})); + } + + // HillshadeLayer::HighlightColor — Color (old) or vector (new) + template + inline void SetHillshadeHighlightColor(LayerImpl* impl, mbgl::Color c) + { + using T = std::decay_tgetHillshadeHighlightColor().asConstant())>; + if constexpr (std::is_same_v) + impl->setHillshadeHighlightColor(mbgl::style::PropertyValue(c)); + else // std::vector + impl->setHillshadeHighlightColor(mbgl::style::PropertyValue({c})); + } + + // ------------------------------------------------------------------------- + // Helpers: serialize mbgl::Value to JSON string (used for expression props) + // ------------------------------------------------------------------------- + static void WriteValue(rapidjson::Writer& writer, + const mbgl::Value& value); + + struct ValueWriter + { + rapidjson::Writer& writer; + void operator()(const mbgl::NullValue&) const { writer.Null(); } + void operator()(bool v) const { writer.Bool(v); } + void operator()(uint64_t v) const { writer.Uint64(v); } + void operator()(int64_t v) const { writer.Int64(v); } + void operator()(double v) const { writer.Double(v); } + void operator()(const std::string& v) const + { + writer.String(v.c_str(), static_cast(v.size())); + } + void operator()(const mapbox::util::recursive_wrapper>& rw) const + { + writer.StartArray(); + for (const auto& item : rw.get()) WriteValue(writer, item); + writer.EndArray(); + } + void operator()(const mapbox::util::recursive_wrapper>& rw) const + { + writer.StartObject(); + for (const auto& kv : rw.get()) + { + writer.Key(kv.first.c_str(), static_cast(kv.first.size())); + WriteValue(writer, kv.second); + } + writer.EndObject(); + } + }; + + static void WriteValue(rapidjson::Writer& writer, + const mbgl::Value& value) + { + mbgl::Value::visit(value, ValueWriter{ writer }); + } + + static std::string ValueToJsonString(const mbgl::Value& value) + { + rapidjson::StringBuffer buf; + rapidjson::Writer writer(buf); + WriteValue(writer, value); + return buf.GetString(); + } +} + +namespace DOTNET_NAMESPACE +{ + // ========================================================================= + // FillLayer + // ========================================================================= + + FillLayer::FillLayer(mbgl::style::FillLayer* layer) : Layer(layer) {} + + mbgl::style::FillLayer* FillLayer::Impl() + { + return static_cast(_layer); + } + + System::String^ FillLayer::Color::get() + { + return GetColor(Impl()->getFillColor(), "#000000"); + } + + System::Void FillLayer::Color::set(System::String^ value) + { + Impl()->setFillColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float FillLayer::Opacity::get() + { + return GetFloat(Impl()->getFillOpacity(), 1.0f); + } + + System::Void FillLayer::Opacity::set(float value) + { + Impl()->setFillOpacity(mbgl::style::PropertyValue(value)); + } + + System::String^ FillLayer::OutlineColor::get() + { + return GetColor(Impl()->getFillOutlineColor(), ""); + } + + System::Void FillLayer::OutlineColor::set(System::String^ value) + { + Impl()->setFillOutlineColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + bool FillLayer::Antialias::get() + { + return GetBool(Impl()->getFillAntialias(), true); + } + + System::Void FillLayer::Antialias::set(bool value) + { + Impl()->setFillAntialias(mbgl::style::PropertyValue(value)); + } + + float FillLayer::SortKey::get() + { + return GetFloat(Impl()->getFillSortKey(), 0.0f); + } + + System::Void FillLayer::SortKey::set(float value) + { + Impl()->setFillSortKey(mbgl::style::PropertyValue(value)); + } + + System::String^ FillLayer::Pattern::get() + { + auto& pv = Impl()->getFillPattern(); + if (pv.isConstant()) + return gcnew System::String(pv.asConstant().id().c_str()); + return System::String::Empty; + } + + System::Void FillLayer::Pattern::set(System::String^ value) + { + auto raw = msclr::interop::marshal_as(value); + Impl()->setFillPattern(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + + cli::array^ FillLayer::Translate::get() + { + auto& pv = Impl()->getFillTranslate(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void FillLayer::Translate::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setFillTranslate(mbgl::style::PropertyValue>(arr)); + } + + TranslateAnchorType FillLayer::TranslateAnchor::get() + { + auto& pv = Impl()->getFillTranslateAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TranslateAnchorType::Map; + } + + System::Void FillLayer::TranslateAnchor::set(TranslateAnchorType value) + { + Impl()->setFillTranslateAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + + // ========================================================================= + // LineLayer + // ========================================================================= + + LineLayer::LineLayer(mbgl::style::LineLayer* layer) : Layer(layer) {} + + mbgl::style::LineLayer* LineLayer::Impl() + { + return static_cast(_layer); + } + + System::String^ LineLayer::Color::get() + { + return GetColor(Impl()->getLineColor(), "#000000"); + } + + System::Void LineLayer::Color::set(System::String^ value) + { + Impl()->setLineColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float LineLayer::Opacity::get() + { + return GetFloat(Impl()->getLineOpacity(), 1.0f); + } + + System::Void LineLayer::Opacity::set(float value) + { + Impl()->setLineOpacity(mbgl::style::PropertyValue(value)); + } + + float LineLayer::Width::get() + { + return GetFloat(Impl()->getLineWidth(), 1.0f); + } + + System::Void LineLayer::Width::set(float value) + { + Impl()->setLineWidth(mbgl::style::PropertyValue(value)); + } + + float LineLayer::Blur::get() + { + return GetFloat(Impl()->getLineBlur(), 0.0f); + } + + System::Void LineLayer::Blur::set(float value) + { + Impl()->setLineBlur(mbgl::style::PropertyValue(value)); + } + + float LineLayer::GapWidth::get() + { + return GetFloat(Impl()->getLineGapWidth(), 0.0f); + } + + System::Void LineLayer::GapWidth::set(float value) + { + Impl()->setLineGapWidth(mbgl::style::PropertyValue(value)); + } + + float LineLayer::Offset::get() + { + return GetFloat(Impl()->getLineOffset(), 0.0f); + } + + System::Void LineLayer::Offset::set(float value) + { + Impl()->setLineOffset(mbgl::style::PropertyValue(value)); + } + + LineCapType LineLayer::Cap::get() + { + auto& pv = Impl()->getLineCap(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return LineCapType::Butt; + } + + System::Void LineLayer::Cap::set(LineCapType value) + { + Impl()->setLineCap(mbgl::style::PropertyValue( + static_cast(value))); + } + + LineJoinType LineLayer::Join::get() + { + auto& pv = Impl()->getLineJoin(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return LineJoinType::Miter; + } + + System::Void LineLayer::Join::set(LineJoinType value) + { + Impl()->setLineJoin(mbgl::style::PropertyValue( + static_cast(value))); + } + + float LineLayer::MiterLimit::get() + { + return GetFloat(Impl()->getLineMiterLimit(), 2.0f); + } + + System::Void LineLayer::MiterLimit::set(float value) + { + Impl()->setLineMiterLimit(mbgl::style::PropertyValue(value)); + } + + float LineLayer::RoundLimit::get() + { + return GetFloat(Impl()->getLineRoundLimit(), 1.05f); + } + + System::Void LineLayer::RoundLimit::set(float value) + { + Impl()->setLineRoundLimit(mbgl::style::PropertyValue(value)); + } + + float LineLayer::SortKey::get() + { + return GetFloat(Impl()->getLineSortKey(), 0.0f); + } + + System::Void LineLayer::SortKey::set(float value) + { + Impl()->setLineSortKey(mbgl::style::PropertyValue(value)); + } + + cli::array^ LineLayer::Dasharray::get() + { + auto& pv = Impl()->getLineDasharray(); + if (pv.isConstant()) + { + const auto& vec = pv.asConstant(); + auto arr = gcnew cli::array(static_cast(vec.size())); + for (int i = 0; i < static_cast(vec.size()); ++i) + arr[i] = vec[i]; + return arr; + } + return gcnew cli::array(0); + } + + System::Void LineLayer::Dasharray::set(cli::array^ value) + { + std::vector vec; + if (value != nullptr) + for each (float v in value) + vec.push_back(v); + Impl()->setLineDasharray(mbgl::style::PropertyValue>(vec)); + } + + System::String^ LineLayer::Pattern::get() + { + auto& pv = Impl()->getLinePattern(); + if (pv.isConstant()) + return gcnew System::String(pv.asConstant().id().c_str()); + return System::String::Empty; + } + + System::Void LineLayer::Pattern::set(System::String^ value) + { + auto raw = msclr::interop::marshal_as(value); + Impl()->setLinePattern(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + + cli::array^ LineLayer::Translate::get() + { + auto& pv = Impl()->getLineTranslate(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void LineLayer::Translate::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setLineTranslate(mbgl::style::PropertyValue>(arr)); + } + + TranslateAnchorType LineLayer::TranslateAnchor::get() + { + auto& pv = Impl()->getLineTranslateAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TranslateAnchorType::Map; + } + + System::Void LineLayer::TranslateAnchor::set(TranslateAnchorType value) + { + Impl()->setLineTranslateAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + + System::String^ LineLayer::Gradient::get() + { + const auto& pv = Impl()->getLineGradient(); + if (!pv.isUndefined()) + { + mbgl::Value serialized = pv.getExpression().serialize(); + return msclr::interop::marshal_as(ValueToJsonString(serialized)); + } + return System::String::Empty; + } + + System::Void LineLayer::Gradient::set(System::String^ value) + { + if (value == nullptr || value->Length == 0) return; + std::string json = msclr::interop::marshal_as(value); + mbgl::JSDocument doc; + doc.Parse(json.c_str()); + if (doc.HasParseError()) + throw gcnew System::ArgumentException("Invalid gradient JSON: parse error"); + mbgl::style::conversion::Error error; + const mbgl::JSValue& jsRoot = doc; + auto gradient = mbgl::style::conversion::convert( + mbgl::style::conversion::Convertible(&jsRoot), error); + if (!gradient) + throw gcnew System::ArgumentException( + gcnew System::String(("Invalid gradient expression: " + error.message).c_str())); + Impl()->setLineGradient(*gradient); + } + + // ========================================================================= + // CircleLayer + // ========================================================================= + + CircleLayer::CircleLayer(mbgl::style::CircleLayer* layer) : Layer(layer) {} + + mbgl::style::CircleLayer* CircleLayer::Impl() + { + return static_cast(_layer); + } + + System::String^ CircleLayer::Color::get() + { + return GetColor(Impl()->getCircleColor(), "#000000"); + } + + System::Void CircleLayer::Color::set(System::String^ value) + { + Impl()->setCircleColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float CircleLayer::Opacity::get() + { + return GetFloat(Impl()->getCircleOpacity(), 1.0f); + } + + System::Void CircleLayer::Opacity::set(float value) + { + Impl()->setCircleOpacity(mbgl::style::PropertyValue(value)); + } + + float CircleLayer::Radius::get() + { + return GetFloat(Impl()->getCircleRadius(), 5.0f); + } + + System::Void CircleLayer::Radius::set(float value) + { + Impl()->setCircleRadius(mbgl::style::PropertyValue(value)); + } + + float CircleLayer::StrokeWidth::get() + { + return GetFloat(Impl()->getCircleStrokeWidth(), 0.0f); + } + + System::Void CircleLayer::StrokeWidth::set(float value) + { + Impl()->setCircleStrokeWidth(mbgl::style::PropertyValue(value)); + } + + System::String^ CircleLayer::StrokeColor::get() + { + return GetColor(Impl()->getCircleStrokeColor(), "#000000"); + } + + System::Void CircleLayer::StrokeColor::set(System::String^ value) + { + Impl()->setCircleStrokeColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float CircleLayer::StrokeOpacity::get() + { + return GetFloat(Impl()->getCircleStrokeOpacity(), 1.0f); + } + + System::Void CircleLayer::StrokeOpacity::set(float value) + { + Impl()->setCircleStrokeOpacity(mbgl::style::PropertyValue(value)); + } + + float CircleLayer::Blur::get() + { + return GetFloat(Impl()->getCircleBlur(), 0.0f); + } + + System::Void CircleLayer::Blur::set(float value) + { + Impl()->setCircleBlur(mbgl::style::PropertyValue(value)); + } + + float CircleLayer::SortKey::get() + { + return GetFloat(Impl()->getCircleSortKey(), 0.0f); + } + + System::Void CircleLayer::SortKey::set(float value) + { + Impl()->setCircleSortKey(mbgl::style::PropertyValue(value)); + } + + CirclePitchScaleType CircleLayer::PitchScale::get() + { + auto& pv = Impl()->getCirclePitchScale(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return CirclePitchScaleType::Map; + } + + System::Void CircleLayer::PitchScale::set(CirclePitchScaleType value) + { + Impl()->setCirclePitchScale(mbgl::style::PropertyValue( + static_cast(value))); + } + + AlignmentType CircleLayer::PitchAlignment::get() + { + auto& pv = Impl()->getCirclePitchAlignment(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return AlignmentType::Viewport; + } + + System::Void CircleLayer::PitchAlignment::set(AlignmentType value) + { + Impl()->setCirclePitchAlignment(mbgl::style::PropertyValue( + static_cast(value))); + } + + cli::array^ CircleLayer::Translate::get() + { + auto& pv = Impl()->getCircleTranslate(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void CircleLayer::Translate::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setCircleTranslate(mbgl::style::PropertyValue>(arr)); + } + + TranslateAnchorType CircleLayer::TranslateAnchor::get() + { + auto& pv = Impl()->getCircleTranslateAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TranslateAnchorType::Map; + } + + System::Void CircleLayer::TranslateAnchor::set(TranslateAnchorType value) + { + Impl()->setCircleTranslateAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + + // ========================================================================= + // SymbolLayer + // ========================================================================= + + SymbolLayer::SymbolLayer(mbgl::style::SymbolLayer* layer) : Layer(layer) {} + + mbgl::style::SymbolLayer* SymbolLayer::Impl() + { + return static_cast(_layer); + } + + // ---- Layout: Symbol placement ---- + + SymbolPlacementType SymbolLayer::SymbolPlacement::get() + { + auto& pv = Impl()->getSymbolPlacement(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return SymbolPlacementType::Point; + } + + System::Void SymbolLayer::SymbolPlacement::set(SymbolPlacementType value) + { + Impl()->setSymbolPlacement(mbgl::style::PropertyValue( + static_cast(value))); + } + + float SymbolLayer::SymbolSpacing::get() + { + return GetFloat(Impl()->getSymbolSpacing(), 250.0f); + } + + System::Void SymbolLayer::SymbolSpacing::set(float value) + { + Impl()->setSymbolSpacing(mbgl::style::PropertyValue(value)); + } + + bool SymbolLayer::SymbolAvoidEdges::get() + { + return GetBool(Impl()->getSymbolAvoidEdges(), false); + } + + System::Void SymbolLayer::SymbolAvoidEdges::set(bool value) + { + Impl()->setSymbolAvoidEdges(mbgl::style::PropertyValue(value)); + } + + float SymbolLayer::SymbolSortKey::get() + { + return GetFloat(Impl()->getSymbolSortKey(), 0.0f); + } + + System::Void SymbolLayer::SymbolSortKey::set(float value) + { + Impl()->setSymbolSortKey(mbgl::style::PropertyValue(value)); + } + + SymbolZOrderType SymbolLayer::SymbolZOrder::get() + { + auto& pv = Impl()->getSymbolZOrder(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return SymbolZOrderType::Auto; + } + + System::Void SymbolLayer::SymbolZOrder::set(SymbolZOrderType value) + { + Impl()->setSymbolZOrder(mbgl::style::PropertyValue( + static_cast(value))); + } + + // ---- Layout: Icon ---- + + System::String^ SymbolLayer::IconImage::get() + { + auto& pv = Impl()->getIconImage(); + if (pv.isConstant()) + return gcnew System::String(pv.asConstant().id().c_str()); + return System::String::Empty; + } + + System::Void SymbolLayer::IconImage::set(System::String^ value) + { + auto raw = msclr::interop::marshal_as(value); + Impl()->setIconImage(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + + float SymbolLayer::IconSize::get() + { + return GetFloat(Impl()->getIconSize(), 1.0f); + } + + System::Void SymbolLayer::IconSize::set(float value) + { + Impl()->setIconSize(mbgl::style::PropertyValue(value)); + } + + SymbolAnchorType SymbolLayer::IconAnchor::get() + { + auto& pv = Impl()->getIconAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return SymbolAnchorType::Center; + } + + System::Void SymbolLayer::IconAnchor::set(SymbolAnchorType value) + { + Impl()->setIconAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + + float SymbolLayer::IconRotate::get() + { + return GetFloat(Impl()->getIconRotate(), 0.0f); + } + + System::Void SymbolLayer::IconRotate::set(float value) + { + Impl()->setIconRotate(mbgl::style::PropertyValue(value)); + } + + cli::array^ SymbolLayer::IconOffset::get() + { + auto& pv = Impl()->getIconOffset(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void SymbolLayer::IconOffset::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setIconOffset(mbgl::style::PropertyValue>(arr)); + } + + float SymbolLayer::IconPadding::get() + { + return GetIconPadding(Impl()->getIconPadding()); + } + + System::Void SymbolLayer::IconPadding::set(float value) + { + SetIconPadding(Impl(), value); + } + + bool SymbolLayer::IconKeepUpright::get() + { + return GetBool(Impl()->getIconKeepUpright(), false); + } + + System::Void SymbolLayer::IconKeepUpright::set(bool value) + { + Impl()->setIconKeepUpright(mbgl::style::PropertyValue(value)); + } + + bool SymbolLayer::IconAllowOverlap::get() + { + return GetBool(Impl()->getIconAllowOverlap(), false); + } + + System::Void SymbolLayer::IconAllowOverlap::set(bool value) + { + Impl()->setIconAllowOverlap(mbgl::style::PropertyValue(value)); + } + + bool SymbolLayer::IconIgnorePlacement::get() + { + return GetBool(Impl()->getIconIgnorePlacement(), false); + } + + System::Void SymbolLayer::IconIgnorePlacement::set(bool value) + { + Impl()->setIconIgnorePlacement(mbgl::style::PropertyValue(value)); + } + + bool SymbolLayer::IconOptional::get() + { + return GetBool(Impl()->getIconOptional(), false); + } + + System::Void SymbolLayer::IconOptional::set(bool value) + { + Impl()->setIconOptional(mbgl::style::PropertyValue(value)); + } + + AlignmentType SymbolLayer::IconRotationAlignment::get() + { + auto& pv = Impl()->getIconRotationAlignment(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return AlignmentType::Auto; + } + + System::Void SymbolLayer::IconRotationAlignment::set(AlignmentType value) + { + Impl()->setIconRotationAlignment(mbgl::style::PropertyValue( + static_cast(value))); + } + + AlignmentType SymbolLayer::IconPitchAlignment::get() + { + auto& pv = Impl()->getIconPitchAlignment(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return AlignmentType::Auto; + } + + System::Void SymbolLayer::IconPitchAlignment::set(AlignmentType value) + { + Impl()->setIconPitchAlignment(mbgl::style::PropertyValue( + static_cast(value))); + } + + // ---- Layout: Text ---- + + System::String^ SymbolLayer::TextField::get() + { + auto& pv = Impl()->getTextField(); + if (pv.isConstant()) + return gcnew System::String(pv.asConstant().toString().c_str()); + return System::String::Empty; + } + + System::Void SymbolLayer::TextField::set(System::String^ value) + { + auto raw = msclr::interop::marshal_as(value); + Impl()->setTextField(mbgl::style::PropertyValue( + mbgl::style::expression::Formatted(raw.c_str()))); + } + + cli::array^ SymbolLayer::TextFont::get() + { + auto& pv = Impl()->getTextFont(); + if (pv.isConstant()) + { + const auto& vec = pv.asConstant(); + auto arr = gcnew cli::array(static_cast(vec.size())); + for (int i = 0; i < static_cast(vec.size()); ++i) + arr[i] = gcnew System::String(vec[i].c_str()); + return arr; + } + return gcnew cli::array(0); + } + + System::Void SymbolLayer::TextFont::set(cli::array^ value) + { + std::vector vec; + if (value != nullptr) + for each (System::String^ s in value) + vec.push_back(msclr::interop::marshal_as(s)); + Impl()->setTextFont(mbgl::style::PropertyValue>(vec)); + } + + float SymbolLayer::TextSize::get() + { + return GetFloat(Impl()->getTextSize(), 16.0f); + } + + System::Void SymbolLayer::TextSize::set(float value) + { + Impl()->setTextSize(mbgl::style::PropertyValue(value)); + } + + float SymbolLayer::TextMaxWidth::get() + { + return GetFloat(Impl()->getTextMaxWidth(), 10.0f); + } + + System::Void SymbolLayer::TextMaxWidth::set(float value) + { + Impl()->setTextMaxWidth(mbgl::style::PropertyValue(value)); + } + + float SymbolLayer::TextLineHeight::get() + { + return GetFloat(Impl()->getTextLineHeight(), 1.2f); + } + + System::Void SymbolLayer::TextLineHeight::set(float value) + { + Impl()->setTextLineHeight(mbgl::style::PropertyValue(value)); + } + + float SymbolLayer::TextLetterSpacing::get() + { + return GetFloat(Impl()->getTextLetterSpacing(), 0.0f); + } + + System::Void SymbolLayer::TextLetterSpacing::set(float value) + { + Impl()->setTextLetterSpacing(mbgl::style::PropertyValue(value)); + } + + TextJustifyType SymbolLayer::TextJustify::get() + { + auto& pv = Impl()->getTextJustify(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TextJustifyType::Center; + } + + System::Void SymbolLayer::TextJustify::set(TextJustifyType value) + { + Impl()->setTextJustify(mbgl::style::PropertyValue( + static_cast(value))); + } + + float SymbolLayer::TextRadialOffset::get() + { + return GetFloat(Impl()->getTextRadialOffset(), 0.0f); + } + + System::Void SymbolLayer::TextRadialOffset::set(float value) + { + Impl()->setTextRadialOffset(mbgl::style::PropertyValue(value)); + } + + SymbolAnchorType SymbolLayer::TextAnchor::get() + { + auto& pv = Impl()->getTextAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return SymbolAnchorType::Center; + } + + System::Void SymbolLayer::TextAnchor::set(SymbolAnchorType value) + { + Impl()->setTextAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + + float SymbolLayer::TextMaxAngle::get() + { + return GetFloat(Impl()->getTextMaxAngle(), 45.0f); + } + + System::Void SymbolLayer::TextMaxAngle::set(float value) + { + Impl()->setTextMaxAngle(mbgl::style::PropertyValue(value)); + } + + float SymbolLayer::TextRotate::get() + { + return GetFloat(Impl()->getTextRotate(), 0.0f); + } + + System::Void SymbolLayer::TextRotate::set(float value) + { + Impl()->setTextRotate(mbgl::style::PropertyValue(value)); + } + + float SymbolLayer::TextPadding::get() + { + return GetFloat(Impl()->getTextPadding(), 2.0f); + } + + System::Void SymbolLayer::TextPadding::set(float value) + { + Impl()->setTextPadding(mbgl::style::PropertyValue(value)); + } + + bool SymbolLayer::TextKeepUpright::get() + { + return GetBool(Impl()->getTextKeepUpright(), true); + } + + System::Void SymbolLayer::TextKeepUpright::set(bool value) + { + Impl()->setTextKeepUpright(mbgl::style::PropertyValue(value)); + } + + TextTransformType SymbolLayer::TextTransform::get() + { + auto& pv = Impl()->getTextTransform(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TextTransformType::None; + } + + System::Void SymbolLayer::TextTransform::set(TextTransformType value) + { + Impl()->setTextTransform(mbgl::style::PropertyValue( + static_cast(value))); + } + + cli::array^ SymbolLayer::TextOffset::get() + { + auto& pv = Impl()->getTextOffset(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void SymbolLayer::TextOffset::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setTextOffset(mbgl::style::PropertyValue>(arr)); + } + + bool SymbolLayer::TextAllowOverlap::get() + { + return GetBool(Impl()->getTextAllowOverlap(), false); + } + + System::Void SymbolLayer::TextAllowOverlap::set(bool value) + { + Impl()->setTextAllowOverlap(mbgl::style::PropertyValue(value)); + } + + bool SymbolLayer::TextIgnorePlacement::get() + { + return GetBool(Impl()->getTextIgnorePlacement(), false); + } + + System::Void SymbolLayer::TextIgnorePlacement::set(bool value) + { + Impl()->setTextIgnorePlacement(mbgl::style::PropertyValue(value)); + } + + bool SymbolLayer::TextOptional::get() + { + return GetBool(Impl()->getTextOptional(), false); + } + + System::Void SymbolLayer::TextOptional::set(bool value) + { + Impl()->setTextOptional(mbgl::style::PropertyValue(value)); + } + + AlignmentType SymbolLayer::TextRotationAlignment::get() + { + auto& pv = Impl()->getTextRotationAlignment(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return AlignmentType::Auto; + } + + System::Void SymbolLayer::TextRotationAlignment::set(AlignmentType value) + { + Impl()->setTextRotationAlignment(mbgl::style::PropertyValue( + static_cast(value))); + } + + AlignmentType SymbolLayer::TextPitchAlignment::get() + { + auto& pv = Impl()->getTextPitchAlignment(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return AlignmentType::Auto; + } + + System::Void SymbolLayer::TextPitchAlignment::set(AlignmentType value) + { + Impl()->setTextPitchAlignment(mbgl::style::PropertyValue( + static_cast(value))); + } + + // ---- Paint: Text ---- + + System::String^ SymbolLayer::TextColor::get() + { + return GetColor(Impl()->getTextColor(), "#000000"); + } + + System::Void SymbolLayer::TextColor::set(System::String^ value) + { + Impl()->setTextColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float SymbolLayer::TextOpacity::get() + { + return GetFloat(Impl()->getTextOpacity(), 1.0f); + } + + System::Void SymbolLayer::TextOpacity::set(float value) + { + Impl()->setTextOpacity(mbgl::style::PropertyValue(value)); + } + + System::String^ SymbolLayer::TextHaloColor::get() + { + return GetColor(Impl()->getTextHaloColor(), "rgba(0,0,0,0)"); + } + + System::Void SymbolLayer::TextHaloColor::set(System::String^ value) + { + Impl()->setTextHaloColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float SymbolLayer::TextHaloWidth::get() + { + return GetFloat(Impl()->getTextHaloWidth(), 0.0f); + } + + System::Void SymbolLayer::TextHaloWidth::set(float value) + { + Impl()->setTextHaloWidth(mbgl::style::PropertyValue(value)); + } + + float SymbolLayer::TextHaloBlur::get() + { + return GetFloat(Impl()->getTextHaloBlur(), 0.0f); + } + + System::Void SymbolLayer::TextHaloBlur::set(float value) + { + Impl()->setTextHaloBlur(mbgl::style::PropertyValue(value)); + } + + // ---- Paint: Icon ---- + + float SymbolLayer::IconOpacity::get() + { + return GetFloat(Impl()->getIconOpacity(), 1.0f); + } + + System::Void SymbolLayer::IconOpacity::set(float value) + { + Impl()->setIconOpacity(mbgl::style::PropertyValue(value)); + } + + System::String^ SymbolLayer::IconColor::get() + { + return GetColor(Impl()->getIconColor(), "#000000"); + } + + System::Void SymbolLayer::IconColor::set(System::String^ value) + { + Impl()->setIconColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + System::String^ SymbolLayer::IconHaloColor::get() + { + return GetColor(Impl()->getIconHaloColor(), "rgba(0,0,0,0)"); + } + + System::Void SymbolLayer::IconHaloColor::set(System::String^ value) + { + Impl()->setIconHaloColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float SymbolLayer::IconHaloWidth::get() + { + return GetFloat(Impl()->getIconHaloWidth(), 0.0f); + } + + System::Void SymbolLayer::IconHaloWidth::set(float value) + { + Impl()->setIconHaloWidth(mbgl::style::PropertyValue(value)); + } + + float SymbolLayer::IconHaloBlur::get() + { + return GetFloat(Impl()->getIconHaloBlur(), 0.0f); + } + + System::Void SymbolLayer::IconHaloBlur::set(float value) + { + Impl()->setIconHaloBlur(mbgl::style::PropertyValue(value)); + } + + IconTextFitType SymbolLayer::IconTextFit::get() + { + auto& pv = Impl()->getIconTextFit(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return IconTextFitType::None; + } + + System::Void SymbolLayer::IconTextFit::set(IconTextFitType value) + { + Impl()->setIconTextFit(mbgl::style::PropertyValue( + static_cast(value))); + } + + cli::array^ SymbolLayer::IconTextFitPadding::get() + { + auto& pv = Impl()->getIconTextFitPadding(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1], arr[2], arr[3] }; + } + return gcnew cli::array { 0.0f, 0.0f, 0.0f, 0.0f }; + } + + System::Void SymbolLayer::IconTextFitPadding::set(cli::array^ value) + { + if (value == nullptr || value->Length < 4) return; + std::array arr{ value[0], value[1], value[2], value[3] }; + Impl()->setIconTextFitPadding(mbgl::style::PropertyValue>(arr)); + } + + cli::array^ SymbolLayer::IconTranslate::get() + { + auto& pv = Impl()->getIconTranslate(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void SymbolLayer::IconTranslate::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setIconTranslate(mbgl::style::PropertyValue>(arr)); + } + + TranslateAnchorType SymbolLayer::IconTranslateAnchor::get() + { + auto& pv = Impl()->getIconTranslateAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TranslateAnchorType::Map; + } + + System::Void SymbolLayer::IconTranslateAnchor::set(TranslateAnchorType value) + { + Impl()->setIconTranslateAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + + cli::array^ SymbolLayer::TextTranslate::get() + { + auto& pv = Impl()->getTextTranslate(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void SymbolLayer::TextTranslate::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setTextTranslate(mbgl::style::PropertyValue>(arr)); + } + + TranslateAnchorType SymbolLayer::TextTranslateAnchor::get() + { + auto& pv = Impl()->getTextTranslateAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TranslateAnchorType::Map; + } + + System::Void SymbolLayer::TextTranslateAnchor::set(TranslateAnchorType value) + { + Impl()->setTextTranslateAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + + // ========================================================================= + // RasterLayer + // ========================================================================= + + RasterLayer::RasterLayer(mbgl::style::RasterLayer* layer) : Layer(layer) {} + + mbgl::style::RasterLayer* RasterLayer::Impl() + { + return static_cast(_layer); + } + + float RasterLayer::Opacity::get() + { + return GetFloat(Impl()->getRasterOpacity(), 1.0f); + } + + System::Void RasterLayer::Opacity::set(float value) + { + Impl()->setRasterOpacity(mbgl::style::PropertyValue(value)); + } + + float RasterLayer::BrightnessMin::get() + { + return GetFloat(Impl()->getRasterBrightnessMin(), 0.0f); + } + + System::Void RasterLayer::BrightnessMin::set(float value) + { + Impl()->setRasterBrightnessMin(mbgl::style::PropertyValue(value)); + } + + float RasterLayer::BrightnessMax::get() + { + return GetFloat(Impl()->getRasterBrightnessMax(), 1.0f); + } + + System::Void RasterLayer::BrightnessMax::set(float value) + { + Impl()->setRasterBrightnessMax(mbgl::style::PropertyValue(value)); + } + + float RasterLayer::Contrast::get() + { + return GetFloat(Impl()->getRasterContrast(), 0.0f); + } + + System::Void RasterLayer::Contrast::set(float value) + { + Impl()->setRasterContrast(mbgl::style::PropertyValue(value)); + } + + float RasterLayer::HueRotate::get() + { + return GetFloat(Impl()->getRasterHueRotate(), 0.0f); + } + + System::Void RasterLayer::HueRotate::set(float value) + { + Impl()->setRasterHueRotate(mbgl::style::PropertyValue(value)); + } + + float RasterLayer::Saturation::get() + { + return GetFloat(Impl()->getRasterSaturation(), 0.0f); + } + + System::Void RasterLayer::Saturation::set(float value) + { + Impl()->setRasterSaturation(mbgl::style::PropertyValue(value)); + } + + float RasterLayer::FadeDuration::get() + { + return GetFloat(Impl()->getRasterFadeDuration(), 300.0f); + } + + System::Void RasterLayer::FadeDuration::set(float value) + { + Impl()->setRasterFadeDuration(mbgl::style::PropertyValue(value)); + } + + RasterResamplingType RasterLayer::Resampling::get() + { + auto& pv = Impl()->getRasterResampling(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return RasterResamplingType::Linear; + } + + System::Void RasterLayer::Resampling::set(RasterResamplingType value) + { + Impl()->setRasterResampling(mbgl::style::PropertyValue( + static_cast(value))); + } + + // ========================================================================= + // BackgroundLayer + // ========================================================================= + + BackgroundLayer::BackgroundLayer(mbgl::style::BackgroundLayer* layer) : Layer(layer) {} + + mbgl::style::BackgroundLayer* BackgroundLayer::Impl() + { + return static_cast(_layer); + } + + System::String^ BackgroundLayer::Color::get() + { + return GetColor(Impl()->getBackgroundColor(), "#000000"); + } + + System::Void BackgroundLayer::Color::set(System::String^ value) + { + Impl()->setBackgroundColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float BackgroundLayer::Opacity::get() + { + return GetFloat(Impl()->getBackgroundOpacity(), 1.0f); + } + + System::Void BackgroundLayer::Opacity::set(float value) + { + Impl()->setBackgroundOpacity(mbgl::style::PropertyValue(value)); + } + + System::String^ BackgroundLayer::Pattern::get() + { + auto& pv = Impl()->getBackgroundPattern(); + if (pv.isConstant()) + return gcnew System::String(pv.asConstant().id().c_str()); + return System::String::Empty; + } + + System::Void BackgroundLayer::Pattern::set(System::String^ value) + { + auto raw = msclr::interop::marshal_as(value); + Impl()->setBackgroundPattern(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + + // ========================================================================= + // HeatmapLayer + // ========================================================================= + + HeatmapLayer::HeatmapLayer(mbgl::style::HeatmapLayer* layer) : Layer(layer) {} + + mbgl::style::HeatmapLayer* HeatmapLayer::Impl() + { + return static_cast(_layer); + } + + float HeatmapLayer::Opacity::get() + { + return GetFloat(Impl()->getHeatmapOpacity(), 1.0f); + } + + System::Void HeatmapLayer::Opacity::set(float value) + { + Impl()->setHeatmapOpacity(mbgl::style::PropertyValue(value)); + } + + float HeatmapLayer::Radius::get() + { + return GetFloat(Impl()->getHeatmapRadius(), 30.0f); + } + + System::Void HeatmapLayer::Radius::set(float value) + { + Impl()->setHeatmapRadius(mbgl::style::PropertyValue(value)); + } + + float HeatmapLayer::Intensity::get() + { + return GetFloat(Impl()->getHeatmapIntensity(), 1.0f); + } + + System::Void HeatmapLayer::Intensity::set(float value) + { + Impl()->setHeatmapIntensity(mbgl::style::PropertyValue(value)); + } + + float HeatmapLayer::Weight::get() + { + return GetFloat(Impl()->getHeatmapWeight(), 1.0f); + } + + System::Void HeatmapLayer::Weight::set(float value) + { + Impl()->setHeatmapWeight(mbgl::style::PropertyValue(value)); + } + + System::String^ HeatmapLayer::Color::get() + { + const auto& pv = Impl()->getHeatmapColor(); + if (!pv.isUndefined()) + { + mbgl::Value serialized = pv.getExpression().serialize(); + return msclr::interop::marshal_as(ValueToJsonString(serialized)); + } + return System::String::Empty; + } + + System::Void HeatmapLayer::Color::set(System::String^ value) + { + if (value == nullptr || value->Length == 0) return; + std::string json = msclr::interop::marshal_as(value); + mbgl::JSDocument doc; + doc.Parse(json.c_str()); + if (doc.HasParseError()) + throw gcnew System::ArgumentException("Invalid heatmap color JSON: parse error"); + mbgl::style::conversion::Error error; + const mbgl::JSValue& jsRoot = doc; + auto colorRamp = mbgl::style::conversion::convert( + mbgl::style::conversion::Convertible(&jsRoot), error); + if (!colorRamp) + throw gcnew System::ArgumentException( + gcnew System::String(("Invalid heatmap color expression: " + error.message).c_str())); + Impl()->setHeatmapColor(*colorRamp); + } + // ========================================================================= + + HillshadeLayer::HillshadeLayer(mbgl::style::HillshadeLayer* layer) : Layer(layer) {} + + mbgl::style::HillshadeLayer* HillshadeLayer::Impl() + { + return static_cast(_layer); + } + + float HillshadeLayer::Exaggeration::get() + { + return GetFloat(Impl()->getHillshadeExaggeration(), 0.5f); + } + + System::Void HillshadeLayer::Exaggeration::set(float value) + { + Impl()->setHillshadeExaggeration(mbgl::style::PropertyValue(value)); + } + + float HillshadeLayer::IlluminationDirection::get() + { + return GetIlluminationDirection(Impl()->getHillshadeIlluminationDirection()); + } + + System::Void HillshadeLayer::IlluminationDirection::set(float value) + { + SetIlluminationDirection(Impl(), value); + } + + System::String^ HillshadeLayer::ShadowColor::get() + { + return GetColorAgnostic(Impl()->getHillshadeShadowColor(), "#000000"); + } + + System::Void HillshadeLayer::ShadowColor::set(System::String^ value) + { + SetHillshadeShadowColor(Impl(), ParseColor(value)); + } + + System::String^ HillshadeLayer::HighlightColor::get() + { + return GetColorAgnostic(Impl()->getHillshadeHighlightColor(), "#ffffff"); + } + + System::Void HillshadeLayer::HighlightColor::set(System::String^ value) + { + SetHillshadeHighlightColor(Impl(), ParseColor(value)); + } + + System::String^ HillshadeLayer::AccentColor::get() + { + return GetColor(Impl()->getHillshadeAccentColor(), "#000000"); + } + + System::Void HillshadeLayer::AccentColor::set(System::String^ value) + { + Impl()->setHillshadeAccentColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + HillshadeIlluminationAnchorType HillshadeLayer::IlluminationAnchor::get() + { + auto& pv = Impl()->getHillshadeIlluminationAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return HillshadeIlluminationAnchorType::Viewport; + } + + System::Void HillshadeLayer::IlluminationAnchor::set(HillshadeIlluminationAnchorType value) + { + Impl()->setHillshadeIlluminationAnchor( + mbgl::style::PropertyValue( + static_cast(value))); + } + + // ========================================================================= + // FillExtrusionLayer + // ========================================================================= + + FillExtrusionLayer::FillExtrusionLayer(mbgl::style::FillExtrusionLayer* layer) : Layer(layer) {} + + mbgl::style::FillExtrusionLayer* FillExtrusionLayer::Impl() + { + return static_cast(_layer); + } + + System::String^ FillExtrusionLayer::Color::get() + { + return GetColor(Impl()->getFillExtrusionColor(), "#000000"); + } + + System::Void FillExtrusionLayer::Color::set(System::String^ value) + { + Impl()->setFillExtrusionColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float FillExtrusionLayer::Opacity::get() + { + return GetFloat(Impl()->getFillExtrusionOpacity(), 1.0f); + } + + System::Void FillExtrusionLayer::Opacity::set(float value) + { + Impl()->setFillExtrusionOpacity(mbgl::style::PropertyValue(value)); + } + + float FillExtrusionLayer::Height::get() + { + return GetFloat(Impl()->getFillExtrusionHeight(), 0.0f); + } + + System::Void FillExtrusionLayer::Height::set(float value) + { + Impl()->setFillExtrusionHeight(mbgl::style::PropertyValue(value)); + } + + float FillExtrusionLayer::Base::get() + { + return GetFloat(Impl()->getFillExtrusionBase(), 0.0f); + } + + System::Void FillExtrusionLayer::Base::set(float value) + { + Impl()->setFillExtrusionBase(mbgl::style::PropertyValue(value)); + } + + bool FillExtrusionLayer::VerticalGradient::get() + { + return GetBool(Impl()->getFillExtrusionVerticalGradient(), true); + } + + System::Void FillExtrusionLayer::VerticalGradient::set(bool value) + { + Impl()->setFillExtrusionVerticalGradient(mbgl::style::PropertyValue(value)); + } + + System::String^ FillExtrusionLayer::Pattern::get() + { + auto& pv = Impl()->getFillExtrusionPattern(); + if (pv.isConstant()) + return gcnew System::String(pv.asConstant().id().c_str()); + return System::String::Empty; + } + + System::Void FillExtrusionLayer::Pattern::set(System::String^ value) + { + auto raw = msclr::interop::marshal_as(value); + Impl()->setFillExtrusionPattern(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + + cli::array^ FillExtrusionLayer::Translate::get() + { + auto& pv = Impl()->getFillExtrusionTranslate(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void FillExtrusionLayer::Translate::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setFillExtrusionTranslate(mbgl::style::PropertyValue>(arr)); + } + + TranslateAnchorType FillExtrusionLayer::TranslateAnchor::get() + { + auto& pv = Impl()->getFillExtrusionTranslateAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TranslateAnchorType::Map; + } + + System::Void FillExtrusionLayer::TranslateAnchor::set(TranslateAnchorType value) + { + Impl()->setFillExtrusionTranslateAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + + // ========================================================================= + // ColorReliefLayer + // ========================================================================= + + ColorReliefLayer::ColorReliefLayer(mbgl::style::ColorReliefLayer* layer) : Layer(layer) {} + + mbgl::style::ColorReliefLayer* ColorReliefLayer::Impl() + { + return static_cast(_layer); + } + + float ColorReliefLayer::Opacity::get() + { + return GetFloat(Impl()->getColorReliefOpacity(), 1.0f); + } + + System::Void ColorReliefLayer::Opacity::set(float value) + { + Impl()->setColorReliefOpacity(mbgl::style::PropertyValue(value)); + } + + System::String^ ColorReliefLayer::ColorRamp::get() + { + const auto& pv = Impl()->getColorReliefColor(); + if (!pv.isUndefined()) + { + mbgl::Value serialized = pv.getExpression().serialize(); + return msclr::interop::marshal_as(ValueToJsonString(serialized)); + } + return System::String::Empty; + } + + System::Void ColorReliefLayer::ColorRamp::set(System::String^ value) + { + if (value == nullptr || value->Length == 0) + return; + + std::string json = msclr::interop::marshal_as(value); + mbgl::JSDocument doc; + doc.Parse(json.c_str()); + if (doc.HasParseError()) + throw gcnew System::ArgumentException("Invalid color ramp JSON: parse error"); + + mbgl::style::conversion::Error error; + const mbgl::JSValue& jsRoot = doc; + auto colorRamp = mbgl::style::conversion::convert( + mbgl::style::conversion::Convertible(&jsRoot), error); + if (!colorRamp) + throw gcnew System::ArgumentException( + gcnew System::String(("Invalid color ramp expression: " + error.message).c_str())); + + Impl()->setColorReliefColor(*colorRamp); + } + + // ========================================================================= + // LocationIndicatorLayer + // ========================================================================= + + LocationIndicatorLayer::LocationIndicatorLayer(mbgl::style::LocationIndicatorLayer* layer) : Layer(layer) {} + + mbgl::style::LocationIndicatorLayer* LocationIndicatorLayer::Impl() + { + return static_cast(_layer); + } + + // ---- Layout: image sprite names ---------------------------------------- + + System::String^ LocationIndicatorLayer::BearingImage::get() + { + const auto& pv = Impl()->getBearingImage(); + if (pv.isConstant()) return msclr::interop::marshal_as(pv.asConstant().id()); + return System::String::Empty; + } + System::Void LocationIndicatorLayer::BearingImage::set(System::String^ value) + { + System::String^ safeValue = value != nullptr ? value : System::String::Empty; + std::string raw = msclr::interop::marshal_as(safeValue); + Impl()->setBearingImage(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + + System::String^ LocationIndicatorLayer::ShadowImage::get() + { + const auto& pv = Impl()->getShadowImage(); + if (pv.isConstant()) return msclr::interop::marshal_as(pv.asConstant().id()); + return System::String::Empty; + } + System::Void LocationIndicatorLayer::ShadowImage::set(System::String^ value) + { + System::String^ safeValue = value != nullptr ? value : System::String::Empty; + std::string raw = msclr::interop::marshal_as(safeValue); + Impl()->setShadowImage(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + + System::String^ LocationIndicatorLayer::TopImage::get() + { + const auto& pv = Impl()->getTopImage(); + if (pv.isConstant()) return msclr::interop::marshal_as(pv.asConstant().id()); + return System::String::Empty; + } + System::Void LocationIndicatorLayer::TopImage::set(System::String^ value) + { + System::String^ safeValue = value != nullptr ? value : System::String::Empty; + std::string raw = msclr::interop::marshal_as(safeValue); + Impl()->setTopImage(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + + // ---- Paint: floats ----------------------------------------------------- + + float LocationIndicatorLayer::AccuracyRadius::get() + { + return GetFloat(Impl()->getAccuracyRadius(), 0.0f); + } + System::Void LocationIndicatorLayer::AccuracyRadius::set(float value) + { + Impl()->setAccuracyRadius(mbgl::style::PropertyValue(value)); + } + + float LocationIndicatorLayer::Bearing::get() + { + const auto& pv = Impl()->getBearing(); + if (pv.isConstant()) return (float)pv.asConstant().getAngle(); + return 0.0f; + } + System::Void LocationIndicatorLayer::Bearing::set(float value) + { + Impl()->setBearing(mbgl::style::PropertyValue( + mbgl::style::Rotation((double)value))); + } + + float LocationIndicatorLayer::BearingImageSize::get() + { + return GetFloat(Impl()->getBearingImageSize(), 1.0f); + } + System::Void LocationIndicatorLayer::BearingImageSize::set(float value) + { + Impl()->setBearingImageSize(mbgl::style::PropertyValue(value)); + } + + float LocationIndicatorLayer::ImageTiltDisplacement::get() + { + return GetFloat(Impl()->getImageTiltDisplacement(), 0.0f); + } + System::Void LocationIndicatorLayer::ImageTiltDisplacement::set(float value) + { + Impl()->setImageTiltDisplacement(mbgl::style::PropertyValue(value)); + } + + float LocationIndicatorLayer::PerspectiveCompensation::get() + { + return GetFloat(Impl()->getPerspectiveCompensation(), 0.85f); + } + System::Void LocationIndicatorLayer::PerspectiveCompensation::set(float value) + { + Impl()->setPerspectiveCompensation(mbgl::style::PropertyValue(value)); + } + + float LocationIndicatorLayer::ShadowImageSize::get() + { + return GetFloat(Impl()->getShadowImageSize(), 1.0f); + } + System::Void LocationIndicatorLayer::ShadowImageSize::set(float value) + { + Impl()->setShadowImageSize(mbgl::style::PropertyValue(value)); + } + + float LocationIndicatorLayer::TopImageSize::get() + { + return GetFloat(Impl()->getTopImageSize(), 1.0f); + } + System::Void LocationIndicatorLayer::TopImageSize::set(float value) + { + Impl()->setTopImageSize(mbgl::style::PropertyValue(value)); + } + + // ---- Paint: colors ----------------------------------------------------- + + System::String^ LocationIndicatorLayer::AccuracyRadiusBorderColor::get() + { + return GetColor(Impl()->getAccuracyRadiusBorderColor(), "rgba(0,0,0,0)"); + } + System::Void LocationIndicatorLayer::AccuracyRadiusBorderColor::set(System::String^ value) + { + Impl()->setAccuracyRadiusBorderColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + System::String^ LocationIndicatorLayer::AccuracyRadiusColor::get() + { + return GetColor(Impl()->getAccuracyRadiusColor(), "rgba(0,0,0,0)"); + } + System::Void LocationIndicatorLayer::AccuracyRadiusColor::set(System::String^ value) + { + Impl()->setAccuracyRadiusColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + // ---- Paint: location (lat/lng/alt) ------------------------------------- + + cli::array^ LocationIndicatorLayer::Location::get() + { + const auto& pv = Impl()->getLocation(); + if (pv.isConstant()) + { + const auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1], arr[2] }; + } + return gcnew cli::array { 0.0, 0.0, 0.0 }; + } + System::Void LocationIndicatorLayer::Location::set(cli::array^ value) + { + if (value == nullptr || value->Length < 3) return; + std::array arr{ value[0], value[1], value[2] }; + Impl()->setLocation(mbgl::style::PropertyValue>(arr)); + } +} diff --git a/src/Light.cpp b/src/Light.cpp new file mode 100644 index 0000000..bde2480 --- /dev/null +++ b/src/Light.cpp @@ -0,0 +1,98 @@ +#include "Light.h" +#include "Convert.h" +#include +#include +#include +#include + +namespace DOTNET_NAMESPACE +{ + Light::Light(mbgl::style::Light* light) + : _light(light) + { + } + + // ------------------------------------------------------------------------- + // Anchor + // ------------------------------------------------------------------------- + + LightAnchor Light::Anchor::get() + { + auto pv = _light->getAnchor(); + if (pv.isConstant()) { + return pv.asConstant() == mbgl::style::LightAnchorType::Viewport + ? LightAnchor::Viewport + : LightAnchor::Map; + } + // Default + return _light->getDefaultAnchor() == mbgl::style::LightAnchorType::Viewport + ? LightAnchor::Viewport + : LightAnchor::Map; + } + + System::Void Light::Anchor::set(LightAnchor value) + { + _light->setAnchor(mbgl::style::PropertyValue( + value == LightAnchor::Viewport + ? mbgl::style::LightAnchorType::Viewport + : mbgl::style::LightAnchorType::Map)); + } + + // ------------------------------------------------------------------------- + // Color + // ------------------------------------------------------------------------- + + System::String^ Light::Color::get() + { + auto pv = _light->getColor(); + if (pv.isConstant()) { + return gcnew System::String(pv.asConstant().stringify().c_str()); + } + return gcnew System::String(_light->getDefaultColor().stringify().c_str()); + } + + System::Void Light::Color::set(System::String^ value) + { + auto parsed = mbgl::Color::parse(Convert::ToStdString(value)); + if (parsed) { + _light->setColor(mbgl::style::PropertyValue(*parsed)); + } + // Silently ignore unparseable colors (same pattern as Layers.cpp) + } + + // ------------------------------------------------------------------------- + // Intensity + // ------------------------------------------------------------------------- + + System::Single Light::Intensity::get() + { + auto pv = _light->getIntensity(); + return pv.isConstant() ? pv.asConstant() : _light->getDefaultIntensity(); + } + + System::Void Light::Intensity::set(System::Single value) + { + _light->setIntensity(mbgl::style::PropertyValue(value)); + } + + // ------------------------------------------------------------------------- + // Position + // ------------------------------------------------------------------------- + + LightPosition Light::Position::get() + { + auto pv = _light->getPosition(); + mbgl::style::Position pos = pv.isConstant() + ? pv.asConstant() + : _light->getDefaultPosition(); + auto sph = pos.getSpherical(); + return LightPosition(sph[0], sph[1], sph[2]); + } + + System::Void Light::Position::set(LightPosition value) + { + std::array sph = { value.Radial, value.Azimuthal, value.Polar }; + _light->setPosition(mbgl::style::PropertyValue( + mbgl::style::Position(sph))); + } +} diff --git a/src/Map.cpp b/src/Map.cpp index 66782c6..c9b445c 100644 --- a/src/Map.cpp +++ b/src/Map.cpp @@ -1,4 +1,4 @@ -#include "AnimationOptions.h" +#include "AnimationOptions.h" #include "BoundOptions.h" #include "CameraOptions.h" #include "ClientOptions.h" @@ -21,7 +21,7 @@ namespace { using namespace ::DOTNET_NAMESPACE; - + void StillImageCallbackHandler(std::exception_ptr eptr, Map::StillImageCallback^ callback) { msclr::gcroot exception = nullptr; @@ -40,6 +40,14 @@ namespace callback->Invoke(exception); } + + mbgl::Map::StillImageCallback MakeStillImageCallback(Map::StillImageCallback^ callback) + { + auto ref = msclr::gcroot(callback); + return [ref](std::exception_ptr eptr) mutable { + StillImageCallbackHandler(eptr, ref); + }; + } } namespace DOTNET_NAMESPACE @@ -92,11 +100,7 @@ namespace DOTNET_NAMESPACE System::Void Map::RenderStill(StillImageCallback^ callback) { - NativePointer->renderStill(std::bind( - &StillImageCallbackHandler, - std::placeholders::_1, - msclr::gcroot(callback) - )); + NativePointer->renderStill(MakeStillImageCallback(callback)); } System::Void Map::RenderStill(CameraOptions^ camera, MapDebugOptions debugOptions, StillImageCallback^ callback) @@ -104,11 +108,7 @@ namespace DOTNET_NAMESPACE NativePointer->renderStill( *camera->NativePointer, (mbgl::MapDebugOptions)debugOptions, - std::bind( - &StillImageCallbackHandler, - std::placeholders::_1, - msclr::gcroot(callback) - ) + MakeStillImageCallback(callback) ); } @@ -277,7 +277,7 @@ namespace DOTNET_NAMESPACE { result.emplace_back(*latLng->NativePointer); } - + return gcnew CameraOptions(CameraOptions::CreateNativePointerHolder( NativePointer->cameraForLatLngs( result, @@ -374,6 +374,16 @@ namespace DOTNET_NAMESPACE NativePointer->setSize(*size->NativePointer); } + System::Void Map::SetFrustumOffset(EdgeInsets^ offset) + { + NativePointer->setFrustumOffset(*offset->NativePointer); + } + + EdgeInsets^ Map::GetFrustumOffset() + { + return gcnew EdgeInsets(EdgeInsets::CreateNativePointerHolder(NativePointer->getFrustumOffset())); + } + ScreenCoordinate Map::PixelForLatLng(LatLng^ latLng) { return ScreenCoordinate(NativePointer->pixelForLatLng(*latLng->NativePointer)); @@ -435,7 +445,7 @@ namespace DOTNET_NAMESPACE { _Style = gcnew Style_(Style_::CreateNativePointerHolder(&NativePointer->getStyle(), false)); } - + return _Style; } @@ -525,6 +535,66 @@ namespace DOTNET_NAMESPACE return NativePointer->isFullyLoaded(); } + System::Boolean Map::IsRenderingStatsViewEnabled::get() + { + return NativePointer->isRenderingStatsViewEnabled(); + } + + System::Void Map::IsRenderingStatsViewEnabled::set(System::Boolean value) + { + NativePointer->enableRenderingStatsView(value); + } + + System::Double Map::TileLodMinRadius::get() + { + return NativePointer->getTileLodMinRadius(); + } + + System::Void Map::TileLodMinRadius::set(System::Double value) + { + NativePointer->setTileLodMinRadius(value); + } + + System::Double Map::TileLodScale::get() + { + return NativePointer->getTileLodScale(); + } + + System::Void Map::TileLodScale::set(System::Double value) + { + NativePointer->setTileLodScale(value); + } + + System::Double Map::TileLodPitchThreshold::get() + { + return NativePointer->getTileLodPitchThreshold(); + } + + System::Void Map::TileLodPitchThreshold::set(System::Double value) + { + NativePointer->setTileLodPitchThreshold(value); + } + + System::Double Map::TileLodZoomShift::get() + { + return NativePointer->getTileLodZoomShift(); + } + + System::Void Map::TileLodZoomShift::set(System::Double value) + { + NativePointer->setTileLodZoomShift(value); + } + + TileLodMode Map::TileLodMode_::get() + { + return (TileLodMode)NativePointer->getTileLodMode(); + } + + System::Void Map::TileLodMode_::set(TileLodMode value) + { + NativePointer->setTileLodMode((mbgl::TileLodMode)value); + } + FreeCameraOptions_^ Map::FreeCameraOptions::get() { return gcnew FreeCameraOptions_(FreeCameraOptions_::CreateNativePointerHolder(NativePointer->getFreeCameraOptions())); diff --git a/src/MapLibreLogger.cpp b/src/MapLibreLogger.cpp new file mode 100644 index 0000000..75d3209 --- /dev/null +++ b/src/MapLibreLogger.cpp @@ -0,0 +1,50 @@ +#include "MapLibreLogger.h" +#include "Convert.h" +#include +#include + +namespace +{ + class ManagedLogObserver : public mbgl::Log::Observer + { + private: + msclr::gcroot _handler; + + public: + ManagedLogObserver(msclr::gcroot handler) + : _handler(handler) {} + + bool onRecord(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string& msg) override + { + try + { + if ((DOTNET_NAMESPACE::MapLibreLogHandler^)_handler != nullptr) + { + ((DOTNET_NAMESPACE::MapLibreLogHandler^)_handler)( + (DOTNET_NAMESPACE::LogEventSeverity)severity, + (DOTNET_NAMESPACE::LogEvent)event, + code, + DOTNET_NAMESPACE::Convert::ToSystemString(msg) + ); + } + } + catch (...) {} + return true; + } + }; +} + +namespace DOTNET_NAMESPACE +{ + void MapLibreLogger::SetObserver(MapLibreLogHandler^ handler) + { + if (handler == nullptr) + { + mbgl::Log::setObserver(nullptr); + return; + } + + auto msclrHandler = msclr::gcroot(handler); + mbgl::Log::setObserver(std::make_unique(msclrHandler)); + } +} diff --git a/src/MapObserver.cpp b/src/MapObserver.cpp index 585caaa..fa17783 100644 --- a/src/MapObserver.cpp +++ b/src/MapObserver.cpp @@ -1,7 +1,8 @@ -#include "MapObserver.h" +#include "MapObserver.h" #include "ShaderRegistry.h" #include #include +#include namespace DOTNET_NAMESPACE { @@ -19,13 +20,16 @@ namespace DOTNET_NAMESPACE NativePointer->WillStartRenderingMapHandler = gcnew WillStartRenderingMapHandler(this, &MapObserver::onWillStartRenderingMap); NativePointer->DidFinishRenderingMapHandler = gcnew DidFinishRenderingMapHandler(this, &MapObserver::onDidFinishRenderingMap); NativePointer->DidFinishLoadingStyleHandler = gcnew DidFinishLoadingStyleHandler(this, &MapObserver::onDidFinishLoadingStyle); - //NativePointer->SourceChangedHandler = gcnew SourceChangedHandler(this, &MapObserver::onSourceChanged); + NativePointer->SourceChangedHandler = gcnew SourceChangedHandler(this, &MapObserver::onSourceChanged); NativePointer->DidBecomeIdleHandler = gcnew DidBecomeIdleHandler(this, &MapObserver::onDidBecomeIdle); NativePointer->StyleImageMissingHandler = gcnew StyleImageMissingHandler(this, &MapObserver::onStyleImageMissing); NativePointer->CanRemoveUnusedStyleImageHandler = gcnew CanRemoveUnusedStyleImageHandler(this, &MapObserver::onCanRemoveUnusedStyleImage); NativePointer->RegisterShadersHandler = gcnew RegisterShadersHandler(this, &MapObserver::onRegisterShaders); + NativePointer->PreCompileShaderHandler = gcnew PreCompileShaderHandler(this, &MapObserver::onPreCompileShader); + NativePointer->PostCompileShaderHandler = gcnew PostCompileShaderHandler(this, &MapObserver::onPostCompileShader); + NativePointer->ShaderCompileFailedHandler = gcnew ShaderCompileFailedHandler(this, &MapObserver::onShaderCompileFailed); } - + MapObserver::~MapObserver() { } @@ -85,13 +89,10 @@ namespace DOTNET_NAMESPACE DidFinishLoadingStyle(); } - // TODO: implement the managed version - /* - System::Void MapObserver::onSourceChanged(Source^ source) + System::Void MapObserver::onSourceChanged(System::String^ id, SourceType type) { - SourceChanged(source); + SourceChanged(id, type); } - */ System::Void MapObserver::onDidBecomeIdle() { @@ -114,6 +115,21 @@ namespace DOTNET_NAMESPACE RegisterShaders(shaderRegistry); } + System::Void MapObserver::onPreCompileShader(System::UInt32 shaderType, System::Byte backendType, System::String^ defines) + { + PreCompileShader(shaderType, backendType, defines); + } + + System::Void MapObserver::onPostCompileShader(System::UInt32 shaderType, System::Byte backendType, System::String^ defines) + { + PostCompileShader(shaderType, backendType, defines); + } + + System::Void MapObserver::onShaderCompileFailed(System::UInt32 shaderType, System::Byte backendType, System::String^ defines) + { + ShaderCompileFailed(shaderType, backendType, defines); + } + NativeMapObserver::NativeMapObserver() { } @@ -178,7 +194,7 @@ namespace DOTNET_NAMESPACE } } - void NativeMapObserver::onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus status) + void NativeMapObserver::onDidFinishRenderingFrame(const mbgl::MapObserver::RenderFrameStatus& status) { if (static_cast(DidFinishRenderingFrameHandler)) { @@ -219,12 +235,12 @@ namespace DOTNET_NAMESPACE void NativeMapObserver::onSourceChanged(mbgl::style::Source& source) { - /* if (static_cast(SourceChangedHandler)) { - SourceChangedHandler->Invoke(gcnew Source(Source::CreateNativePointerHolder(source))); + SourceChangedHandler->Invoke( + Convert::ToSystemString(source.getID()), + (DOTNET_NAMESPACE::SourceType)source.getType()); } - */ } void NativeMapObserver::onDidBecomeIdle() @@ -260,4 +276,28 @@ namespace DOTNET_NAMESPACE RegisterShadersHandler->Invoke(gcnew ShaderRegistry(ShaderRegistry::CreateNativePointerHolder(&shaderRegistry, false))); } } + + void NativeMapObserver::onPreCompileShader(mbgl::shaders::BuiltIn type, mbgl::gfx::Backend::Type backend, const std::string& defines) + { + if (static_cast(PreCompileShaderHandler)) + { + PreCompileShaderHandler->Invoke(static_cast(type), static_cast(backend), Convert::ToSystemString(defines)); + } + } + + void NativeMapObserver::onPostCompileShader(mbgl::shaders::BuiltIn type, mbgl::gfx::Backend::Type backend, const std::string& defines) + { + if (static_cast(PostCompileShaderHandler)) + { + PostCompileShaderHandler->Invoke(static_cast(type), static_cast(backend), Convert::ToSystemString(defines)); + } + } + + void NativeMapObserver::onShaderCompileFailed(mbgl::shaders::BuiltIn type, mbgl::gfx::Backend::Type backend, const std::string& defines) + { + if (static_cast(ShaderCompileFailedHandler)) + { + ShaderCompileFailedHandler->Invoke(static_cast(type), static_cast(backend), Convert::ToSystemString(defines)); + } + } } diff --git a/src/MapOptions.cpp b/src/MapOptions.cpp index af57af2..44ff463 100644 --- a/src/MapOptions.cpp +++ b/src/MapOptions.cpp @@ -1,4 +1,4 @@ -#include "MapOptions.h" +#include "MapOptions.h" #include "Size.h" #include @@ -19,14 +19,14 @@ namespace DOTNET_NAMESPACE MapOptions^ MapOptions::WithMapMode(DOTNET_NAMESPACE::MapMode mode) { NativePointer->withMapMode((mbgl::MapMode)mode); - + return this; } MapOptions^ MapOptions::WithConstrainMode(DOTNET_NAMESPACE::ConstrainMode mode) { NativePointer->withConstrainMode((mbgl::ConstrainMode)mode); - + return this; } @@ -47,21 +47,21 @@ namespace DOTNET_NAMESPACE MapOptions^ MapOptions::WithNorthOrientation(DOTNET_NAMESPACE::NorthOrientation orientation) { NativePointer->withNorthOrientation((mbgl::NorthOrientation)orientation); - + return this; } MapOptions^ MapOptions::WithSize(Size_^ size) { NativePointer->withSize(*size->NativePointer); - + return this; } MapOptions^ MapOptions::WithPixelRatio(System::Single ratio) { NativePointer->withPixelRatio(ratio); - + return this; } diff --git a/src/OverscaledTileID.cpp b/src/OverscaledTileID.cpp index 5d84255..1addabe 100644 --- a/src/OverscaledTileID.cpp +++ b/src/OverscaledTileID.cpp @@ -1,4 +1,4 @@ -#include "CanonicalTileID.h" +#include "CanonicalTileID.h" #include "OverscaledTileID.h" #include "UnwrappedTileID.h" #include @@ -9,7 +9,7 @@ namespace DOTNET_NAMESPACE : NativeWrapper(new mbgl::OverscaledTileID(overscaledZ, wrap, *canonical->NativePointer)) { } - + OverscaledTileID::OverscaledTileID(System::Byte overscaledZ, System::Int16 wrap, System::Byte z, System::UInt32 x, System::UInt32 y) : NativeWrapper(new mbgl::OverscaledTileID(overscaledZ, wrap, z, x, y)) { diff --git a/src/Projection.cpp b/src/Projection.cpp index 95aec8e..65ab7a5 100644 --- a/src/Projection.cpp +++ b/src/Projection.cpp @@ -1,82 +1,82 @@ -#include "LatLng.h" +#include "LatLng.h" #include "Projection.h" #include namespace DOTNET_NAMESPACE { - System::Double Projection::WorldSize(System::Double scale) - { - return mbgl::Projection::worldSize(scale); - } - - System::Double Projection::GetMetersPerPixelAtLatitude(System::Double lat, System::Double zoom) - { - return mbgl::Projection::getMetersPerPixelAtLatitude(lat, zoom); - } - - ProjectedMeters^ Projection::ProjectedMetersForLatLng(LatLng^ latLng) - { - return gcnew ProjectedMeters(ProjectedMeters::CreateNativePointerHolder(mbgl::Projection::projectedMetersForLatLng(*latLng->NativePointer))); - } - - LatLng^ Projection::LatLngForProjectedMeters(ProjectedMeters^ projectedMeters) - { - return gcnew LatLng(LatLng::CreateNativePointerHolder(mbgl::Projection::latLngForProjectedMeters(*projectedMeters->NativePointer))); - } - - PointDouble Projection::Project(LatLng^ latLng, System::Double scale) - { - return PointDouble(mbgl::Projection::project(*latLng->NativePointer, scale)); - } - - PointDouble Projection::Project(LatLng^ latLng, System::Int32 zoom) - { - return PointDouble(mbgl::Projection::project(*latLng->NativePointer, zoom)); - } - - LatLng^ Projection::Unproject(PointDouble p, System::Double scale) - { - return gcnew LatLng(LatLng::CreateNativePointerHolder(mbgl::Projection::unproject(mbgl::ScreenCoordinate(p.X, p.Y), scale))); - } - - LatLng^ Projection::Unproject(PointDouble p, System::Double scale, LatLng::WrapMode wrapMode) - { - return gcnew LatLng(LatLng::CreateNativePointerHolder(mbgl::Projection::unproject(mbgl::ScreenCoordinate(p.X, p.Y), scale, (mbgl::LatLng::WrapMode)wrapMode))); - } - - ProjectedMeters::ProjectedMeters(System::Double northing) : ProjectedMeters(northing, 0) - { - } - - ProjectedMeters::ProjectedMeters(System::Double northing, System::Double easting) : NativeWrapper(new mbgl::ProjectedMeters(northing, easting)) - { - } - - ProjectedMeters::ProjectedMeters(NativePointerHolder^ nativePointerHolder) : NativeWrapper(nativePointerHolder) - { - } - - ProjectedMeters::~ProjectedMeters() - { - } - - System::Double ProjectedMeters::Northing::get() - { - return NativePointer->northing(); - } - - System::Double ProjectedMeters::Easting::get() - { - return NativePointer->easting(); - } - - System::Boolean ProjectedMeters::operator==(ProjectedMeters^ a, ProjectedMeters^ b) - { - return *a->NativePointer == *b->NativePointer; - } - - System::Boolean ProjectedMeters::operator!=(ProjectedMeters^ a, ProjectedMeters^ b) - { - return *a->NativePointer != *b->NativePointer; - } + System::Double Projection::WorldSize(System::Double scale) + { + return mbgl::Projection::worldSize(scale); + } + + System::Double Projection::GetMetersPerPixelAtLatitude(System::Double lat, System::Double zoom) + { + return mbgl::Projection::getMetersPerPixelAtLatitude(lat, zoom); + } + + ProjectedMeters^ Projection::ProjectedMetersForLatLng(LatLng^ latLng) + { + return gcnew ProjectedMeters(ProjectedMeters::CreateNativePointerHolder(mbgl::Projection::projectedMetersForLatLng(*latLng->NativePointer))); + } + + LatLng^ Projection::LatLngForProjectedMeters(ProjectedMeters^ projectedMeters) + { + return gcnew LatLng(LatLng::CreateNativePointerHolder(mbgl::Projection::latLngForProjectedMeters(*projectedMeters->NativePointer))); + } + + PointDouble Projection::Project(LatLng^ latLng, System::Double scale) + { + return PointDouble(mbgl::Projection::project(*latLng->NativePointer, scale)); + } + + PointDouble Projection::Project(LatLng^ latLng, System::Int32 zoom) + { + return PointDouble(mbgl::Projection::project(*latLng->NativePointer, zoom)); + } + + LatLng^ Projection::Unproject(PointDouble p, System::Double scale) + { + return gcnew LatLng(LatLng::CreateNativePointerHolder(mbgl::Projection::unproject(mbgl::ScreenCoordinate(p.X, p.Y), scale))); + } + + LatLng^ Projection::Unproject(PointDouble p, System::Double scale, LatLng::WrapMode wrapMode) + { + return gcnew LatLng(LatLng::CreateNativePointerHolder(mbgl::Projection::unproject(mbgl::ScreenCoordinate(p.X, p.Y), scale, (mbgl::LatLng::WrapMode)wrapMode))); + } + + ProjectedMeters::ProjectedMeters(System::Double northing) : ProjectedMeters(northing, 0) + { + } + + ProjectedMeters::ProjectedMeters(System::Double northing, System::Double easting) : NativeWrapper(new mbgl::ProjectedMeters(northing, easting)) + { + } + + ProjectedMeters::ProjectedMeters(NativePointerHolder^ nativePointerHolder) : NativeWrapper(nativePointerHolder) + { + } + + ProjectedMeters::~ProjectedMeters() + { + } + + System::Double ProjectedMeters::Northing::get() + { + return NativePointer->northing(); + } + + System::Double ProjectedMeters::Easting::get() + { + return NativePointer->easting(); + } + + System::Boolean ProjectedMeters::operator==(ProjectedMeters^ a, ProjectedMeters^ b) + { + return *a->NativePointer == *b->NativePointer; + } + + System::Boolean ProjectedMeters::operator!=(ProjectedMeters^ a, ProjectedMeters^ b) + { + return *a->NativePointer != *b->NativePointer; + } } diff --git a/src/ProjectionMode.cpp b/src/ProjectionMode.cpp index 5163d54..3173f37 100644 --- a/src/ProjectionMode.cpp +++ b/src/ProjectionMode.cpp @@ -1,4 +1,4 @@ -#include "ProjectionMode.h" +#include "ProjectionMode.h" #include namespace DOTNET_NAMESPACE @@ -61,7 +61,7 @@ namespace DOTNET_NAMESPACE { return System::Nullable(NativePointer->xSkew.value()); } - + return System::Nullable(); } diff --git a/src/Renderer.cpp b/src/Renderer.cpp index 8587990..f4fd631 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -2,6 +2,11 @@ #include "RendererObserver.h" #include "UpdateParameters.h" #include +#include +#include +#include +#include +#include namespace DOTNET_NAMESPACE { @@ -42,4 +47,47 @@ namespace DOTNET_NAMESPACE { NativePointer->clearData(); } + + static mbgl::RenderedQueryOptions BuildQueryOptions( + System::Collections::Generic::IEnumerable^ layerIds) + { + mbgl::RenderedQueryOptions opts; + if (layerIds != nullptr) + { + std::vector ids; + for each (System::String^ id in layerIds) + ids.push_back(msclr::interop::marshal_as(id)); + if (!ids.empty()) + opts.layerIDs = std::move(ids); + } + return opts; + } + + static System::String^ FeaturesToGeoJson(const std::vector& features) + { + mapbox::feature::feature_collection coll; + coll.reserve(features.size()); + for (const auto& f : features) + coll.push_back(f); + std::string json = mapbox::geojson::stringify(coll); + return msclr::interop::marshal_as(json); + } + + System::String^ Renderer::QueryRenderedFeaturesAtPoint( + double x, double y, + System::Collections::Generic::IEnumerable^ layerIds) + { + mbgl::ScreenCoordinate pt{ x, y }; + auto features = NativePointer->queryRenderedFeatures(pt, BuildQueryOptions(layerIds)); + return FeaturesToGeoJson(features); + } + + System::String^ Renderer::QueryRenderedFeaturesInBox( + double x1, double y1, double x2, double y2, + System::Collections::Generic::IEnumerable^ layerIds) + { + mbgl::ScreenBox box{ { x1, y1 }, { x2, y2 } }; + auto features = NativePointer->queryRenderedFeatures(box, BuildQueryOptions(layerIds)); + return FeaturesToGeoJson(features); + } } diff --git a/src/RendererObserver.cpp b/src/RendererObserver.cpp index 8deb582..0b83e23 100644 --- a/src/RendererObserver.cpp +++ b/src/RendererObserver.cpp @@ -1,4 +1,4 @@ -#include "Convert.h" +#include "Convert.h" #include "RendererObserver.h" #include "ShaderRegistry.h" #include @@ -154,7 +154,7 @@ namespace DOTNET_NAMESPACE StyleImageMissingCallbackHelper^ callbackHelper = gcnew StyleImageMissingCallbackHelper(this); _PendingCallbacks->TryAdd(callbackHelper, System::IntPtr(new StyleImageMissingCallback(done))); - + StyleImageMissingHandler->Invoke( Convert::ToSystemString(id), gcnew DOTNET_NAMESPACE::RendererObserver::StyleImageMissingCallback( diff --git a/src/RenderingStats.cpp b/src/RenderingStats.cpp index 1af436b..1eefedc 100644 --- a/src/RenderingStats.cpp +++ b/src/RenderingStats.cpp @@ -20,83 +20,46 @@ namespace DOTNET_NAMESPACE return NativePointer->isZero(); } - System::Int32 RenderingStats::NumDrawCalls::get() - { - return NativePointer->numDrawCalls; - } - - System::Void RenderingStats::NumDrawCalls::set(System::Int32 value) - { - NativePointer->numDrawCalls = value; - } - - System::Int32 RenderingStats::NumActiveTextures::get() - { - return NativePointer->numActiveTextures; - } - - System::Void RenderingStats::NumActiveTextures::set(System::Int32 value) - { - NativePointer->numActiveTextures = value; - } - - System::Int32 RenderingStats::NumCreatedTextures::get() - { - return NativePointer->numCreatedTextures; - } - - System::Void RenderingStats::NumCreatedTextures::set(System::Int32 value) - { - NativePointer->numCreatedTextures; - } - - System::Int32 RenderingStats::NumBuffers::get() - { - return NativePointer->numBuffers; - } - - System::Void RenderingStats::NumBuffers::set(System::Int32 value) - { - NativePointer->numBuffers; - } - - System::Int32 RenderingStats::NumFrameBuffers::get() - { - return NativePointer->numFrameBuffers; - } - - System::Void RenderingStats::NumFrameBuffers::set(System::Int32 value) - { - NativePointer->numFrameBuffers; - } - - System::Int32 RenderingStats::MemTextures::get() - { - return NativePointer->memTextures; - } - - System::Void RenderingStats::MemTextures::set(System::Int32 value) - { - NativePointer->memTextures; - } - - System::Int32 RenderingStats::MemIndexBuffers::get() - { - return NativePointer->memIndexBuffers; - } - - System::Void RenderingStats::MemIndexBuffers::set(System::Int32 value) - { - NativePointer->memIndexBuffers; - } - - System::Int32 RenderingStats::MemVertexBuffers::get() - { - return NativePointer->memVertexBuffers; - } - - System::Void RenderingStats::MemVertexBuffers::set(System::Int32 value) - { - NativePointer->memVertexBuffers = value; - } + System::Double RenderingStats::EncodingTime::get() { return NativePointer->encodingTime; } + System::Double RenderingStats::RenderingTime::get() { return NativePointer->renderingTime; } + System::Int32 RenderingStats::NumFrames::get() { return NativePointer->numFrames; } + + System::Int32 RenderingStats::NumDrawCalls::get() { return NativePointer->numDrawCalls; } + System::Void RenderingStats::NumDrawCalls::set(System::Int32 value) { NativePointer->numDrawCalls = value; } + System::Int32 RenderingStats::TotalDrawCalls::get() { return NativePointer->totalDrawCalls; } + + System::Int32 RenderingStats::NumActiveTextures::get() { return NativePointer->numActiveTextures; } + System::Void RenderingStats::NumActiveTextures::set(System::Int32 value) { NativePointer->numActiveTextures = value; } + System::Int32 RenderingStats::NumCreatedTextures::get() { return NativePointer->numCreatedTextures; } + System::Void RenderingStats::NumCreatedTextures::set(System::Int32 value) { NativePointer->numCreatedTextures = value; } + System::Int32 RenderingStats::NumTextureBindings::get() { return NativePointer->numTextureBindings; } + System::Int32 RenderingStats::NumTextureUpdates::get() { return NativePointer->numTextureUpdates; } + System::Int64 RenderingStats::TextureUpdateBytes::get() { return static_cast(NativePointer->textureUpdateBytes); } + + System::Int32 RenderingStats::NumBuffers::get() { return NativePointer->numBuffers; } + System::Void RenderingStats::NumBuffers::set(System::Int32 value) { NativePointer->numBuffers = value; } + System::Int32 RenderingStats::NumFrameBuffers::get() { return NativePointer->numFrameBuffers; } + System::Void RenderingStats::NumFrameBuffers::set(System::Int32 value) { NativePointer->numFrameBuffers = value; } + System::Int32 RenderingStats::NumIndexBuffers::get() { return NativePointer->numIndexBuffers; } + System::Int64 RenderingStats::IndexUpdateBytes::get() { return static_cast(NativePointer->indexUpdateBytes); } + System::Int32 RenderingStats::NumVertexBuffers::get() { return NativePointer->numVertexBuffers; } + System::Int64 RenderingStats::VertexUpdateBytes::get() { return static_cast(NativePointer->vertexUpdateBytes); } + System::Int32 RenderingStats::NumUniformBuffers::get() { return NativePointer->numUniformBuffers; } + System::Int32 RenderingStats::NumUniformUpdates::get() { return NativePointer->numUniformUpdates; } + System::Int64 RenderingStats::UniformUpdateBytes::get() { return static_cast(NativePointer->uniformUpdateBytes); } + System::Int64 RenderingStats::TotalBuffers::get() { return static_cast(NativePointer->totalBuffers); } + System::Int64 RenderingStats::BufferUpdates::get() { return static_cast(NativePointer->bufferUpdates); } + System::Int64 RenderingStats::BufferUpdateBytes::get() { return static_cast(NativePointer->bufferUpdateBytes); } + + System::Int32 RenderingStats::MemTextures::get() { return NativePointer->memTextures; } + System::Void RenderingStats::MemTextures::set(System::Int32 value) { NativePointer->memTextures = value; } + System::Int32 RenderingStats::MemBuffers::get() { return NativePointer->memBuffers; } + System::Int32 RenderingStats::MemIndexBuffers::get() { return NativePointer->memIndexBuffers; } + System::Void RenderingStats::MemIndexBuffers::set(System::Int32 value) { NativePointer->memIndexBuffers = value; } + System::Int32 RenderingStats::MemVertexBuffers::get() { return NativePointer->memVertexBuffers; } + System::Void RenderingStats::MemVertexBuffers::set(System::Int32 value) { NativePointer->memVertexBuffers = value; } + System::Int32 RenderingStats::MemUniformBuffers::get() { return NativePointer->memUniformBuffers; } + + System::Int32 RenderingStats::StencilClears::get() { return NativePointer->stencilClears; } + System::Int32 RenderingStats::StencilUpdates::get() { return NativePointer->stencilUpdates; } } diff --git a/src/Resource.cpp b/src/Resource.cpp index 37322d1..488c2a4 100644 --- a/src/Resource.cpp +++ b/src/Resource.cpp @@ -271,7 +271,7 @@ namespace DOTNET_NAMESPACE { if (value) { - NativePointer->priorData = std::shared_ptr(&Convert::ToStdString(value)); + NativePointer->priorData = std::make_shared(Convert::ToStdString(value)); } else { diff --git a/src/ResourceOptions.cpp b/src/ResourceOptions.cpp index 9b351ce..7939eea 100644 --- a/src/ResourceOptions.cpp +++ b/src/ResourceOptions.cpp @@ -1,4 +1,4 @@ -#include "Convert.h" +#include "Convert.h" #include "ResourceOptions.h" #include @@ -24,7 +24,7 @@ namespace DOTNET_NAMESPACE ResourceOptions^ ResourceOptions::WithApiKey(System::String^ token) { NativePointer->withApiKey(Convert::ToStdString(token)); - + return this; } @@ -48,21 +48,21 @@ namespace DOTNET_NAMESPACE ResourceOptions^ ResourceOptions::WithAssetPath(System::String^ path) { NativePointer->withAssetPath(Convert::ToStdString(path)); - + return this; } ResourceOptions^ ResourceOptions::WithMaximumCacheSize(System::UInt64 size) { NativePointer->withMaximumCacheSize(size); - + return this; } ResourceOptions^ ResourceOptions::WithPlatformContext(System::IntPtr context) { NativePointer->withPlatformContext(context.ToPointer()); - + return this; } diff --git a/src/Response.cpp b/src/Response.cpp index 1b5da7c..0dab8b6 100644 --- a/src/Response.cpp +++ b/src/Response.cpp @@ -1,4 +1,4 @@ -#include "Convert.h" +#include "Convert.h" #include "Response.h" #include @@ -36,7 +36,7 @@ namespace DOTNET_NAMESPACE { _Error = nullptr; } - + return _Error; } @@ -90,7 +90,7 @@ namespace DOTNET_NAMESPACE { return Convert::ToSystemArray(reinterpret_cast(const_cast(NativePointer->data->data())), static_cast(NativePointer->data->size())); } - + return nullptr; } @@ -98,7 +98,7 @@ namespace DOTNET_NAMESPACE { if (value) { - NativePointer->data = std::shared_ptr(&Convert::ToStdString(value)); + NativePointer->data = std::make_shared(Convert::ToStdString(value)); } else { @@ -177,7 +177,7 @@ namespace DOTNET_NAMESPACE (mbgl::Response::Error::Reason)reason, message ? - Convert::ToStdString(message) + Convert::ToStdString(message) : std::string(), retryAfter.HasValue diff --git a/src/RunLoop.cpp b/src/RunLoop.cpp index 799a221..64c0cdf 100644 --- a/src/RunLoop.cpp +++ b/src/RunLoop.cpp @@ -31,9 +31,12 @@ namespace DOTNET_NAMESPACE } #pragma managed(push, off) +#pragma warning(push) +#pragma warning(disable: 5308) #undef _M_CEE #include #define _M_CEE +#pragma warning(pop) namespace DOTNET_NAMESPACE { diff --git a/src/Size.cpp b/src/Size.cpp index b61ed15..f652906 100644 --- a/src/Size.cpp +++ b/src/Size.cpp @@ -1,4 +1,4 @@ -#include "Size.h" +#include "Size.h" #include namespace DOTNET_NAMESPACE @@ -59,7 +59,7 @@ namespace DOTNET_NAMESPACE { return *a->NativePointer != *b->NativePointer; } - + Size::Size(NativePointerHolder^ nativePointerHolder) : NativeWrapper(nativePointerHolder) { } diff --git a/src/Source.cpp b/src/Source.cpp new file mode 100644 index 0000000..a1778ad --- /dev/null +++ b/src/Source.cpp @@ -0,0 +1,75 @@ +#include "Source.h" + +#include +#include +#include + +namespace DOTNET_NAMESPACE +{ + Source::Source(mbgl::style::Source* source) + : _source(source) + { + } + + System::String^ Source::Id::get() + { + return msclr::interop::marshal_as(_source->getID()); + } + + System::String^ Source::Type::get() + { + switch (_source->getType()) + { + case mbgl::style::SourceType::Vector: return "vector"; + case mbgl::style::SourceType::Raster: return "raster"; + case mbgl::style::SourceType::RasterDEM: return "raster-dem"; + case mbgl::style::SourceType::GeoJSON: return "geojson"; + case mbgl::style::SourceType::Image: return "image"; + default: return "unknown"; + } + } + + System::String^ Source::Attribution::get() + { + auto attr = _source->getAttribution(); + return attr ? msclr::interop::marshal_as(*attr) : System::String::Empty; + } + + bool Source::IsVolatile::get() + { + return _source->isVolatile(); + } + + System::Void Source::IsVolatile::set(bool value) + { + _source->setVolatile(value); + } + + System::Int32 Source::PrefetchZoomDelta::get() + { + auto val = _source->getPrefetchZoomDelta(); + return val.has_value() ? (System::Int32)*val : -1; + } + + System::Void Source::PrefetchZoomDelta::set(System::Int32 value) + { + if (value < 0) + _source->setPrefetchZoomDelta(std::nullopt); + else + _source->setPrefetchZoomDelta(std::optional((uint8_t)value)); + } + + System::Int32 Source::MaxOverscaleFactorForParentTiles::get() + { + auto val = _source->getMaxOverscaleFactorForParentTiles(); + return val.has_value() ? (System::Int32)*val : -1; + } + + System::Void Source::MaxOverscaleFactorForParentTiles::set(System::Int32 value) + { + if (value < 0) + _source->setMaxOverscaleFactorForParentTiles(std::nullopt); + else + _source->setMaxOverscaleFactorForParentTiles(std::optional((uint8_t)value)); + } +} diff --git a/src/Sources.cpp b/src/Sources.cpp new file mode 100644 index 0000000..f459540 --- /dev/null +++ b/src/Sources.cpp @@ -0,0 +1,172 @@ +#include "Sources.h" +#include "PremultipliedImage.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DOTNET_NAMESPACE +{ + // ========================================================================= + // GeoJSONSource + // ========================================================================= + + GeoJSONSource::GeoJSONSource(mbgl::style::GeoJSONSource* source) + : Source(source) + { + } + + mbgl::style::GeoJSONSource* GeoJSONSource::Impl() + { + return static_cast(_source); + } + + System::Void GeoJSONSource::SetUrl(System::String^ url) + { + Impl()->setURL(msclr::interop::marshal_as(url)); + } + + System::Void GeoJSONSource::SetData(System::String^ geoJson) + { + std::string raw = msclr::interop::marshal_as(geoJson); + mbgl::style::conversion::Error err; + auto parsed = mbgl::style::conversion::parseGeoJSON(raw, err); + if (parsed) + { + Impl()->setGeoJSON(*parsed); + } + } + + // ========================================================================= + // VectorSource + // ========================================================================= + + VectorSource::VectorSource(mbgl::style::VectorSource* source) + : Source(source) + { + } + + mbgl::style::VectorSource* VectorSource::Impl() + { + return static_cast(_source); + } + + System::String^ VectorSource::Url::get() + { + auto opt = Impl()->getURL(); + return opt ? msclr::interop::marshal_as(*opt) : System::String::Empty; + } + + // ========================================================================= + // RasterSource + // ========================================================================= + + RasterSource::RasterSource(mbgl::style::RasterSource* source) + : Source(source) + { + } + + mbgl::style::RasterSource* RasterSource::Impl() + { + return static_cast(_source); + } + + System::String^ RasterSource::Url::get() + { + auto opt = Impl()->getURL(); + return opt ? msclr::interop::marshal_as(*opt) : System::String::Empty; + } + + System::UInt16 RasterSource::TileSize::get() + { + return Impl()->getTileSize(); + } + + // ========================================================================= + // RasterDEMSource + // ========================================================================= + + RasterDEMSource::RasterDEMSource(mbgl::style::RasterDEMSource* source) + : Source(source) + { + } + + mbgl::style::RasterDEMSource* RasterDEMSource::Impl() + { + return static_cast(_source); + } + + System::String^ RasterDEMSource::Url::get() + { + // RasterDEMSource inherits RasterSource; cast through to RasterSource for the URL + auto* rs = static_cast(_source); + auto opt = rs->getURL(); + return opt ? msclr::interop::marshal_as(*opt) : System::String::Empty; + } + + System::UInt16 RasterDEMSource::TileSize::get() + { + auto* rs = static_cast(_source); + return rs->getTileSize(); + } + + // ========================================================================= + // ImageSource + // ========================================================================= + + ImageSource::ImageSource(mbgl::style::ImageSource* source) + : Source(source) + { + } + + mbgl::style::ImageSource* ImageSource::Impl() + { + return static_cast(_source); + } + + System::String^ ImageSource::Url::get() + { + auto opt = Impl()->getURL(); + return opt ? msclr::interop::marshal_as(*opt) : System::String::Empty; + } + + System::Void ImageSource::Url::set(System::String^ value) + { + Impl()->setURL(msclr::interop::marshal_as(value)); + } + + array^ ImageSource::Coordinates::get() + { + auto coords = Impl()->getCoordinates(); + auto result = gcnew array(4); + for (int i = 0; i < 4; ++i) + { + result[i] = gcnew LatLng(coords[i].latitude(), coords[i].longitude()); + } + return result; + } + + System::Void ImageSource::Coordinates::set(array^ value) + { + if (!value || value->Length < 4) + return; + std::array coords = { + mbgl::LatLng(value[0]->Latitude, value[0]->Longitude), + mbgl::LatLng(value[1]->Latitude, value[1]->Longitude), + mbgl::LatLng(value[2]->Latitude, value[2]->Longitude), + mbgl::LatLng(value[3]->Latitude, value[3]->Longitude) + }; + Impl()->setCoordinates(coords); + } + + System::Void ImageSource::SetImage(PremultipliedImage^ image) + { + if (image == nullptr) return; + Impl()->setImage(image->NativePointer->clone()); + } +} diff --git a/src/Style.cpp b/src/Style.cpp index 7ad8446..1a98a42 100644 --- a/src/Style.cpp +++ b/src/Style.cpp @@ -1,11 +1,40 @@ #include "Convert.h" #include "FileSource.h" #include "Style.h" +#include "Layers.h" +#include "Sources.h" +#include "Light.h" +#include "PremultipliedImage.h" +#include "CameraOptions.h" +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace DOTNET_NAMESPACE { - Style::Style(FileSource^ fileSource, System::Single pixelRatio) : NativeWrapper(new mbgl::style::Style(*fileSource->NativePointer, pixelRatio)) + Style::Style(FileSource^ fileSource, System::Single pixelRatio) : NativeWrapper(new mbgl::style::Style(*fileSource->NativePointer, pixelRatio, mbgl::TaggedScheduler{mbgl::Scheduler::GetBackground(), {}})) { } @@ -41,4 +70,586 @@ namespace DOTNET_NAMESPACE { return Convert::ToSystemString(NativePointer->getName()); } + + System::Void Style::AddGeoJsonSource(System::String^ sourceId, System::String^ url) + { + std::string id = Convert::ToStdString(sourceId); + std::string uri = Convert::ToStdString(url); + auto src = std::make_unique(id); + src->setURL(uri); + NativePointer->addSource(std::move(src)); + } + + System::Void Style::SetGeoJsonSourceUrl(System::String^ sourceId, System::String^ url) + { + std::string id = Convert::ToStdString(sourceId); + std::string uri = Convert::ToStdString(url); + auto* raw = NativePointer->getSource(id); + if (!raw) return; + auto* gjs = raw->as(); + if (!gjs) return; + gjs->setURL(uri); + } + + System::Void Style::SetGeoJsonSourceData(System::String^ sourceId, System::String^ geojsonString) + { + std::string id = Convert::ToStdString(sourceId); + std::string geoJson = Convert::ToStdString(geojsonString); + auto* raw = NativePointer->getSource(id); + if (!raw) return; + auto* gjs = raw->as(); + if (!gjs) return; + mbgl::style::conversion::Error err; + auto parsed = mbgl::style::conversion::parseGeoJSON(geoJson, err); + if (parsed) { + gjs->setGeoJSON(*parsed); + } + } + + System::Boolean Style::RemoveSource(System::String^ sourceId) + { + std::string id = Convert::ToStdString(sourceId); + return NativePointer->removeSource(id) != nullptr; + } + + System::Boolean Style::HasSource(System::String^ sourceId) + { + std::string id = Convert::ToStdString(sourceId); + return NativePointer->getSource(id) != nullptr; + } + + System::Void Style::AddCircleLayer(System::String^ layerId, System::String^ sourceId, + System::String^ color, float radius, float opacity, + System::String^ filterJson) + { + std::string lid = Convert::ToStdString(layerId); + std::string sid = Convert::ToStdString(sourceId); + + auto layer = std::make_unique(lid, sid); + + // Parse hex color string (e.g. "#rrggbb") + std::string colorStr = Convert::ToStdString(color); + auto parsedColor = mbgl::Color::parse(colorStr); + if (parsedColor) { + layer->setCircleColor(mbgl::style::PropertyValue(*parsedColor)); + } + + layer->setCircleRadius(mbgl::style::PropertyValue(radius)); + layer->setCircleOpacity(mbgl::style::PropertyValue(opacity)); + + // filterJson is reserved for future use; filter parsing requires full + // JSON conversion infrastructure which is intentionally omitted here. + + NativePointer->addLayer(std::move(layer)); + } + + System::Boolean Style::RemoveLayer(System::String^ layerId) + { + std::string id = Convert::ToStdString(layerId); + return NativePointer->removeLayer(id) != nullptr; + } + + System::Boolean Style::HasLayer(System::String^ layerId) + { + std::string id = Convert::ToStdString(layerId); + return NativePointer->getLayer(id) != nullptr; + } + + // ------------------------------------------------------------------------- + // Helper: wrap a native Layer* as the correct managed concrete type + // ------------------------------------------------------------------------- + static Layer^ WrapLayer(mbgl::style::Layer* raw) + { + if (!raw) return nullptr; + if (auto* p = dynamic_cast(raw)) return gcnew FillLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew LineLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew CircleLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew SymbolLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew RasterLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew BackgroundLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew HeatmapLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew HillshadeLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew FillExtrusionLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew ColorReliefLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew LocationIndicatorLayer(p); + // Unknown type — return base Layer wrapper + return gcnew Layer(raw); + } + + // ------------------------------------------------------------------------- + // Helper: wrap a native Source* as the correct managed concrete type + // ------------------------------------------------------------------------- + static Source^ WrapSource(mbgl::style::Source* raw) + { + if (!raw) return nullptr; + if (auto* p = raw->as()) return gcnew GeoJSONSource(p); + if (auto* p = raw->as()) return gcnew VectorSource(p); + if (auto* p = raw->as()) return gcnew RasterDEMSource(p); + if (auto* p = raw->as()) return gcnew RasterSource(p); + if (auto* p = raw->as()) return gcnew ImageSource(p); + return gcnew Source(raw); + } + + // ------------------------------------------------------------------------- + // Layer retrieval + // ------------------------------------------------------------------------- + + Layer^ Style::GetLayer(System::String^ layerId) + { + auto* raw = NativePointer->getLayer(Convert::ToStdString(layerId)); + return WrapLayer(raw); + } + + System::Collections::Generic::List^ Style::GetLayers() + { + auto result = gcnew System::Collections::Generic::List(); + for (auto* raw : NativePointer->getLayers()) + { + result->Add(WrapLayer(raw)); + } + return result; + } + + // ------------------------------------------------------------------------- + // Source retrieval + // ------------------------------------------------------------------------- + + Source^ Style::GetSource(System::String^ sourceId) + { + auto* raw = NativePointer->getSource(Convert::ToStdString(sourceId)); + return WrapSource(raw); + } + + System::Collections::Generic::List^ Style::GetSources() + { + auto result = gcnew System::Collections::Generic::List(); + for (auto* raw : NativePointer->getSources()) + { + result->Add(WrapSource(raw)); + } + return result; + } + + // ------------------------------------------------------------------------- + // Add layer helpers + // ------------------------------------------------------------------------- + + FillLayer^ Style::AddFillLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew FillLayer(raw); + } + + LineLayer^ Style::AddLineLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew LineLayer(raw); + } + + CircleLayer^ Style::AddCircleLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew CircleLayer(raw); + } + + SymbolLayer^ Style::AddSymbolLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew SymbolLayer(raw); + } + + RasterLayer^ Style::AddRasterLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew RasterLayer(raw); + } + + BackgroundLayer^ Style::AddBackgroundLayer(System::String^ layerId) + { + auto layer = std::make_unique(Convert::ToStdString(layerId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew BackgroundLayer(raw); + } + + HeatmapLayer^ Style::AddHeatmapLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew HeatmapLayer(raw); + } + + HillshadeLayer^ Style::AddHillshadeLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew HillshadeLayer(raw); + } + + FillExtrusionLayer^ Style::AddFillExtrusionLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew FillExtrusionLayer(raw); + } + + ColorReliefLayer^ Style::AddColorReliefLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew ColorReliefLayer(raw); + } + + LocationIndicatorLayer^ Style::AddLocationIndicatorLayer(System::String^ layerId) + { + auto layer = std::make_unique(Convert::ToStdString(layerId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew LocationIndicatorLayer(raw); + } + + FillLayer^ Style::AddFillLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew FillLayer(raw); + } + + LineLayer^ Style::AddLineLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew LineLayer(raw); + } + + CircleLayer^ Style::AddCircleLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew CircleLayer(raw); + } + + SymbolLayer^ Style::AddSymbolLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew SymbolLayer(raw); + } + + RasterLayer^ Style::AddRasterLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew RasterLayer(raw); + } + + BackgroundLayer^ Style::AddBackgroundLayer(System::String^ layerId, System::String^ beforeLayerId) + { + auto layer = std::make_unique(Convert::ToStdString(layerId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew BackgroundLayer(raw); + } + + HeatmapLayer^ Style::AddHeatmapLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew HeatmapLayer(raw); + } + + HillshadeLayer^ Style::AddHillshadeLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew HillshadeLayer(raw); + } + + FillExtrusionLayer^ Style::AddFillExtrusionLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew FillExtrusionLayer(raw); + } + + ColorReliefLayer^ Style::AddColorReliefLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew ColorReliefLayer(raw); + } + + LocationIndicatorLayer^ Style::AddLocationIndicatorLayer(System::String^ layerId, System::String^ beforeLayerId) + { + auto layer = std::make_unique(Convert::ToStdString(layerId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew LocationIndicatorLayer(raw); + } + + // ------------------------------------------------------------------------- + // Add source helpers + // ------------------------------------------------------------------------- + + GeoJSONSource^ Style::AddGeoJsonSource(System::String^ sourceId) + { + auto src = std::make_unique(Convert::ToStdString(sourceId)); + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew GeoJSONSource(raw); + } + + static mbgl::style::GeoJSONOptions BuildGeoJSONOptions(GeoJSONOptions^ opts) + { + mbgl::style::GeoJSONOptions o; + o.minzoom = opts->MinZoom; + o.maxzoom = opts->MaxZoom; + o.tileSize = opts->TileSize; + o.buffer = opts->Buffer; + o.tolerance = opts->Tolerance; + o.lineMetrics = opts->LineMetrics; + o.cluster = opts->Cluster; + o.clusterRadius = opts->ClusterRadius; + o.clusterMaxZoom = opts->ClusterMaxZoom; + return o; + } + + GeoJSONSource^ Style::AddGeoJsonSource(System::String^ sourceId, GeoJSONOptions^ options) + { + auto nativeOpts = mbgl::Immutable( + mbgl::makeMutable(BuildGeoJSONOptions(options))); + auto src = std::make_unique( + Convert::ToStdString(sourceId), nativeOpts); + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew GeoJSONSource(raw); + } + + GeoJSONSource^ Style::AddGeoJsonSourceFromUrl(System::String^ sourceId, System::String^ url) + { + auto src = std::make_unique(Convert::ToStdString(sourceId)); + src->setURL(Convert::ToStdString(url)); + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew GeoJSONSource(raw); + } + + GeoJSONSource^ Style::AddGeoJsonSourceFromUrl(System::String^ sourceId, System::String^ url, GeoJSONOptions^ options) + { + auto nativeOpts = mbgl::Immutable( + mbgl::makeMutable(BuildGeoJSONOptions(options))); + auto src = std::make_unique( + Convert::ToStdString(sourceId), nativeOpts); + src->setURL(Convert::ToStdString(url)); + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew GeoJSONSource(raw); + } + + VectorSource^ Style::AddVectorSource(System::String^ sourceId, System::String^ url) + { + auto src = std::make_unique( + Convert::ToStdString(sourceId), + mbgl::variant(Convert::ToStdString(url))); + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew VectorSource(raw); + } + + RasterSource^ Style::AddRasterSource(System::String^ sourceId, System::String^ url, System::UInt16 tileSize) + { + auto src = std::make_unique( + Convert::ToStdString(sourceId), + mbgl::variant(Convert::ToStdString(url)), + tileSize); + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew RasterSource(raw); + } + + RasterDEMSource^ Style::AddRasterDemSource(System::String^ sourceId, System::String^ url, System::UInt16 tileSize) + { + auto src = std::make_unique( + Convert::ToStdString(sourceId), + mbgl::variant(Convert::ToStdString(url)), + tileSize); + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew RasterDEMSource(raw); + } + + ImageSource^ Style::AddImageSource(System::String^ sourceId, System::String^ url, array^ coordinates) + { + std::array coords = { + mbgl::LatLng(coordinates[0]->Latitude, coordinates[0]->Longitude), + mbgl::LatLng(coordinates[1]->Latitude, coordinates[1]->Longitude), + mbgl::LatLng(coordinates[2]->Latitude, coordinates[2]->Longitude), + mbgl::LatLng(coordinates[3]->Latitude, coordinates[3]->Longitude) + }; + auto src = std::make_unique(Convert::ToStdString(sourceId), coords); + if (url && url->Length > 0) + { + src->setURL(Convert::ToStdString(url)); + } + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew ImageSource(raw); + } + + // ------------------------------------------------------------------------- + // Light + // ------------------------------------------------------------------------- + + Light^ Style::GetLight() + { + return gcnew Light(NativePointer->getLight()); + } + + // ------------------------------------------------------------------------- + // Transition options + // ------------------------------------------------------------------------- + + TransitionOptions^ Style::GetTransitionOptions() + { + auto native = NativePointer->getTransitionOptions(); + auto opts = gcnew TransitionOptions(); + if (native.duration) { + opts->DurationMilliseconds = System::Nullable( + (System::Int64)std::chrono::duration_cast(*native.duration).count()); + } + if (native.delay) { + opts->DelayMilliseconds = System::Nullable( + (System::Int64)std::chrono::duration_cast(*native.delay).count()); + } + opts->EnablePlacementTransitions = native.enablePlacementTransitions; + return opts; + } + + System::Void Style::SetTransitionOptions(TransitionOptions^ options) + { + mbgl::style::TransitionOptions native; + if (options->DurationMilliseconds.HasValue) { + native.duration = std::chrono::milliseconds(options->DurationMilliseconds.Value); + } + if (options->DelayMilliseconds.HasValue) { + native.delay = std::chrono::milliseconds(options->DelayMilliseconds.Value); + } + native.enablePlacementTransitions = options->EnablePlacementTransitions; + NativePointer->setTransitionOptions(native); + } + + // ------------------------------------------------------------------------- + // Default camera + // ------------------------------------------------------------------------- + + CameraOptions^ Style::GetDefaultCamera() + { + auto cam = NativePointer->getDefaultCamera(); + return gcnew CameraOptions(gcnew NativePointerHolder(cam)); + } + + // ------------------------------------------------------------------------- + // Sprite / icon images + // ------------------------------------------------------------------------- + + System::Void Style::AddImage(System::String^ id, PremultipliedImage^ pixels, + System::Single pixelRatio, System::Boolean sdf) + { + // Clone the pixel data (style::Image takes ownership via move) + auto pixelsClone = pixels->NativePointer->clone(); + auto img = std::make_unique( + Convert::ToStdString(id), + std::move(pixelsClone), + pixelRatio, + (bool)sdf); + NativePointer->addImage(std::move(img)); + } + + System::Void Style::AddImage(System::String^ id, PremultipliedImage^ pixels, + System::Single pixelRatio) + { + AddImage(id, pixels, pixelRatio, false); + } + + StyleImage^ Style::GetImage(System::String^ id) + { + auto opt = NativePointer->getImage(Convert::ToStdString(id)); + if (!opt) return nullptr; + const auto& img = *opt; + const auto& px = img.getImage(); + return gcnew StyleImage( + gcnew System::String(img.getID().c_str()), + (System::UInt32)px.size.width, + (System::UInt32)px.size.height, + img.getPixelRatio(), + img.isSdf()); + } + + System::Boolean Style::HasImage(System::String^ id) + { + return NativePointer->getImage(Convert::ToStdString(id)).has_value(); + } + + System::Void Style::RemoveImage(System::String^ id) + { + NativePointer->removeImage(Convert::ToStdString(id)); + } + + System::Collections::Generic::List^ Style::GetSourceAttributions() + { + auto result = gcnew System::Collections::Generic::List(); + auto seen = gcnew System::Collections::Generic::HashSet(); + for (auto* raw : NativePointer->getSources()) + { + auto attr = raw->getAttribution(); + if (attr && !attr->empty()) + { + auto managed = Convert::ToSystemString(*attr); + if (seen->Add(managed)) + result->Add(managed); + } + } + return result; + } } diff --git a/src/Value.cpp b/src/Value.cpp index 3b97931..d69bae6 100644 --- a/src/Value.cpp +++ b/src/Value.cpp @@ -1,4 +1,4 @@ -#include "Convert.h" +#include "Convert.h" #include "Value.h" #include @@ -102,7 +102,7 @@ namespace DOTNET_NAMESPACE { return System::Nullable(*value); } - + return System::Nullable(); } @@ -112,7 +112,7 @@ namespace DOTNET_NAMESPACE { return System::Nullable(*value); } - + return System::Nullable(); } @@ -122,7 +122,7 @@ namespace DOTNET_NAMESPACE { return System::Nullable(*value); } - + return System::Nullable(); } @@ -132,7 +132,7 @@ namespace DOTNET_NAMESPACE { return System::Nullable(*value); } - + return System::Nullable(); } diff --git a/src/Vector.cpp b/src/Vector.cpp index 26a200e..133f9ec 100644 --- a/src/Vector.cpp +++ b/src/Vector.cpp @@ -92,12 +92,14 @@ namespace DOTNET_NAMESPACE System::Collections::Generic::IEnumerator^ Vec3::GetEnumerator() { - return gcnew VectorEnumerator(&NativePointer->begin(), &NativePointer->end()); + auto begin = NativePointer->begin(); auto end = NativePointer->end(); + return gcnew VectorEnumerator(&begin, &end); } System::Collections::IEnumerator^ Vec3::GetEnumeratorObject() { - return gcnew VectorEnumerator(&NativePointer->begin(), &NativePointer->end()); + auto begin = NativePointer->begin(); auto end = NativePointer->end(); + return gcnew VectorEnumerator(&begin, &end); } System::Double Vec3::default::get(System::Int32 index) @@ -139,12 +141,14 @@ namespace DOTNET_NAMESPACE System::Collections::Generic::IEnumerator^ Vec4::GetEnumerator() { - return gcnew VectorEnumerator(&NativePointer->begin(), &NativePointer->end()); + auto begin = NativePointer->begin(); auto end = NativePointer->end(); + return gcnew VectorEnumerator(&begin, &end); } System::Collections::IEnumerator^ Vec4::GetEnumeratorObject() { - return gcnew VectorEnumerator(&NativePointer->begin(), &NativePointer->end()); + auto begin = NativePointer->begin(); auto end = NativePointer->end(); + return gcnew VectorEnumerator(&begin, &end); } System::Double Vec4::default::get(System::Int32 index) @@ -201,12 +205,14 @@ namespace DOTNET_NAMESPACE System::Collections::Generic::IEnumerator^ Mat4::GetEnumerator() { - return gcnew VectorEnumerator(&NativePointer->begin(), &NativePointer->end()); + auto begin = NativePointer->begin(); auto end = NativePointer->end(); + return gcnew VectorEnumerator(&begin, &end); } System::Collections::IEnumerator^ Mat4::GetEnumeratorObject() { - return gcnew VectorEnumerator(&NativePointer->begin(), &NativePointer->end()); + auto begin = NativePointer->begin(); auto end = NativePointer->end(); + return gcnew VectorEnumerator(&begin, &end); } System::Double Mat4::default::get(System::Int32 index)