Markers & popups
Add, update and remove markers one at a time or in batches, with custom icons, titles and info popups. FitToMarkersAsync frames them all.
Drop a MapControl into WinForms, WPF, .NET MAUI or Blazor and get markers, routes, clustering, heatmaps and drawing tools, plus geocoding, directions, elevation and places services. One API across Google Maps, Azure Maps, HERE, Apple MapKit, Leaflet and OpenLayers.
> dotnet add package TMS.Maps.Wpf.Trial --prerelease


Each framework gets a native control that feels at home there: the designer in WinForms, XAML properties in WPF and .NET MAUI, parameters and EventCallbacks in Blazor. Behind it is the same map, the same methods and the same events.
Drop it on a form in the designer or create it in code. Standard .NET events.
TMS.Maps.WinForms.Trial
A XAML control for MAUI apps on Android, iOS, Mac Catalyst and Windows.
TMS.Maps.Maui.Trial
A Razor component for Blazor Server and WebAssembly, also hostable from ASP.NET Core MVC.
TMS.Maps.Blazor.Trial
Markers, shapes, overlays, layers and events all work the same on every provider, so you can change providers without rewriting your map code.
Add, update and remove markers one at a time or in batches, with custom icons, titles and info popups. FitToMarkersAsync frames them all.
Group thousands of nearby markers into clusters that split apart as you zoom in. Tune the radius and maximum zoom with MapClusterOptions.
Show where your data is densest with weighted heatmap layers that you can show, hide and update at runtime.
Polylines, polygons, circles and rectangles, with full control over stroke, fill, opacity and z-order.
Let users draw markers, polylines, polygons, circles or rectangles themselves, and get the shape back in DrawingCompleted.
Place text labels on the map, or pin your own HTML panels (legends, toolbars, status cards) on top of it with element containers.
Stack custom tile overlays such as weather, cadastral or your own tiles, or point the base map at a tile server you host.
Switch live traffic and public transit layers on or off with one call, on providers that support them.
Pan, zoom, fit to bounds, tilt and rotate. Convert between screen pixels and coordinates for your own overlays.
Click, double-click, right-click, drag and vertex-changed events on the map, markers and every shape type.
Move from Leaflet during development to Google, Azure or HERE in production, or let users pick, without changing your map code.
Load and save GeoJSON features and GPX tracks, so routes and regions can move between your app and other GIS tools.
Declare the control the way your framework expects. The code that fills the map is the same everywhere, so a feature built for the desktop app works in the web app too.
ServiceResult<T>, so errors are values you can check. They don't surprise you as exceptions.<Window xmlns:maps="clr-namespace:TMS.Maps.Wpf;assembly=TMS.Maps.Wpf">
<maps:MapControl x:Name="Map"
Provider="Leaflet"
CenterLatitude="50.8466"
CenterLongitude="4.3528"
ZoomLevel="9" />
</Window>
using TMS.Maps.Core.Models;
var map = new TMS.Maps.WinForms.MapControl
{
Dock = DockStyle.Fill,
Provider = Providers.Leaflet,
CenterLatitude = 50.8466,
CenterLongitude = 4.3528,
ZoomLevel = 9
};
Controls.Add(map);
@using TMS.Maps.Core.Models
@using TMS.Maps.UI.Web
<MapControl @ref="map"
Provider="Providers.Leaflet"
CenterLatitude="50.8466"
CenterLongitude="4.3528"
ZoomLevel="9"
Width="100%" Height="600px" />
<ContentPage xmlns:maps="clr-namespace:TMS.Maps.Maui;assembly=TMS.Maps.Maui">
<maps:MapControl x:Name="Map"
Provider="Leaflet"
CenterLatitude="50.8466"
CenterLongitude="4.3528"
ZoomLevel="9" />
</ContentPage>
using TMS.Maps.Core.Models;
using TMS.Maps.Core.Models.UI;
// Call once the map's ready event has fired
await map.AddMarkersAsync(new[]
{
new MapMarker(new Coordinate(51.0543, 3.7174)) { Title = "Ghent depot" },
new MapMarker(new Coordinate(51.2194, 4.4025)) { Title = "Antwerp depot" },
});
await map.EnableClusteringAsync(new MapClusterOptions { Radius = 80 });
await map.AddCircleAsync(50.8466, 4.3528, radiusMetres: 15000);
await map.FitToMarkersAsync();
The services are plain .NET classes. Use them next to the map control, or on their own in console apps, background workers and ASP.NET Core back ends. Each one takes a provider and returns a typed result.
Turn addresses into coordinates and coordinates back into addresses, for search boxes, address validation and "what's here?" lookups.
using var geocoding = new GeocodingService(Providers.Google, "YOUR_API_KEY");
// Address → coordinates
var forward = await geocoding.GetAsync("Grote Markt, Brussels");
if (!forward.IsError(out var places))
Console.WriteLine(places.First().Coordinate);
// Coordinates → address
var reverse = await geocoding.GetAsync(new Coordinate(50.8467, 4.3525));
if (!reverse.IsError(out var addresses))
Console.WriteLine(addresses.First().FormattedAddress);
Calculate routes between two points or through a list of waypoints. Each result includes legs, steps, distance, duration and geometry you can draw on the map.
using var directions = new DirectionsService(Providers.Here, "YOUR_API_KEY");
var origin = new Coordinate(51.2093, 3.2247); // Bruges
var destination = new Coordinate(50.6326, 5.5797); // Liège
var result = await directions.GetAsync(origin, destination);
if (!result.IsError(out var routes))
{
foreach (var leg in routes.First().Legs)
Console.WriteLine($"{leg.Distance} m, {leg.Duration} s");
}
Search for points of interest by text or near a location, for store locators, "find nearby" features and autocomplete.
using var places = new PlacesService(Providers.Azure, "YOUR_API_KEY");
var request = new PlacesRequest(
Query: "coffee",
RequestType: PlacesRequestType.TextSearch,
Location: new Coordinate(50.8466, 4.3528),
Radius: 1500);
var result = await places.GetAsync(request);
if (!result.IsError(out var hits))
foreach (var place in hits)
Console.WriteLine(place.Name);
Look up the elevation of one point or many, for height profiles along a route, hiking and cycling apps, and terrain analysis.
using var elevation = new ElevationService(Providers.Google, "YOUR_API_KEY");
var result = await elevation.GetAsync(new Coordinate(50.4108, 6.0729)); // Signal de Botrange
if (!result.IsError(out var points))
Console.WriteLine($"{points.First().Elevation} m");
Render a map as an image for reports, emails, PDFs and thumbnails. Get just the URL, or download the image bytes.
using var staticMap = new StaticMapService(Providers.Google, "YOUR_API_KEY");
// Just the URL, e.g. to bind to an Image source
string url = staticMap.GetUrl(new Coordinate(50.8466, 4.3528));
// Or download the rendered image
var request = new StaticMapRequest(new Coordinate(50.8466, 4.3528), Width: 800, Height: 600, Zoom: 13);
var result = await staticMap.GetAsync(request);
if (!result.IsError(out var image))
{
await using var file = File.Create("map.png");
await image.CopyToAsync(file);
}
Find the current device's or caller's location, or look up any IP address, to center the map on the user or localize content.
using var location = new LocationService(Providers.IPStack, "YOUR_API_KEY");
var current = await location.GetAsync(); // this caller
var byIp = await location.GetByIpAsync("8.8.8.8");
if (!current.IsError(out var loc))
Console.WriteLine($"{loc.Coordinate.Latitude}, {loc.Coordinate.Longitude}");
Build and edit multi-stop routes directly on the map, with undo history and a pluggable renderer.
Ask GetProvidersByCapability(Directions) which providers can do the job, or check one with SupportsElevation().
Services call the provider directly with your own API key. Nothing is proxied through TMS.
Start with keyless Leaflet or OpenLayers maps, then switch to a commercial provider when you need its data or terms. The table shows what each provider supports in this beta.
| Provider | Map control | Geocoding | Directions | Places | Elevation | Static maps | Location | API key |
|---|---|---|---|---|---|---|---|---|
| Maps and services | ||||||||
| 1 | 1 | 1 | 1 | 1 | 1 | 1 | Required | |
| 1 | 1 | 1 | 1 | 1 | 1 | Required | ||
| 1 | 1 | 1 | 1 | 1 | Required | |||
| 1 | Token | |||||||
| 1 | Not needed | |||||||
| 1 | Not needed | |||||||
| Services only | ||||||||
| 1 | 1 | Required | ||||||
| 1 | 1 | 1 | Required | |||||
| 1 | 1 | 1 | Required | |||||
| 1 | Required | |||||||
| 1 | Required | |||||||
Leaflet and OpenLayers load OpenStreetMap tiles by default, subject to the OSM tile usage policy. Provider API keys come from your own account with that provider and are billed by them. The privacy & network guide lists every host each provider loads from.
One key works with all four beta packages, the map controls and every service. It's checked offline, so there's no activation step and no license server.
It takes less than a minute.
Your key arrives by email within a few minutes.
Pass it to TMSLicensing.RegisterLicense at startup and the map is ready.
The open beta is on nuget.org. You don't need a private feed, a nuget.config or a token.
The key is a single string. Keep it out of source control, for example in user secrets or an environment variable.
Install one package per project. Each one already includes the map control and every service.
> dotnet add package TMS.Maps.Wpf.Trial --prerelease
Do this before creating any map control or service, in App.xaml.cs, Program.cs or MauiProgram.cs. The key is checked offline.
using TMS.Maps.Core.Licensing;
TMSLicensing.RegisterLicense("YOUR-BETA-LICENSE-KEY");
Put a MapControl in your window, form, page or component (see the examples above). With Provider="Leaflet" it runs with no provider key.
The guides have a WinForms / WPF / .NET MAUI / Blazor selector. Pick your framework once and every page follows it.
Install, license and show your first map, step by step for each framework.
Markers, shapes, clustering, heatmaps, camera, events and provider switching.
Geocoding, directions, elevation, places, static maps and location, with the result pattern.
Every public type and member, generated from the source so it matches the shipped build.
Every host the SDK contacts, for firewall allow-lists and privacy statements.
The evaluation terms you accept when you install a beta package.
Ask questions, report bugs and see what other beta users are building.
The TMS Software support center for all products.
Release notes, tutorials and news about the beta.
The beta is when your input matters most. Tell us what breaks, what's missing and what feels awkward in the API. We read every post and use it to decide what ships in 1.0.
TMS.Maps.Wpf.Trial 1.0.0-beta.xYes. Request a free beta license key and install the .Trial package for your framework. The beta is for evaluation only, not for production use.
Not for Leaflet or OpenLayers, which use OpenStreetMap tiles by default. Google Maps, Azure Maps, HERE, Apple MapKit and the service-only providers need your own key from that provider, and they bill you directly.
No. The license key is checked locally against a public key in the assembly. The SDK sends no license data, machine data or telemetry. The map only talks to the provider you chose. See the privacy & network guide.
WinForms, WPF, .NET MAUI (Android, iOS, Mac Catalyst and Windows) and Blazor (Server and WebAssembly), all on .NET 10.
Install one .Trial package per project. Each one already includes the map control and all services. Combining packages causes duplicate type conflicts.
With a valid key the map works normally. A small "TMS .NET Maps BETA — expires date" strip along the bottom links to the docs and the feedback forum, and the first map in each app run shows a one-time welcome notice.
The map shows a "Map unavailable" panel and service calls throw TMSLicensingException. Update to the latest beta package and key to keep evaluating. TMSLicensing.Status tells you the expiry date in advance.
On the TMS.NET Maps forum, or by email to info@tmssoftware.com. See what to include.
Get a free beta key, install one package, and your map is running.