Skip to content
Open
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
40 changes: 24 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,36 @@ selectively and must not prevent structural improvements.

## Status

Early. The core currently includes GDAL setup and OGR-backed vector dataset
and layer access, including feature and field reading and updates. Available
formats depend on the GDAL drivers enabled in the build; tests cover shapefiles
and GeoPackage. The ActiveX layer supports COM activation, vector loading,
polygon rendering, basic map navigation, and asynchronous generic GDAL
Early. The core includes GDAL setup, OGR-backed vector dataset and layer
access, viewport-windowed raster display, XYZ tile layers, coordinate
reprojection, and map navigation.
Available formats depend on the GDAL drivers enabled in the build; tests cover
shapefiles and GeoPackage. The ActiveX layer supports COM activation, vector
and raster loading, polygon/line/point rendering, keyboard, touch and mouse
navigation, navigation history, animated fits, and asynchronous generic GDAL
algorithms with progress and cancellation events.

The core also supports a shared map viewport with pan, center zoom, anchored
zoom, and fitting to the visible map or a named layer. These navigation calls
do not depend on a window. ActiveX exposes map/layer zoom operations and
supports cursor-anchored mouse-wheel zoom and left-button drag-to-pan.
The ActiveX control also accepts supported vector datasets through file
drag-and-drop.
The map uses the first layer's declared coordinate reference system (CRS).
Layers in other declared CRSs are reprojected into it; layers with and without
a CRS cannot be mixed. Raster layers are read for the visible map window at
viewport resolution. XYZ URL templates are supported as Web Mercator tile
layers, with GDAL-managed in-memory tile caching. OpenGL renders supported
simple polygons; GDI clips and draws polygons, lines, points, and rasters.

The core supports pan, center and cursor-anchored zoom, map/layer fitting,
back/forward navigation history, and timed animated fits. ActiveX exposes
these operations, configurable zoom factors, mouse-wheel zoom, drag-to-pan,
arrow-key navigation, and touch pan/pinch gestures. ActiveX animation runs on
the control's owner-thread timer.

Vector loads add layers rather than replace them. The first layer fits the
initial view; later loads preserve it. Call `ZoomToMap` to include new layers
in the visible extent, or `ClearLayers` to release loaded datasets before
switching or refreshing them.
in the visible extent, `MoveLayer` to change drawing order, or `ClearLayers`
to release loaded datasets before switching or refreshing them.

Dropped files append as layers and the control fits the combined visible
extent after processing the batch. Unsupported or invalid files are skipped;
valid files in the same drop are still loaded.
Dropped vector and raster files append as layers and the control fits the
combined visible extent after processing the batch. Unsupported or invalid
files are skipped; valid files in the same drop are still loaded.

## Build and test

Expand Down
157 changes: 86 additions & 71 deletions docs/ARCHITECTURE.md

Large diffs are not rendered by default.

52 changes: 33 additions & 19 deletions docs/PORTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,32 +49,38 @@ choose deliberately whether it belongs in v6 and document important departures.
## Current ActiveX compatibility boundary

The initial `MapWinGIS.ocx` exposes a deliberately small v6 automation
contract. `IMapWinGISControl` reports Core initialization, loads a vector
dataset, and reports whether a layer is loaded and its feature count.
contract. `IMapWinGISControl` reports Core initialization, loads a vector dataset, and
reports whether a layer is loaded and its feature count.
`IMapWinGISControl2` adds renderer preference, active backend, and status.
`IMapWinGISControl3` adds `ClearLayers` and is the default automation interface.
`IMapWinGISControl3` adds `ClearLayers`. `IMapWinGISControl4` adds layer
management and basic zoom methods, `IMapWinGISControl5` adds raster loading,
`IMapWinGISControl6` adds navigation history and animated fits, and
`IMapWinGISControl7` is the current default automation interface. Version 7
adds layer reordering and XYZ tile loading.
Loads add layers, including duplicate loads of one dataset. Call `ClearLayers`
before switching or refreshing datasets. It releases all layers and resets
the cached feature total while keeping the viewport.
The windowless control accepts Explorer file drops through its OLE drop-target
hook. Dropped supported vector datasets append as layers, then the control
fits the combined visible extent once. Unsupported, missing, or invalid paths
are skipped without discarding other files that loaded successfully. A partial
drop returns `S_FALSE` with a copy effect; a drop with no successful files
returns a failing HRESULT and no effect. Failed paths and HRESULTs are written
to the debugger output.
hook. Dropped supported vector and raster datasets append as layers, then the
control fits the combined visible extent once. Unsupported, missing, or
invalid paths are skipped without discarding other files that loaded
successfully. A partial drop returns `S_FALSE` with a copy effect; a drop with
no successful files returns a failing HRESULT and no effect. Failed paths and
HRESULTs are written to the debugger output.
New members belong on a new derived interface; do not change a published
interface IID or silently add vtable members to it.

