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)
- API Root: `http://localhost:9984`
- 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`
- 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
«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
«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
**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.
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{
"pin-s": {20, 30},
"pin-m": {27, 41}, // Maptiler-Standard
"pin-l": {34, 52},
"pin-s": {20, 50},
"pin-m": {30, 70}, // mapbox-lib.js: sizes.medium = [30, 70]
"pin-l": {35, 90},
}
// 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
}
// Geometrie: Kreis füllt ~74% der Breite, r/d ≈ 0.37 → Öffnungswinkel 22°
// Entspricht exakt Maptiler pin-m Proportionen (27×41px @1x)
// Geometrie für 30×70 (@1x) / 60×140 (@2x):
// - 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
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
r := w * 0.47 // r≈14px (@1x): Kreis fast so breit wie Canvas
cy := r + 1.0 // cy≈15: Kreis berührt fast den oberen Rand
tipY := h - 1.0 // Spitze 1px vom unteren Rand
borderW := math.Max(2.0, w*0.08) // weißer Rand: gut sichtbar
innerR := r * 0.38 // weißer Innenpunkt: 38% des Kreisradius
borderW := math.Max(1.5, w*0.06) // weißer Rand
innerR := r * 0.36 // weißer Innenpunkt
// Füllfarbe: 85% opak; Rand und Punkt: vollständig weiß
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("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("Content-Length", strconv.Itoa(len(data)))
w.WriteHeader(http.StatusOK)