Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .ai/memory/architecture/core-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ GDAL/PROJ runtime data deployed beside the Core DLL. Do not rely on
remain Core responsibilities; COM callbacks and host dispatch stay in
ActiveX.

Algorithm outputs need different completion rules. Runner-owned persistent
outputs pass a checked close before success. After successful finalization,
the runner tries all readers with the original access mode, then read-only.
An unavailable reader produces a warning and no returned dataset, not a failed
write. Anonymous
outputs retain their references for pipelines. Caller-supplied outputs remain
caller-owned, so the runner can check their flush but not their final close.
See `GdalAlgorithmRunner` and its output-failure and ownership tests.

Core render scenes are owned, backend-neutral snapshots. They are distinct
from viewport state and are rebuilt when relevant map data changes. Source
geometry edits made outside the map are not automatically observed; callers
Expand Down
2 changes: 2 additions & 0 deletions .ai/memory/build/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ platform-specific tree because vcpkg can remove another triplet from a shared
install directory.

The build deploys GDAL and PROJ runtime data beside the output binaries.
Only the Core project's target deploys this shared data. Consumer projects
reference Core so parallel solution builds do not copy the same files at once.
`GdalEnvironment` configures those paths; do not set `GDAL_DATA` or
`PROJ_LIB` for the application.

Expand Down
7 changes: 4 additions & 3 deletions .ai/memory/build/windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ automated UI and pixel checks.

## CI scope

`.github/workflows/build.yml` runs Debug x64 build and native tests for pull
requests. Pushes to `main` additionally build and test Debug and Release for
both `x64` and `Win32`. The runner overrides the default SDK with `10.0`
`.github/workflows/build.yml` runs Debug and full-driver Release x64 builds
and native tests for pull requests and pushes to `development`.
Each job has a 180-minute timeout. Pushes to `main` additionally build and test
both configurations for `Win32`. The runner overrides the default SDK with `10.0`
because the `windows-2025` image does not include the repository's pinned SDK.
The C# host checks require an interactive desktop and are not part of this CI
workflow.
Expand Down
14 changes: 14 additions & 0 deletions .ai/memory/lessons/lessons.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,17 @@ integration test still requires an interactive Windows desktop.

