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

Every map on this page is rendered by Leaflet against OpenStreetMap's tile servers. There is no API key, no usage meter, and no bill - the trade is that OSM's tile policy rules out heavy or commercial traffic, so the tile source is a config value (Maps:TileUrl) that can be swapped for a hosted provider if the site ever outgrows it.

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 zoom
References

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.
An unhandled error has occurred. Reload 🗙