From e6dc8c27fded6b2471d7167702680f251cdf71ac Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:38:12 -1000 Subject: [PATCH] feat: support custom HTML/SVG element markers in MapLibre Add Element, Anchor, and Rotation fields to the Marker struct so users can render custom icons (ships, circles, buildings) instead of the default pin. When Element is set, Color is ignored and the raw HTML/SVG is injected as the marker's DOM element. Update the example to demonstrate a static ship SVG marker with rotation and a signal-driven vehicle marker using a custom SVG circle. --- internal/examples/maplibre/main.go | 20 ++++++++++++++++++-- maplibre/js.go | 17 ++++++++++++++++- maplibre/types.go | 13 +++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/internal/examples/maplibre/main.go b/internal/examples/maplibre/main.go index 21b606b..f6610e7 100644 --- a/internal/examples/maplibre/main.go +++ b/internal/examples/maplibre/main.go @@ -75,14 +75,30 @@ func main() { }, }) - // Purple vehicle marker — reads shared Go state + // Custom SVG ship marker (static) + m.AddMarker("ship", maplibre.Marker{ + LngLat: maplibre.LngLat{Lng: -122.38, Lat: 37.80}, + Element: ``, + Anchor: "center", + Rotation: 45, + Popup: &maplibre.Popup{ + Content: "Ferry
Heading NE
", + }, + }) + + // Custom SVG vehicle marker — reads shared Go state vehicleLng := c.Signal(-122.43) vehicleLat := c.Signal(37.77) m.AddMarker("vehicle", maplibre.Marker{ LngSignal: vehicleLng, LatSignal: vehicleLat, - Color: "#9b59b6", + Element: ``, + Anchor: "center", }) c.OnInterval(time.Second, func() { diff --git a/maplibre/js.go b/maplibre/js.go index 67952f1..7137daa 100644 --- a/maplibre/js.go +++ b/maplibre/js.go @@ -179,10 +179,25 @@ func initScript(m *Map) string { // markerBodyJS generates JS to add a marker, assuming `map` is in scope. func markerBodyJS(mapID, markerID string, mk Marker) string { var b strings.Builder + + if mk.Element != "" { + b.WriteString(fmt.Sprintf( + `var _mkEl=document.createElement('div');_mkEl.innerHTML=%s;`, + jsonStr(mk.Element))) + } + opts := "{" - if mk.Color != "" { + if mk.Element != "" { + opts += `element:_mkEl.firstElementChild||_mkEl,` + } else if mk.Color != "" { opts += fmt.Sprintf(`color:%s,`, jsonStr(mk.Color)) } + if mk.Anchor != "" { + opts += fmt.Sprintf(`anchor:%s,`, jsonStr(mk.Anchor)) + } + if mk.Rotation != 0 { + opts += fmt.Sprintf(`rotation:%s,`, formatFloat(mk.Rotation)) + } if mk.Draggable { opts += `draggable:true,` } diff --git a/maplibre/types.go b/maplibre/types.go index c9fa611..5772772 100644 --- a/maplibre/types.go +++ b/maplibre/types.go @@ -302,6 +302,19 @@ type Marker struct { Draggable bool Popup *Popup + // Element is raw HTML/SVG used as a custom marker instead of the + // default pin. When set, Color is ignored. + // Do not pass untrusted user input without sanitizing it first. + Element string + + // Anchor controls which part of the element sits at the coordinate. + // Values: "center" (default for custom elements), "bottom" (default + // for the pin), "top", "left", "right", "top-left", etc. + Anchor string + + // Rotation is clockwise degrees. Useful for directional icons (ships, vehicles). + Rotation float64 + // Signal-backed position. When set, signals drive marker position reactively. // Initial position is read from the signal values. LngLat is ignored when signals are set. // If Draggable is true, drag updates write back to these signals.