fix: pin dimensions match Kartographer mapbox-lib.js exactly

Kartographer/mapbox-lib.js defines:
  sizes = { small:[20,50], medium:[30,70], large:[35,90] }
  iconSize: sizes[size]  <- CSS stretches image to this

Previous: pin-m=27x41 -> CSS stretched to 30x70 (+11% h, +71% v)
Result: slim tip distorted into fat oval shape

Fix: generate images at exact expected dimensions:
  pin-s: 20x50, pin-m: 30x70, pin-l: 35x90 (@1x)
  pin-m@2x: 60x140

Geometry: r=w*0.47 (circle fills width), cy=r+1, tipY=h-1
This commit is contained in:
Conrad Schulz 2026-07-01 13:48:39 +00:00
parent 641312aba9
commit bedbe0f3f3
4 changed files with 41 additions and 13 deletions

View file

@ -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) - Image: `192.168.178.6:8083/cschulz/rd13_tile_server-marker-api:latest` (Forgejo Registry)
- API Root: `http://localhost:9984` - API Root: `http://localhost:9984`
- Endpunkt: `/styles/v4/marker/{type}+{hexcolor}(@{scale}x).png` - Endpunkt: `/styles/v4/marker/{type}+{hexcolor}(@{scale}x).png`
- Typen: `pin-s` (20×30px), `pin-m` (27×41px, Maptiler-Standard), `pin-l` (34×52px); 85% opak, weißer Rand + Innenpunkt - Typen: `pin-s` (20×50px), `pin-m` (30×70px), `pin-l` (35×90px) @1x exakte Kartographer-Maße (mapbox-lib.js `sizes`); weißer Rand + Innenpunkt, 85% opak
- Image neu bauen: `docker compose build marker-api && docker compose up -d marker-api` - 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/` - CI: Forgejo baut + pusht automatisch bei Änderungen in `services/marker-api/`

View file

@ -113,6 +113,28 @@ Geometry-Fix: r=35.5% der Breite, tipY=97.5%, Spitze passt jetzt sauber in die C
### Prompt 13 ### Prompt 13
«Das sieht aber immer noch komisch aus...» «Das sieht aber immer noch komisch aus...»
### Prompt 15
«Ich habe immer zwischen den tests den gesamten browser cache geleert das kann es nicht sein!»
### Antwort 15
Analyse der Kartographer-Quelldatei `/wikidata/extensions/Kartographer/lib/external/mapbox/mapbox-lib.js` (Zeilen 1477314789):
```js
var sizes = { small: [20,50], medium: [30,70], large: [35,90] };
iconSize: sizes[size], // CSS-Zielgröße
iconAnchor: [sizes[size][0]/2, sizes[size][1]/2] // Anker = MITTE
```
Zwei Ursachen:
1. Größen-Mismatch: Kartographer erwartet pin-m = 30×70px. Wir lieferten 27×41px. CSS-Stretch: +11% horizontal / +71% vertikal → schlanke Spitze wird zu dickem Oval.
2. iconAnchor an der Mitte [15,35]: Funktioniert nur wenn Canvas wirklich 70px hoch ist.
Fix: baseSizes auf exakte Kartographer-Maße gesetzt:
- pin-s: 20×50, pin-m: 30×70, pin-l: 35×90
- Geometrie angepasst: r = w*0.47 (Kreis füllt Breite), cy=r+1, tipY=h-1
Verifiziert: pin-m@1x=30×70px, pin-m@2x=60×140px. Pin mit langer Spitze korrekt.
### Prompt 14 ### 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» «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»

View file

@ -1,6 +1,6 @@
# PROJECT_CONTEXT rd13_tile_server # PROJECT_CONTEXT rd13_tile_server
**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. **Letzte Aktualisierung:** 2026-07-01 **Marker-API**: Go-Service für Maptiler-kompatible Marker-Endpunkte (`/styles/v4/marker/*`). Maße exakt nach Kartographer mapbox-lib.js: pin-m=30×70px (@1x)/60×140px (@2x). Weißer Rand + Innenpunkt (A=255). Ursache der Verzerrung: Größen-Mismatch (27×41→30×70 CSS-Stretch). Forgejo CI baut + pusht Image.
--- ---

View file

@ -25,10 +25,14 @@ var markerPattern = regexp.MustCompile(
// Der Pin ankert an der Spitze (bottom-center), nicht am Bildmittelpunkt. // Der Pin ankert an der Spitze (bottom-center), nicht am Bildmittelpunkt.
type pinDims struct{ w, h float64 } type pinDims struct{ w, h float64 }
// Maße MÜSSEN exakt den Werten in Kartographer's mapbox-lib.js entsprechen:
// sizes = { small: [20,50], medium: [30,70], large: [35,90] }
// Leaflet rendert das Bild per CSS auf genau diese Pixelgröße.
// Abweichungen → verzerrte Form (27×41 → 30×70 streckt Spitze um 71% vertikal).
var baseSizes = map[string]pinDims{ var baseSizes = map[string]pinDims{
"pin-s": {20, 30}, "pin-s": {20, 50},
"pin-m": {27, 41}, // Maptiler-Standard "pin-m": {30, 70}, // mapbox-lib.js: sizes.medium = [30, 70]
"pin-l": {34, 52}, "pin-l": {35, 90},
} }
// Marker-Cache (max 500 Einträge, reicht für alle Farb/Typ/Scale-Kombinationen) // Marker-Cache (max 500 Einträge, reicht für alle Farb/Typ/Scale-Kombinationen)
@ -163,15 +167,17 @@ func generateMarker(pinType, hexColor string, scale float64) ([]byte, error) {
h = 15 h = 15
} }
// Geometrie: Kreis füllt ~74% der Breite, r/d ≈ 0.37 → Öffnungswinkel 22° // Geometrie für 30×70 (@1x) / 60×140 (@2x):
// Entspricht exakt Maptiler pin-m Proportionen (27×41px @1x) // - Kreis füllt fast die ganze Breite (r≈47% width)
// - iconAnchor=[w/2, h/2] in mapbox-lib.js → Anker liegt bei y=35 im Tail
// - Spitze bei y≈h-1: 34px unterhalb des Ankers (deutlich sichtbar)
cx := w / 2 cx := w / 2
r := w * 0.37 // Kreisradius: Kreis fast so breit wie Canvas r := w * 0.47 // r≈14px (@1x): Kreis fast so breit wie Canvas
cy := r + h*0.04 // Kreismittelpunkt Y: kleiner oberer Rand cy := r + 1.0 // cy≈15: Kreis berührt fast den oberen Rand
tipY := h - h*0.03 // Spitze Y: 3% Abstand vom unteren Rand tipY := h - 1.0 // Spitze 1px vom unteren Rand
borderW := math.Max(2.0, w*0.08) // weißer Rand: gut sichtbar borderW := math.Max(1.5, w*0.06) // weißer Rand
innerR := r * 0.38 // weißer Innenpunkt: 38% des Kreisradius innerR := r * 0.36 // weißer Innenpunkt
// Füllfarbe: 85% opak; Rand und Punkt: vollständig weiß // Füllfarbe: 85% opak; Rand und Punkt: vollständig weiß
fillBase := hexToColor(hexColor) fillBase := hexToColor(hexColor)
@ -234,7 +240,7 @@ func handleMarker(w http.ResponseWriter, r *http.Request) {
} }
w.Header().Set("Content-Type", "image/png") w.Header().Set("Content-Type", "image/png")
w.Header().Set("Cache-Control", "public, max-age=2592000, immutable") w.Header().Set("Cache-Control", "public, max-age=86400")
w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Length", strconv.Itoa(len(data))) w.Header().Set("Content-Length", strconv.Itoa(len(data)))
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)