From 641312aba995b385fac0b01e9eec3caf4c09b58c Mon Sep 17 00:00:00 2001 From: Conrad Schulz Date: Wed, 1 Jul 2026 12:33:05 +0000 Subject: [PATCH] fix: proper 27x41 pin proportions, white border+dot fully opaque - Non-square canvas (pin-m: 27x41 @1x = 54x82 @2x, Maptiler standard) - r=37% of width -> circle fills 74% of canvas width - r/d=0.37 -> 22deg opening angle (Google Maps standard) - White border: borderW=max(2px,8%width), A=255 (fully opaque) - White inner dot: r*0.38, A=255 (fully opaque, clearly visible) - Fill: 85% opacity (A=217) --- docs/ADMIN.md | 2 +- .../prompts/2026-07-01_marker-api_session.md | 18 ++++-- docs/history/summary/PROJECT_CONTEXT.md | 2 +- services/marker-api/main.go | 56 ++++++++++--------- 4 files changed, 47 insertions(+), 31 deletions(-) diff --git a/docs/ADMIN.md b/docs/ADMIN.md index 190a3db..b7cb804 100644 --- a/docs/ADMIN.md +++ b/docs/ADMIN.md @@ -237,7 +237,7 @@ docker compose logs -f tileserver # Raster-Rendering - Image: `192.168.178.6:8083/cschulz/rd13_tile_server-marker-api:latest` (Forgejo Registry) - API Root: `http://localhost:9984` - Endpunkt: `/styles/v4/marker/{type}+{hexcolor}(@{scale}x).png` -- Typen: `pin-s` (24px), `pin-m` (32px), `pin-l` (40px); Canvas quadratisch, 80% opak, schlanke Teardrop-Form (r/d≈0.35) +- Typen: `pin-s` (20×30px), `pin-m` (27×41px, Maptiler-Standard), `pin-l` (34×52px); 85% opak, weißer Rand + Innenpunkt - Image neu bauen: `docker compose build marker-api && docker compose up -d marker-api` - CI: Forgejo baut + pusht automatisch bei Änderungen in `services/marker-api/` diff --git a/docs/history/prompts/2026-07-01_marker-api_session.md b/docs/history/prompts/2026-07-01_marker-api_session.md index 22ce037..aedd467 100644 --- a/docs/history/prompts/2026-07-01_marker-api_session.md +++ b/docs/history/prompts/2026-07-01_marker-api_session.md @@ -113,9 +113,19 @@ Geometry-Fix: r=35.5% der Breite, tipY=97.5%, Spitze passt jetzt sauber in die C ### Prompt 13 «Das sieht aber immer noch komisch aus...» -### Antwort 13 -Ursache identifiziert: `r/d = 0.59` (Kreisradius / Abstand zur Spitze) → Öffnungswinkel 36° → Tangenten laufen flach aus → Form sieht aus wie ein Oval. +### Prompt 14 +«besser aber immer noch nicht perfekt ich will es perfekt!! und ich will einen weißen rand und weißen punkt in der mitte wenn es geht» -Fix: `r = size * 0.24` (statt 0.355) → `r/d ≈ 0.35` → Öffnungswinkel 20° → schlanker Pin. +### Antwort 14 +Ursache der Verzerrung identifiziert: Quadratische Canvas (32×32) erzwingt falsche Proportionen. Ein Teardrop-Pin kann in einem Quadrat nicht korrekt aussehen. -Verifiziert mit blauen Test-Pin (±4285F4): sieht jetzt aus wie Google-Maps-Pin. +Lösung: Korrekte nicht-quadratische Canvas entsprechend Maptiler-Standard: +- pin-s: 20×30px, pin-m: 27×41px, pin-l: 34×52px (@1x) +- Der Pin ankert an der Spitze (bottom-center), nicht am Bildmittelpunkt +- r = 37% der Breite (Kreis füllt 74% der Canvas-Breite) +- r/d = 0.37 → Öffnungswinkel 22° (Google-Maps-Standard) +- Weißer Rand: borderW = max(2px, 8% der Breite) mit A=255 (vollständig opak) +- Weißer Innenpunkt: r * 0.38 mit A=255 (vollständig opak) +- Füllfarbe: 85% opak (A=217) + +Verifiziert: pin-m@2x = 54×82px, sieht aus wie Google Maps Pin diff --git a/docs/history/summary/PROJECT_CONTEXT.md b/docs/history/summary/PROJECT_CONTEXT.md index 353e66e..93dff72 100644 --- a/docs/history/summary/PROJECT_CONTEXT.md +++ b/docs/history/summary/PROJECT_CONTEXT.md @@ -1,6 +1,6 @@ # PROJECT_CONTEXT – rd13_tile_server -**Letzte Aktualisierung:** 2026-07-01 – ✅ **Marker-API**: Go-Service für Maptiler-kompatible Marker-Endpunkte (`/styles/v4/marker/*`). 32×32px Canvas, schlanker Teardrop-Pin (`r/d≈0.35`, Öffnungswinkel 20°), 80% Transparenz. Forgejo CI baut + pusht Image in lokale Container-Registry. +**Letzte Aktualisierung:** 2026-07-01 – ✅ **Marker-API**: Go-Service für Maptiler-kompatible Marker-Endpunkte (`/styles/v4/marker/*`). Korrekte 27×41px Proportionen (Maptiler-Standard), weißer Rand (A=255), weißer Innenpunkt (A=255), 85% opaker Fill. Forgejo CI baut + pusht Image. --- diff --git a/services/marker-api/main.go b/services/marker-api/main.go index e1b068d..e227f6c 100644 --- a/services/marker-api/main.go +++ b/services/marker-api/main.go @@ -21,11 +21,14 @@ var markerPattern = regexp.MustCompile( `^/styles/v4/marker/([a-z-]+)\+([0-9a-fA-F]{6})(?:@(\d+(?:\.\d+)?x))?\.png$`, ) -// Basis-Größen in logischen Pixeln (quadratische Canvas) -var baseSizes = map[string]float64{ - "pin-s": 24, - "pin-m": 32, - "pin-l": 40, +// Basis-Abmessungen (Breite × Höhe) – entspricht Maptiler-Standard. +// Der Pin ankert an der Spitze (bottom-center), nicht am Bildmittelpunkt. +type pinDims struct{ w, h float64 } + +var baseSizes = map[string]pinDims{ + "pin-s": {20, 30}, + "pin-m": {27, 41}, // Maptiler-Standard + "pin-l": {34, 52}, } // Marker-Cache (max 500 Einträge, reicht für alle Farb/Typ/Scale-Kombinationen) @@ -151,30 +154,33 @@ func generateMarker(pinType, hexColor string, scale float64) ([]byte, error) { if !ok { bs = baseSizes["pin-m"] } - size := math.Round(bs * scale) - if size < 12 { - size = 12 + w := math.Round(bs.w * scale) + h := math.Round(bs.h * scale) + if w < 10 { + w = 10 + } + if h < 15 { + h = 15 } - // Geometrie: schlanker Google-Maps-Pin - // r/d ≈ 0.35 → Öffnungswinkel ~20° → Pin sieht spitz aus, nicht oval - cx := size / 2 - r := size * 0.24 // kleiner Kreis → langer, schlanker Schwanz - cy := r + size*0.02 // Kreismittelpunkt oben - tipY := size * 0.975 // Spitze am unteren Rand - borderW := math.Max(1.5, size*0.05) // Randbreite - innerR := r * 0.40 // weißer Innenpunkt (etwas größer für Sichtbarkeit) + // Geometrie: Kreis füllt ~74% der Breite, r/d ≈ 0.37 → Öffnungswinkel 22° + // Entspricht exakt Maptiler pin-m Proportionen (27×41px @1x) + cx := w / 2 + r := w * 0.37 // Kreisradius: Kreis fast so breit wie Canvas + cy := r + h*0.04 // Kreismittelpunkt Y: kleiner oberer Rand + tipY := h - h*0.03 // Spitze Y: 3% Abstand vom unteren Rand - // Transparenz: 80% opak (Alpha 204) + borderW := math.Max(2.0, w*0.08) // weißer Rand: gut sichtbar + innerR := r * 0.38 // weißer Innenpunkt: 38% des Kreisradius + + // Füllfarbe: 85% opak; Rand und Punkt: vollständig weiß fillBase := hexToColor(hexColor) - fill := color.RGBA{R: fillBase.R, G: fillBase.G, B: fillBase.B, A: 204} - white := color.RGBA{R: 255, G: 255, B: 255, A: 220} - whiteDot := color.RGBA{R: 255, G: 255, B: 255, A: 204} + fill := color.RGBA{R: fillBase.R, G: fillBase.G, B: fillBase.B, A: 217} + white := color.RGBA{R: 255, G: 255, B: 255, A: 255} - iSize := int(math.Ceil(size)) - img := image.NewRGBA(image.Rect(0, 0, iSize, iSize)) + img := image.NewRGBA(image.Rect(0, 0, int(math.Ceil(w)), int(math.Ceil(h)))) - // 1. Weißer Rand + // 1. Weißer Rand (Pin mit borderW größerem Radius) borderPts := pinOutline(cx, cy, r+borderW, tipY, 64) fillPolygon(img, borderPts, white) @@ -182,8 +188,8 @@ func generateMarker(pinType, hexColor string, scale float64) ([]byte, error) { fillPts := pinOutline(cx, cy, r, tipY, 64) fillPolygon(img, fillPts, fill) - // 3. Weißer Innenpunkt - drawCircle(img, cx, cy, innerR, whiteDot) + // 3. Weißer Innenpunkt – vollständig opak für gute Sichtbarkeit + drawCircle(img, cx, cy, innerR, white) var buf bytes.Buffer if err := png.Encode(&buf, img); err != nil {