**References:** [Testing guide](../../../docs/TESTING.md#activex-host-integration-checks)
and [PR #26](https://github.com/MapWindow/MapWinGIS6/pull/26).

## Shapefile geometry edits do not shrink stored bounds

OGR's Shapefile driver expands bounds during `SetFeature` but does not shrink
them. Core runs `RECOMPUTE EXTENT ON` during an explicit `VectorDataset::Flush()`
for dirty Shapefile and GeoPackage layers. Call it before closing after geometry
edits to preserve exact stored bounds. This costs one O(n) scan per dirty layer,
not per edit. Attribute edits do not trigger it. A recomputation failure throws
after geometry writes and leaves the layer dirty for retry. It does not roll
back the writes. Other formats retain their driver's stored-extent behavior.

See [VectorDataset.cpp](../../../src/MapWinGIS.Core/src/vector/VectorDataset.cpp)
and the shrinking and reopening cases in
[VectorLayerTests.cpp](../../../tests/MapWinGIS.Core.Tests/VectorLayerTests.cpp).
9 changes: 8 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ concurrency:
jobs:
build:
runs-on: windows-2025
timeout-minutes: 180

strategy:
fail-fast: false
matrix: ${{ fromJSON(github.event_name == 'push' && github.ref == 'refs/heads/main' && '{"platform":["x64","Win32"],"configuration":["Debug","Release"]}' || '{"include":[{"platform":"x64","configuration":"Debug"}]}') }}
matrix: ${{ fromJSON(github.event_name == 'push' && github.ref == 'refs/heads/main' && '{"platform":["x64","Win32"],"configuration":["Debug","Release"]}' || '{"platform":["x64"],"configuration":["Debug","Release"]}') }}

env:
VCPKG_BINARY_SOURCES: 'clear;files,${{ github.workspace }}\vcpkg-binary-cache,readwrite'
Expand Down Expand Up @@ -46,14 +47,20 @@ jobs:
- name: Set up MSBuild
uses: microsoft/setup-msbuild@v3

- name: Check build configuration
shell: pwsh
run: ./build/scripts/Test-BuildConfiguration.ps1

- name: Build
shell: pwsh
run: |
$features = if ('${{ matrix.configuration }}' -eq 'Release') { 'tests%3Bfull-drivers' } else { 'tests' }
msbuild MapWinGIS6.slnx `
-restore `
-p:Configuration=${{ matrix.configuration }} `
-p:Platform=${{ matrix.platform }} `
-p:MwgWindowsSdk=$env:MWG_WINDOWS_SDK `
"-p:MwgVcpkgFeatures=$features" `
-m -v:m

- name: Test
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@

<!-- Manifest features. "full-drivers" is what release packages build. -->
<MwgVcpkgFeatures Condition="'$(MwgVcpkgFeatures)' == ''">tests</MwgVcpkgFeatures>
<VcpkgAdditionalInstallOptions>--overlay-triplets=$(MwgRepoRoot)build\triplets --x-feature=$(MwgVcpkgFeatures.Replace(';',' --x-feature='))</VcpkgAdditionalInstallOptions>
<VcpkgAdditionalInstallOptions>--overlay-triplets=&quot;$(MwgRepoRoot)build\triplets&quot; --x-feature=$(MwgVcpkgFeatures.Replace(';',' --x-feature='))</VcpkgAdditionalInstallOptions>
</PropertyGroup>

<PropertyGroup Label="VcpkgPaths">
Expand Down
5 changes: 3 additions & 2 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@

vcpkg installs these under share/, and nothing copies them next to the
binaries. GdalEnvironment looks for "gdal-data" and "proj" beside the
loaded module, so they are copied there.
loaded module, so the Core project copies them there once. All consumers
reference Core and share its output directory.
-->

<PropertyGroup>
<MwgDeployRuntimeData Condition="'$(MwgDeployRuntimeData)' == ''">true</MwgDeployRuntimeData>
<MwgDeployRuntimeData Condition="'$(MwgDeployRuntimeData)' == ''">false</MwgDeployRuntimeData>
</PropertyGroup>

<Target Name="MwgDeployGdalRuntimeData"
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ Release configuration, build with `-p:Configuration=Release` and run
For additional configurations, dependency features, and troubleshooting, see
[docs/BUILDING.md](docs/BUILDING.md).

CI Release artifacts use the `full-drivers` feature. Their tests require
HDF5, NetCDF, KML, SpatiaLite, and PostgreSQL support.
Pull requests build and test both Debug and full-driver Release for x64.
Each CI build has a 180-minute timeout.

After vector geometry edits, call `VectorDataset::Flush()` before closing to
persist exact Shapefile and GeoPackage bounds. The flush batches stored-extent
scans across multiple edits.

## Layout

| Path | Contents |
Expand Down
78 changes: 78 additions & 0 deletions build/scripts/Test-BuildConfiguration.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<#
.SYNOPSIS
Tests dependency argument quoting and single-owner runtime deployment.
#>
[CmdletBinding()]
param(
[string] $MSBuild = 'msbuild'
)

$ErrorActionPreference = 'Stop'
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
$temp = Join-Path ([System.IO.Path]::GetTempPath()) "MapWinGIS build checks $([guid]::NewGuid())"
try {
New-Item -ItemType Directory -Path $temp -Force | Out-Null
Copy-Item "$repoRoot\Directory.Build.props", "$repoRoot\Directory.Build.targets" $temp
Copy-Item "$repoRoot\build" "$temp\build" -Recurse
New-Item -ItemType Directory -Path "$temp\packages\share\gdal", "$temp\packages\share\proj" -Force | Out-Null
Set-Content "$temp\packages\share\gdal\test.csv" 'GDAL fixture'
Set-Content "$temp\packages\share\proj\proj.db" 'PROJ fixture'
Set-Content "$temp\capture.ps1" @'
$commandLine = [Environment]::GetCommandLineArgs()
$start = [array]::IndexOf($commandLine, $PSCommandPath) + 1
$commandLine | Select-Object -Skip $start | Set-Content (Join-Path $PSScriptRoot 'arguments.txt')
'@

$project = @'
<Project>
<PropertyGroup>
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
<MwgVcpkgRoot>$(MSBuildThisFileDirectory)no-vcpkg</MwgVcpkgRoot>
</PropertyGroup>
<Import Project="Directory.Build.props" />
<PropertyGroup>
<MwgVcpkgTripletDir>$(MSBuildThisFileDirectory)packages\</MwgVcpkgTripletDir>
<OutDir>$(MSBuildThisFileDirectory)output\</OutDir>
</PropertyGroup>
<Import Project="Directory.Build.targets" />
<Target Name="CaptureArguments">
<Exec Command="&quot;$(MwgTestPowerShell)&quot; -NoProfile -File &quot;$(MSBuildThisFileDirectory)capture.ps1&quot; $(VcpkgAdditionalInstallOptions)" />
</Target>
</Project>
'@
Set-Content "$temp\RuntimeDataOwner.proj" $project
Set-Content "$temp\MapWinGIS.ActiveX.proj" $project
$powerShell = (Get-Process -Id $PID).Path
& $MSBuild "$temp\RuntimeDataOwner.proj" -nologo -v:q -t:CaptureArguments "-p:MwgTestPowerShell=$powerShell" '-p:MwgVcpkgFeatures=tests%3Bfull-drivers'
if ($LASTEXITCODE -ne 0) { throw 'Dependency argument capture failed.' }
$arguments = @(Get-Content "$temp\arguments.txt")
if ($arguments.Count -ne 3 -or
$arguments[0] -ne "--overlay-triplets=$temp\build\triplets" -or
$arguments[1] -ne '--x-feature=tests' -or
$arguments[2] -ne '--x-feature=full-drivers') {
throw "Dependency arguments were split or changed: $arguments"
}

# A consumer must not copy, even when the shared output is empty.
& $MSBuild "$temp\MapWinGIS.ActiveX.proj" -nologo -v:q -t:MwgDeployGdalRuntimeData
if ($LASTEXITCODE -ne 0) { throw 'Consumer deployment check failed.' }
if (Test-Path "$temp\output") { throw 'A consumer copied shared runtime data.' }

& $MSBuild "$temp\RuntimeDataOwner.proj" -nologo -v:q -t:MwgDeployGdalRuntimeData -p:MwgDeployRuntimeData=true
if ($LASTEXITCODE -ne 0) { throw 'Core deployment failed.' }
foreach ($relative in @('gdal-data\test.csv', 'proj\proj.db')) {
if (-not (Test-Path "$temp\output\$relative")) { throw "Runtime data missing: $relative" }
}

Set-Content "$temp\packages\share\proj\proj.db" 'Updated PROJ fixture'
& $MSBuild "$temp\MapWinGIS.ActiveX.proj" -nologo -v:q -t:MwgDeployGdalRuntimeData
if ($LASTEXITCODE -ne 0) { throw 'Consumer redeployment check failed.' }
if ((Get-Content "$temp\output\proj\proj.db") -ne 'PROJ fixture') {
throw 'A consumer overwrote shared runtime data.'
}
Write-Host 'Build configuration checks passed.'
}
finally {
if (Test-Path $temp) { Remove-Item -LiteralPath $temp -Recurse -Force }
}
4 changes: 2 additions & 2 deletions docs/ACTIVEX-INVESTIGATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ create a raster directly with GDAL, but this is test fixture code rather than
a core raster API. GDAL must be initialized through `GdalEnvironment`.

The build already defines Debug and Release for Win32 and x64 and uses vcpkg
for GDAL, GEOS, and GoogleTest. Pull requests build and test Debug x64; pushes
to `main` also cover Debug and Release for Win32 and x64.
for GDAL, GEOS, and GoogleTest. Pull requests build and test Debug and full-driver
Release for x64. Pushes to `main` also cover Win32.

## MapWinGIS 5 reference

Expand Down
22 changes: 19 additions & 3 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,9 @@ expect.
`SetLogHandler()`. Never write to stderr and never show message boxes from
the core.
- Algorithm runners, compatibility utilities, and pipelines share an execution
lock. Their callbacks run on the calling thread and cannot re-enter these
operations. Direct dataset access still needs caller synchronization.
lock. GDAL can report progress on worker threads. The shared progress adapter
serializes callbacks, which cannot re-enter these operations.
Direct dataset access still needs caller synchronization.
See [operation results and threading](gdal-modern-capabilities.md#operation-results-logging-and-cancellation)
for result, cancellation, and callback lifetime contracts.
- The supported GDAL version and dependency pin are documented in
Expand Down Expand Up @@ -138,6 +139,19 @@ state; hosts and renderers read from it.
The map does not reproject; layers in different coordinate reference
systems do not align until coordinate transformations are implemented.

Vector layer extent queries cache successful results. Read failures throw
without caching an empty result. If a driver cannot provide an extent and
reports no failure diagnostic, Core scans the geometries with read-error checks.
The scan asks the driver to ignore attributes.
Attribute edits keep the cached extent. After geometry edits, the next extent
query scans once and caches the result.

Call `VectorDataset::Flush()` before closing after geometry edits to persist
exact Shapefile and GeoPackage bounds. The flush runs one O(n) stored-extent
scan per dirty layer, rather than one per edit. A recomputation failure throws
without rolling back geometry writes and leaves the layer dirty for retry.
Other formats retain their driver's stored-extent behavior.

## Viewport

`mwg::Viewport` (`mapwingis/map/Viewport.h`) is the view of map coordinates on
Expand Down Expand Up @@ -387,7 +401,9 @@ reading feature geometries sequentially without WKT serialization and
reparsing. Adding a prepared layer retains the existing scene. Other layer
changes and explicit invalidation rebuild the scene for all visible layers.
There is no tiling, paging, or clipping to the visible extent. GDI skips a
layer whose device coordinates exceed its range. The opt-in
layer whose device coordinates exceed its range. It clips drawing to the
requested target rectangle and the host's existing clip region, then restores
the device context. The opt-in
rendering baseline (see [TESTING.md](TESTING.md#rendering-performance-baseline))
tracks scene preparation and backend frame costs. OpenGL only supports convex
single-ring polygons and copies each frame through CPU memory. Its reusable CPU
Expand Down
21 changes: 17 additions & 4 deletions docs/BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ Release with it again reinstalls the tree each time.
3. `..\vcpkg` next to the repository
4. the `VCPKG_ROOT` environment variable

The build quotes the overlay-triplet path when it invokes vcpkg, so checkout
paths can contain spaces.

## Configurations and platforms

| | |
Expand Down Expand Up @@ -158,17 +161,27 @@ Manifest features:
| `full-drivers` | Adds HDF5, NetCDF, KML, SpatiaLite and PostgreSQL to GDAL | Release packages. Much slower to build |

```powershell
msbuild MapWinGIS6.slnx -restore -p:Platform=x64 -p:Configuration=Release -p:MwgVcpkgFeatures="tests;full-drivers"
msbuild MapWinGIS6.slnx -restore -p:Platform=x64 -p:Configuration=Release "-p:MwgVcpkgFeatures=tests%3Bfull-drivers"
```

`%3B` passes a literal semicolon through MSBuild's property parser.
CI uses this feature set for Release artifacts. Core tests built with
`full-drivers` require HDF5, NetCDF, LIBKML, PostgreSQL, and SpatiaLite support.
The SpatiaLite test creates a spatial database without an external server.
Pull requests and pushes to `development` build and test Debug and full-driver
Release for x64. Pushes to `main` also cover Win32. Each job has a
180-minute timeout.

### Runtime data

vcpkg installs `gdal-data` and `proj.db` under
`vcpkg_installed\<Platform>\<triplet>\share`. Each platform has its own install
tree because vcpkg removes other triplets from a shared tree, which would break
alternating Win32 and x64 builds.
A target in `Directory.Build.targets` copies them into the output folder as
`gdal-data\` and `proj\`, and `GdalEnvironment` points GDAL and PROJ at them.
The Core project's target in `Directory.Build.targets` copies them into the
output folder as `gdal-data\` and `proj\`. Other projects reference Core and
do not copy the shared data. This prevents competing writes during parallel
solution builds. `GdalEnvironment` points GDAL and PROJ at these folders.
No `GDAL_DATA` or `PROJ_LIB` environment variable is needed or set.

### Binary cache
Expand Down Expand Up @@ -206,7 +219,7 @@ bin\Release\x64\MapWinGIS.ActiveX.Tests.exe --gtest_output=xml:activex-testresul
| `MwgVcpkgRoot` | resolved (see above) | vcpkg location |
| `MwgVcpkgFeatures` | `tests` | Semicolon-separated manifest features |
| `MwgEnableAsan` | `false` | Adds `/fsanitize=address` |
| `MwgDeployRuntimeData` | `true` | Copies `gdal-data` and `proj` to the output folder |
| `MwgDeployRuntimeData` | `true` for Core, `false` otherwise | Copies `gdal-data` and `proj` to the output folder |

## Troubleshooting

Expand Down
13 changes: 13 additions & 0 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ bin\Release\x64\MapWinGIS.ActiveX.Tests.exe --gtest_output=xml:activex-testresul
Also runnable from Visual Studio Test Explorer (GoogleTest adapter picks it
up automatically).

Run the build configuration tests from a Visual Studio developer shell:

```powershell
pwsh -File build\scripts\Test-BuildConfiguration.ps1
```

These tests pass `tests%3Bfull-drivers` on the MSBuild command line in a path
with spaces and inspect the resulting dependency arguments.
They also make sure that only Core deploys GDAL and PROJ data to the shared
output folder. CI runs them before the solution build. Builds with
`full-drivers` also test the release driver set in `GdalEnvironment` tests.
Pull requests run both Debug and full-driver Release tests for x64.

## ActiveX host integration checks

The native `MapWinGIS.ActiveX.Tests` project covers COM activation, registered
Expand Down
21 changes: 19 additions & 2 deletions docs/gdal-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ against GDAL's metadata and captures warnings and failures in `Errors()`.
`Result()` returns a snapshot without transferring output datasets;
`TakeOutputDataset()` transfers ownership of an output dataset.

For persistent outputs that the runner owns, `Run()` checks the final close
before reporting success. After successful finalization, it tries all registered
readers to reopen the output with its original access mode.
If update access fails, it tries read-only access. If no reader can open the
output, the write still succeeds with a warning and `TakeOutputDataset()` returns
`nullptr`. This includes write-only targets such as `/vsistdout/`. Anonymous outputs,
including in-memory VRT datasets, remain open. If the caller supplies an
output through `SetDatasetArgument()`, the runner checks its flush but cannot
check its eventual close. The caller retains the original reference.
Output discovery follows the selected algorithm when command-line arguments
select a child of a group.

For common operations, use a typed options structure. This example converts
an input raster to GeoTIFF and reports captured diagnostics on failure:

Expand Down Expand Up @@ -68,7 +80,8 @@ copying an option list into application documentation.

## Progress and cancellation

Progress callbacks execute synchronously on the thread running GDAL. Return
GDAL can invoke progress callbacks on worker threads. The shared adapter
serializes callbacks and protects cancellation and exception state. Return
`false` to cancel. A cancellation token can also be cancelled from another
thread and must remain alive until the operation returns:

Expand All @@ -89,6 +102,9 @@ algorithms promptly. Cancellation can leave partial output files. Callbacks
must not start another GDAL operation or wait on a thread that needs the GDAL
execution lock; callback exceptions are returned as operation failures.

The legacy `GdalUtils::GdalRasterWarp()` accepts `-multi` and threaded warp
options such as `-wo NUM_THREADS=2`, with or without a progress callback.

## ActiveX operations

The ActiveX control exposes asynchronous generic GDAL execution through its
Expand Down Expand Up @@ -213,7 +229,8 @@ The solution builds the runnable C++ example
direct GDAL calls are not covered by that lock. Synchronize access to shared
datasets and process-wide configuration yourself.
- Native Core objects are not thread-safe unless their API says otherwise.
Progress and logging callbacks run synchronously on the operation thread.
GDAL can report progress on worker threads. The adapter serializes progress
callbacks. Logging callbacks run on the thread that raises the diagnostic.
- If initialization reports missing GDAL or PROJ data, rebuild with runtime
data deployment enabled. The data directories are placed beside the DLL;
do not set `GDAL_DATA` or `PROJ_LIB` as a workaround.
Expand Down
Loading