Compare commits

...

3 commits

Author SHA1 Message Date
8feaa6bd15 fix: pin tip at iconAnchor position (h/2, not h-1)
Some checks failed
Build & Push marker-api / build (push) Failing after 5s
mapbox-lib.js: iconAnchor = [w/2, h/2] (image center)
Previous: tipY=h-1 -> anchor at y=35, tip at y=69 -> pin floated above click
Fix: tipY=h/2 -> anchor and tip coincide exactly
Reduce r=w*0.33 to keep slim shape (r/d=0.41 -> 24deg opening)
Bottom canvas half is transparent
2026-07-01 13:55:52 +00:00
bedbe0f3f3 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
2026-07-01 13:48:39 +00:00
641312aba9 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)
2026-07-01 12:33:05 +00:00
4 changed files with 92 additions and 32 deletions

View file

@ -237,7 +237,8 @@ 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×50px), `pin-m` (30×70px), `pin-l` (35×90px) @1x exakte Kartographer-Maße (mapbox-lib.js `sizes`); weißer Rand + Innenpunkt, 85% opak
- Ankerpunkt: Nadelspitze liegt bei Pixel (w/2, h/2) = `iconAnchor` in mapbox-lib.js → geografische Koordinate zeigt exakt auf Spitze
- 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,9 +113,55 @@ 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 16
«Besser aber nicht perfekt. und gibt es eine möglichkeit das der angewählte punkt auch die spitze der nadel ist? gerade fühlt es sich so an als wäre die mitte des markers der fixierte punkt...»
Fix: `r = size * 0.24` (statt 0.355) → `r/d ≈ 0.35` → Öffnungswinkel 20° → schlanker Pin.
### Antwort 16
mapbox-lib.js setzt `iconAnchor = [w/2, h/2]` = Bildmitte, nicht Spitze.
Verifiziert mit blauen Test-Pin (±4285F4): sieht jetzt aus wie Google-Maps-Pin.
Fix: Spitze bei `tipY = h/2` gezeichnet, untere Canvas-Hälfte bleibt transparent.
Damit fällt `iconAnchor=[15,35]` exakt auf die Nadelspitze.
Geometrie: r = w*0.33 (schlanker Kreis), cy = r+1, tipY = h/2
→ r/d = 10/24 ≈ 0.41 → 24° Öffnungswinkel (Google-Maps-ähnlich)
Verifiziert: pin-m@2x = 60×140px, Spitze bei y=70 = Ankerpunkt.
### 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»
### Antwort 14
Ursache der Verzerrung identifiziert: Quadratische Canvas (32×32) erzwingt falsche Proportionen. Ein Teardrop-Pin kann in einem Quadrat nicht korrekt aussehen.
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

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/*`). 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. Maße nach Kartographer mapbox-lib.js (pin-m=30×70/@1x). Spitze bei tipY=h/2 damit iconAnchor=[w/2,h/2] exakt auf Nadelspitze zeigt. Untere Canvas-Hälfte transparent. r=w*0.33 → r/d=0.41 → 24°. Weißer Rand+Punkt. Forgejo CI.
---

View file

@ -21,11 +21,18 @@ 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 }
// 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, 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)
@ -151,30 +158,36 @@ 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)
// mapbox-lib.js setzt: iconAnchor = [w/2, h/2]
// → geografische Koordinate liegt bei Pixel (w/2, h/2) = Bildmitte
// → Spitze MUSS bei tipY = h/2 liegen, untere Hälfte bleibt transparent
//
// r muss entsprechend kleiner sein: r = w*0.33 → r/d = 10/(35-11) ≈ 0.41 → 24° Öffnung
cx := w / 2
r := w * 0.33 // schlanker Kreis; r/d ≈ 0.41 @ tipY=h/2
cy := r + 1.0 // Kreis nahe Oberkante
tipY := h / 2 // Spitze an Ankerpunkt [w/2, h/2] → zeigt exakt auf Koordinate
// Transparenz: 80% opak (Alpha 204)
borderW := math.Max(1.5, w*0.06)
innerR := r * 0.36
// 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 +195,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 {
@ -228,7 +241,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)