Compare commits

..

No commits in common. "8feaa6bd1590b97fdccfd3da7a0418c6fd619934" and "9367ba5b6bbe398463103e32bb8cbc8717f5a261" have entirely different histories.

4 changed files with 32 additions and 92 deletions

View file

@ -237,8 +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×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
- Typen: `pin-s` (24px), `pin-m` (32px), `pin-l` (40px); Canvas quadratisch, 80% opak, schlanke Teardrop-Form (r/d≈0.35)
- 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,55 +113,9 @@ 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 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...»
### Antwort 13
Ursache identifiziert: `r/d = 0.59` (Kreisradius / Abstand zur Spitze) → Öffnungswinkel 36° → Tangenten laufen flach aus → Form sieht aus wie ein Oval.
### Antwort 16
mapbox-lib.js setzt `iconAnchor = [w/2, h/2]` = Bildmitte, nicht Spitze.
Fix: `r = size * 0.24` (statt 0.355) → `r/d ≈ 0.35` → Öffnungswinkel 20° → schlanker 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
Verifiziert mit blauen Test-Pin (±4285F4): sieht jetzt 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. 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.
**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.
---

View file

@ -21,18 +21,11 @@ var markerPattern = regexp.MustCompile(
`^/styles/v4/marker/([a-z-]+)\+([0-9a-fA-F]{6})(?:@(\d+(?:\.\d+)?x))?\.png$`,
)
// 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},
// Basis-Größen in logischen Pixeln (quadratische Canvas)
var baseSizes = map[string]float64{
"pin-s": 24,
"pin-m": 32,
"pin-l": 40,
}
// Marker-Cache (max 500 Einträge, reicht für alle Farb/Typ/Scale-Kombinationen)
@ -158,36 +151,30 @@ func generateMarker(pinType, hexColor string, scale float64) ([]byte, error) {
if !ok {
bs = baseSizes["pin-m"]
}
w := math.Round(bs.w * scale)
h := math.Round(bs.h * scale)
if w < 10 {
w = 10
}
if h < 15 {
h = 15
size := math.Round(bs * scale)
if size < 12 {
size = 12
}
// 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
// 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)
borderW := math.Max(1.5, w*0.06)
innerR := r * 0.36
// Füllfarbe: 85% opak; Rand und Punkt: vollständig weiß
// Transparenz: 80% opak (Alpha 204)
fillBase := hexToColor(hexColor)
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}
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}
img := image.NewRGBA(image.Rect(0, 0, int(math.Ceil(w)), int(math.Ceil(h))))
iSize := int(math.Ceil(size))
img := image.NewRGBA(image.Rect(0, 0, iSize, iSize))
// 1. Weißer Rand (Pin mit borderW größerem Radius)
// 1. Weißer Rand
borderPts := pinOutline(cx, cy, r+borderW, tipY, 64)
fillPolygon(img, borderPts, white)
@ -195,8 +182,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 vollständig opak für gute Sichtbarkeit
drawCircle(img, cx, cy, innerR, white)
// 3. Weißer Innenpunkt
drawCircle(img, cx, cy, innerR, whiteDot)
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
@ -241,7 +228,7 @@ func handleMarker(w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Cache-Control", "public, max-age=86400")
w.Header().Set("Cache-Control", "public, max-age=2592000, immutable")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
w.WriteHeader(http.StatusOK)