These are new interfaces, not the MapWinGIS 5 type-library interfaces, and the
control does not claim binary or API compatibility with existing v5 clients.
Each control load selects the first dataset layer. Core prepares an ordered
scene for all visible map layers, including Polygon, MultiPolygon,
CurvePolygon, and MultiSurface geometry. It
does not expose the broad v5 map, layer, shape, event, or editing API. It draws
in source coordinates without reprojection. OpenGL only handles convex
single-ring polygons; GDI handles the supported polygon scene as the CPU
fallback.
Each vector load selects the first dataset layer. Core prepares an ordered
scene for all visible map layers, including point, line, and polygon
geometries; raster layers are read by viewport-sized windows. XYZ tile URL
templates use the Web Mercator tile matrix and GDAL's in-memory cache. The map
uses the first layer's declared CRS, transforms other declared CRSs into it,
and rejects mixing referenced and unreferenced layers. The control does not expose the
broad v5 map, layer, shape, event, or editing API. OpenGL only handles convex
single-ring polygons; GDI handles the supported vector and raster scene as the
CPU fallback.

## Map and viewport behavior

Expand All @@ -83,9 +89,17 @@ independent of its display name and current list index. `MapView` owns the
viewport and the visible extent shared by every rendered layer. Map X increases
right and map Y increases up; screen X increases right and screen Y increases
down. Resizing keeps the view center and scale where possible. Loading through
`LoadVectorDataset` appends layers and preserves an existing view; a file-drop
batch appends layers and then fits the combined visible extent. Source
coordinates are not reprojected, so layers must use compatible coordinates.
`LoadVectorDataset` or `LoadRasterDataset` appends layers and preserves an
existing view; a file-drop batch appends layers and then fits the combined
visible extent. Layers with declared CRSs are transformed into the first
layer's CRS.

`IMapWinGISControl6` adds pan, anchored zoom, configurable zoom factors,
back/forward view history, animated fits, and host input for mouse, keyboard,
and touch pan/pinch gestures. `IMapWinGISControl7` adds `MoveLayer` and
`LoadXyzTileLayer`. These are new derived interfaces; earlier IIDs remain
unchanged. Animated map/layer fits run on the control's owner-thread timer and
can be canceled explicitly.

Keep these gaps visible when extending the COM surface. Reuse Core behavior,
add only the automation members required by a concrete use case, and retain a
Expand Down
19 changes: 19 additions & 0 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,22 @@ unchanged-geometry cache. Total memory includes the OpenGL driver and context.
- Wrap expected GDAL noise in `GdalEnvironment::QuietScope`.
- Tests use local generated data and do not require network access by default.
Set `MWG_RUN_NETWORK_GDAL_TESTS=1` to enable the public `/vsicurl/` COG test.

### Phase 7 final map-model and navigation verification

The Debug x64 solution build succeeded. The native test executable ran 323
tests: 320 passed and 3 were skipped (the opt-in network test, separately
exercised ActiveX hosting, and the opt-in rendering baseline). Coverage
includes vector reprojection and point/line/polygon scenes, raster preview and
reprojection, navigation history and animated transitions, the new ActiveX
navigation interface, its owner-thread animation timer, keyboard panning, and
one-entry-per-gesture navigation history.
The automated C# ActiveX host passed all 16 checks, including programmatic
navigation, mouse-wheel zoom, drag-to-pan, resize, repaint, and control
recreation. Touch gesture hardware input remains a manual validation item.

