Interactive Maps
Leaflet + OpenStreetMap maps with markers, clustering, layer toggles, and click-to-place pin picking - no API key, no billing.
Table of Contents
Basic map
Markers and popups
Clustering
Layer toggles
Click to place
Embedding in articles
References
Basic map
The minimum: a centre, a zoom level, and a height. Leaflet needs an explicit height or it renders into a zero-pixel container and shows nothing.
<SharkMap Height="300px" CenterLat="39.8283" CenterLng="-98.5795" Zoom="4" FitToMarkers="false" />Markers and popups
Pass a list of MapMarker records. The map fits itself to whatever it's given,
and each pin gets a popup built from its name, description, thumbnail, and link. The
component knows nothing about what a marker represents, which is what lets campgrounds,
photos, and any future category share it.
new MapMarker(1, "Acadia National Park", 44.3386, -68.2733, "campground",
Description: "Maine's rocky Atlantic coastline.");Clustering
120 markers at once. Without clustering these overlap into an unreadable smear; with
Clustered="true" nearby pins collapse into a counted bubble that splits apart
as you zoom in. This is what makes a photo gallery of a few thousand images usable.
Layer toggles
Markers are grouped by their LayerKey, and ShowLayerToggle adds a
checkbox control for turning each group on and off. Adding a new category to the site is a
new layer key - the map itself doesn't change.
Click to place
With PickMode, clicking drops a draggable pin and raises
OnLocationPicked. This is how coordinates get entered in the admin pages -
individual campsites rarely have an address to geocode, so pointing at them on a map is
both faster and more accurate.
Click anywhere on the map above to drop a pin.
Embedding in articles
Blog content is stored as HTML and rendered through a MarkupString, which
can't host a Blazor component. So articles use a plain-text token instead: the post page
splits the content around it and renders a real map in its place. Plain text also survives
the HTML sanitizer that AI-written and MCP-written articles pass through, which a custom
element or data- attribute would not.
[[map:campground/lake-george-koa]] a single place
[[map:layer/campground]] every published place in a layer
[[map:post]] places linked to this article
[[map:layer/photo height=500 zoom=6]] optional height and zoomReferences
For more detailed information and advanced scenarios, check out these helpful resources:
Leaflet documentation
The mapping library behind this component (BSD-2, no key required).OpenStreetMap tile usage policy
What the free tile servers do and don't allow.Leaflet.markercluster
The clustering plugin used on dense layers.Nominatim usage policy
Rules for the free geocoder that turns addresses into coordinates.ASP.NET Core JavaScript interop
How the Blazor component drives Leaflet.