TMS FNC Leaflet — Guides
Map options and attribution
Options is where the map is configured before a single overlay is added: the view the map opens on, which interactions the user gets, and — for Leaflet specifically — which raster source draws the world underneath everything else. Reach for it when a map should start somewhere other than the middle of the ocean, when wheel zoom or double-click zoom fights the surrounding form, or when the default OpenStreetMap tiles are not the right basemap for the data.
procedure TForm1.ConfigureLeafletOptions;
begin
TMSFNCLeaflet1.BeginUpdate;
try
{ Start view: used until the map is moved or a Coordinate/Zoom is applied. }
TMSFNCLeaflet1.Options.DefaultLatitude := 51.5074;
TMSFNCLeaflet1.Options.DefaultLongitude := -0.1278;
TMSFNCLeaflet1.Options.DefaultZoomLevel := 12;
{ Leaflet-specific: replace the built-in OpenStreetMap raster source. }
TMSFNCLeaflet1.Options.ShowBaseLayer := True;
TMSFNCLeaflet1.Options.TileServer :=
'https://tile.opentopomap.org/{z}/{x}/{y}.png';
{ Most public tile providers require visible attribution. }
TMSFNCLeaflet1.Options.ShowAttribution := True;
TMSFNCLeaflet1.Options.AttributionPrefix := 'Leaflet';
TMSFNCLeaflet1.Options.AttributionText := 'Map data: OpenTopoMap (CC-BY-SA)';
{ Interaction switches shared by every map engine. }
TMSFNCLeaflet1.Options.ShowZoomControl := True;
TMSFNCLeaflet1.Options.ZoomOnWheelScroll := True;
TMSFNCLeaflet1.Options.ZoomOnDblClick := False;
TMSFNCLeaflet1.Options.Panning := True;
finally
TMSFNCLeaflet1.EndUpdate;
end;
end;
TileServer takes an XYZ URL template and replaces the built-in OpenStreetMap source for the base layer; leaving it empty keeps that default. ShowAttribution, AttributionPrefix, and AttributionText render the credit box in the map corner — most public tile providers require it by licence, so set the text to match whatever your chosen TileServer demands rather than leaving the Leaflet default.
Two behaviours are worth knowing before tuning these:
TileServeris ignored whileShowBaseLayerisFalse. The two properties are read together, so a custom URL only takes effect with the base layer enabled. SetShowBaseLayer := Falsewhen your own tile layers (below) are meant to be the only imagery, and apply it as part of the same option pass rather than toggling it after the map has loaded.DefaultLatitude/DefaultLongitude/DefaultZoomLeveldescribe the initial view only. Once the user pans or zooms, or once the map is recentered from code, changing them has no visible effect — use the map's own navigation for later view changes.
Tile layers
Tile layers are the overlays stacked on top of the base layer: a hillshade, a weather radar sweep, a cadastral WMS layer. Add them with the public AddTileLayer(AURL, AOpacity) method, which returns the new TTMSFNCLeafletTileLayer so the source kind and any service parameters can be set on the result. TileLayers itself is a protected collection, so application code goes through AddTileLayer and ClearTileLayers rather than TileLayers.Add.
procedure TForm1.ConfigureLeafletTileLayers;
var
LOverlay: TTMSFNCLeafletTileLayer;
LWeather: TTMSFNCLeafletTileLayer;
begin
TMSFNCLeaflet1.BeginUpdate;
try
TMSFNCLeaflet1.ClearTileLayers;
{ XYZ template overlay, drawn on top of the base layer. }
LOverlay := TMSFNCLeaflet1.AddTileLayer(
'https://tile.opentopomap.org/{z}/{x}/{y}.png', 0.45);
LOverlay.Source := lsXYZ;
{ WMS service: the URL is the service endpoint and Params.Layers names
the layer(s) to request. }
LWeather := TMSFNCLeaflet1.AddTileLayer(
'https://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi', 0.6);
LWeather.Source := lsWMS;
LWeather.Params.Layers := 'nexrad-n0r-900913';
finally
TMSFNCLeaflet1.EndUpdate;
end;
end;
Source selects how the URL is interpreted: lsXYZ treats it as a {z}/{x}/{y} tile template, while lsWMS treats it as a WMS service endpoint and sends Params.Layers as the requested layer name — a WMS URL added as lsXYZ silently renders nothing. Layers draw in the order they were added, so add the broad imagery first and the detail overlay last, and give overlays an Opacity below 1 so the base layer stays readable through them.
Markers
Add markers via Markers.Add and set Coordinate.Latitude, Coordinate.Longitude, and Title.
Polygons and polylines
Use Polygons.Add (TTMSFNCLeafletPolygon) and Polylines.Add (TTMSFNCLeafletPolyline) to add area and route overlays.
Polygons with holes
A hole excludes an interior ring from a filled polygon — a lake inside a park, an exclusion zone inside a delivery area, a courtyard inside a building footprint. Drawing two separate polygons cannot express this, because the inner one would still be filled; AddHole makes the region genuinely transparent so the tiles and any lower overlay show through.
procedure TForm1.ConfigureLeafletPolygonHole;
var
LPolygon: TTMSFNCLeafletPolygon;
LOuter: TTMSFNCMapsCoordinateRecArray;
LInner: TTMSFNCMapsCoordinateRecArray;
begin
SetLength(LOuter, 4);
LOuter[0] := CreateCoordinate(51.55, -0.20);
LOuter[1] := CreateCoordinate(51.55, -0.05);
LOuter[2] := CreateCoordinate(51.46, -0.05);
LOuter[3] := CreateCoordinate(51.46, -0.20);
{ The hole ring must lie inside the outer ring. }
SetLength(LInner, 4);
LInner[0] := CreateCoordinate(51.52, -0.14);
LInner[1] := CreateCoordinate(51.52, -0.11);
LInner[2] := CreateCoordinate(51.50, -0.11);
LInner[3] := CreateCoordinate(51.50, -0.14);
TMSFNCLeaflet1.BeginUpdate;
try
LPolygon := TMSFNCLeaflet1.AddPolygon(LOuter, True);
LPolygon.AddHole(LInner);
finally
TMSFNCLeaflet1.EndUpdate;
end;
end;
AddHole takes a TTMSFNCMapsCoordinateRecArray and returns the created hole item, and can be called more than once to punch several holes out of the same polygon. The ring must lie inside the polygon's outer ring; a hole that crosses the outline produces undefined fill. Holes belong to the polygon, so clearing or rebuilding the polygon discards them — re-add them after any rebuild.
Dragging overlays
Leaflet can let the user reposition an overlay directly on the map, which is the natural gesture for correcting a geocoded address, moving a depot, or nudging a service area. Every Leaflet overlay item — marker, polygon, polyline, circle, rectangle — declares Draggable, and it is False by default so a map stays read-only until you opt in.
procedure TForm1.ConfigureLeafletDragging;
var
LMarker: TTMSFNCLeafletMarker;
LPolygon: TTMSFNCLeafletPolygon;
LCircle: TTMSFNCLeafletCircle;
LArea: TTMSFNCMapsCoordinateRecArray;
begin
TMSFNCLeaflet1.BeginUpdate;
try
{ Markers.Add and AddMarker both return the Leaflet marker class,
so Draggable is reachable without a cast. }
LMarker := TMSFNCLeaflet1.AddMarker(51.5074, -0.1278, 'Depot');
LMarker.Draggable := True;
SetLength(LArea, 4);
LArea[0] := CreateCoordinate(51.53, -0.16);
LArea[1] := CreateCoordinate(51.53, -0.09);
LArea[2] := CreateCoordinate(51.49, -0.09);
LArea[3] := CreateCoordinate(51.49, -0.16);
LPolygon := TMSFNCLeaflet1.AddPolygon(LArea, True);
LPolygon.Draggable := True;
{ Use AddCircle / AddRectangle rather than Circles.Add / Rectangles.Add:
only the Add* methods are typed as the Leaflet item class that
declares Draggable. }
LCircle := TMSFNCLeaflet1.AddCircle(CreateCoordinate(51.5033, -0.1196), 800);
LCircle.Draggable := True;
finally
TMSFNCLeaflet1.EndUpdate;
end;
TMSFNCLeaflet1.OnMarkerDragEnd := TMSFNCLeaflet1MarkerDragEnd;
end;
procedure TForm1.TMSFNCLeaflet1MarkerDragEnd(Sender: TObject;
AEventData: TTMSFNCMapsEventData);
begin
if not Assigned(AEventData.Marker) then
Exit;
{ Only AEventData.Coordinate carries the drop position - the marker item
still holds its pre-drag coordinate, so write it back explicitly. }
AEventData.Marker.Coordinate.Latitude := AEventData.Coordinate.Latitude;
AEventData.Marker.Coordinate.Longitude := AEventData.Coordinate.Longitude;
StatusLabel.Text := Format('%s moved to %.6f, %.6f',
[AEventData.Marker.Title,
AEventData.Coordinate.Latitude, AEventData.Coordinate.Longitude]);
end;
OnMarkerDragEnd fires once, when the user releases the marker, not continuously during the gesture — so it is safe to persist from directly inside the handler. AEventData.Coordinate holds the drop position and AEventData.Marker is the item that moved, resolved by its map ID.
The marker item is not updated for you. AEventData.Marker.Coordinate still holds the pre-drag latitude and longitude; only the event's own Coordinate reflects the drop. Assign it back to the item inside the handler whenever later code reads the marker's position, otherwise the map and the object model drift apart the first time the user drags anything.
The one trap is typing. Draggable is declared on the Leaflet item classes, but only some of the collections are re-typed to them: Markers, Polygons, and Polylines return Leaflet items, while Circles and Rectangles are still typed as the shared maps classes. Add circles and rectangles through AddCircle and AddRectangle — which are typed as TTMSFNCLeafletCircle and TTMSFNCLeafletRectangle — instead of Circles.Add / Rectangles.Add, and no cast is needed anywhere. There is no matching drag-end event for shapes, so when a dragged polygon's position must be persisted, read its coordinates on your own trigger (a Save button, or OnMapClick) rather than waiting for a callback.
Heat maps
Heat maps show density and intensity across a region without turning every data point into a clickable marker. Add a heat map via HeatMaps.Add (TTMSFNCLeafletHeatMap), or call AddHeatMap when a coordinate array is already available. Populate WeightedCoordinates with latitude, longitude, and a Weight value per point, then tune Opacity and the GradientStartColor/GradientMidColor/GradientEndColor colors so the overlay stays readable against the active tile layer.
procedure TForm1.ConfigureLeafletHeatMap;
var
LHeatMap: TTMSFNCLeafletHeatMap;
LWeightedCoordinate: TTMSFNCMapsWeightedCoordinate;
begin
TMSFNCLeaflet1.HeatMaps.Clear;
LHeatMap := TMSFNCLeaflet1.HeatMaps.Add;
LHeatMap.Opacity := 0.6;
LHeatMap.GradientStartColor := gcGreen;
LHeatMap.GradientMidColor := gcYellow;
LHeatMap.GradientEndColor := gcRed;
LWeightedCoordinate := LHeatMap.WeightedCoordinates.Add;
LWeightedCoordinate.Coordinate.Latitude := 51.5074;
LWeightedCoordinate.Coordinate.Longitude := -0.1278;
LWeightedCoordinate.Weight := 0.9;
LWeightedCoordinate := LHeatMap.WeightedCoordinates.Add;
LWeightedCoordinate.Coordinate.Latitude := 51.5155;
LWeightedCoordinate.Coordinate.Longitude := -0.1419;
LWeightedCoordinate.Weight := 0.6;
end;
Prefer a heat map when the audience needs a regional trend (busy areas, sensor readings, request volume) and markers or labels when they need an exact point. Call HeatMaps.Clear or ClearHeatMaps before repopulating a layer so refreshed data does not stack on top of the previous set.
Right-click events
Leaflet exposes right-click hooks for the map canvas, markers, and poly elements. Wire OnMapRightClick, OnMarkerRightClick, and OnPolyElementRightClick when the application needs context menus, quick edit actions, or inspection panels without taking over the normal left-click navigation and selection flow.
procedure TForm1.ConfigureLeafletContextEvents;
begin
TMSFNCLeaflet1.OnMapRightClick := TMSFNCLeaflet1MapRightClick;
TMSFNCLeaflet1.OnMarkerRightClick := TMSFNCLeaflet1MarkerRightClick;
TMSFNCLeaflet1.OnPolyElementRightClick := TMSFNCLeaflet1PolyElementRightClick;
end;
procedure TForm1.TMSFNCLeaflet1MapRightClick(Sender: TObject;
AEventData: TTMSFNCMapsEventData);
begin
{ Show a map-level context menu at AEventData.Coordinate. }
end;
procedure TForm1.TMSFNCLeaflet1MarkerRightClick(Sender: TObject;
AEventData: TTMSFNCMapsEventData);
begin
if Assigned(AEventData.Marker) then
AEventData.Marker.Title := 'Selected marker';
end;
procedure TForm1.TMSFNCLeaflet1PolyElementRightClick(Sender: TObject;
AEventData: TTMSFNCMapsEventData);
begin
if Assigned(AEventData.PolyElement) then
AEventData.PolyElement.DataString := 'Selected poly element';
end;
Each handler receives a TTMSFNCMapsEventData argument, so the same pattern reads the clicked Coordinate, Marker, or PolyElement depending on which event fired. Keep handlers short; long-running work should be deferred so the embedded browser stays responsive.