The viewport-raster and XYZ-tile follow-up passed the Debug x64 build and all
337 native tests (334 passed, 3 skipped). This includes an XYZ integration
test that serves a generated PNG tile from a loopback HTTP listener and checks
the requested tile path and rendered pixel; it does not require public network
access. The automated C# ActiveX host also passed all 16 checks.
38 changes: 34 additions & 4 deletions src/MapWinGIS.ActiveX/GdalDispatchWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

/**
* @file GdalDispatchWindow.h
* @brief Declares the message-only window used to dispatch GDAL notifications.
* @brief Declares the owner-thread window for GDAL notifications and navigation timers.
*
* The window is created on the control's owning COM thread and accepts only
* posted wake-up messages from native workers.
* The window is created on the control's owning COM thread. It accepts
* worker wake-up messages and timer messages for navigation animation.
*/
#pragma once

Expand All @@ -17,7 +17,9 @@ namespace mwg::activex
{
inline constexpr UINT kGdalDispatchMessage = WM_APP + 0x316;
inline constexpr UINT_PTR kGdalDispatchRetryTimerId = 1;
inline constexpr UINT_PTR kNavigationAnimationTimerId = 2;
inline constexpr UINT kGdalDispatchRetryIntervalMilliseconds = 100;
inline constexpr UINT kNavigationAnimationIntervalMilliseconds = 16;

class GdalDispatchWindow final :
public ATL::CWindowImpl<GdalDispatchWindow>,
Expand Down Expand Up @@ -68,9 +70,35 @@ namespace mwg::activex
_deferredLifetime = shared_from_this();
}
::KillTimer(m_hWnd, kGdalDispatchRetryTimerId);
::KillTimer(m_hWnd, kNavigationAnimationTimerId);
DestroyWindow();
}

[[nodiscard]] HRESULT SetAnimationTimer(const bool enabled) noexcept
{
if (m_hWnd == nullptr)
{
return E_UNEXPECTED;
}
if (!enabled)
{
::KillTimer(m_hWnd, kNavigationAnimationTimerId);
return S_OK;
}
if (::SetTimer(
m_hWnd,
kNavigationAnimationTimerId,
kNavigationAnimationIntervalMilliseconds,
nullptr) == 0)
{
const DWORD error = ::GetLastError();
return error == ERROR_SUCCESS
? E_FAIL
: HRESULT_FROM_WIN32(error);
}
return S_OK;
}

BEGIN_MSG_MAP(GdalDispatchWindow)
MESSAGE_HANDLER(kGdalDispatchMessage, OnDispatch)
MESSAGE_HANDLER(WM_TIMER, OnRetryTimer)
Expand Down Expand Up @@ -117,7 +145,9 @@ namespace mwg::activex
const std::shared_ptr<GdalDispatchWindow> lifetime = shared_from_this();
handled = TRUE;
MessageGuard messageGuard(*this);
if (timerId == kGdalDispatchRetryTimerId && _callback != nullptr)
if ((timerId == kGdalDispatchRetryTimerId ||
timerId == kNavigationAnimationTimerId) &&
_callback != nullptr)
{
_callback(_context);
}
Expand Down
7 changes: 7 additions & 0 deletions src/MapWinGIS.ActiveX/GdalEventSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ namespace mwg::activex
return S_OK;
}

HRESULT GdalEventSource::SetAnimationTimer(const bool enabled) noexcept
{
return _dispatchWindow == nullptr
? E_UNEXPECTED
: _dispatchWindow->SetAnimationTimer(enabled);
}

std::uint64_t GdalEventSource::Begin(
std::string algorithmPath,
std::vector<std::string> arguments)
Expand Down
1 change: 1 addition & 0 deletions src/MapWinGIS.ActiveX/GdalEventSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ namespace mwg::activex
GdalEventSource& operator=(const GdalEventSource&) = delete;

[[nodiscard]] HRESULT EnsureDispatchWindow() noexcept;
[[nodiscard]] HRESULT SetAnimationTimer(bool enabled) noexcept;
[[nodiscard]] std::uint64_t Begin(
std::string algorithmPath,
std::vector<std::string> arguments);
Expand Down
Loading