Compare commits
3 commits
9367ba5b6b
...
8feaa6bd15
| Author | SHA1 | Date | |
|---|---|---|---|
| 8feaa6bd15 | |||
| bedbe0f3f3 | |||
| 641312aba9 |
4 changed files with 92 additions and 32 deletions
|
|
@ -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)
|
- 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` (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`
|
- 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/`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -113,9 +113,55 @@ 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...»
|
||||||
|
|
||||||
### Antwort 13
|
### Prompt 16
|
||||||
Ursache identifiziert: `r/d = 0.59` (Kreisradius / Abstand zur Spitze) → Öffnungswinkel 36° → Tangenten laufen flach aus → Form sieht aus wie ein Oval.
|
«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 14773–14789):
|
||||||
|
|
||||||
|
```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
|
||||||
|
|
|
||||||
|
|
@ -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/*`). 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,18 @@ var markerPattern = regexp.MustCompile(
|
||||||
`^/styles/v4/marker/([a-z-]+)\+([0-9a-fA-F]{6})(?:@(\d+(?:\.\d+)?x))?\.png$`,
|
`^/styles/v4/marker/([a-z-]+)\+([0-9a-fA-F]{6})(?:@(\d+(?:\.\d+)?x))?\.png$`,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Basis-Größen in logischen Pixeln (quadratische Canvas)
|
// Basis-Abmessungen (Breite × Höhe) – entspricht Maptiler-Standard.
|
||||||
var baseSizes = map[string]float64{
|
// Der Pin ankert an der Spitze (bottom-center), nicht am Bildmittelpunkt.
|
||||||
"pin-s": 24,
|
type pinDims struct{ w, h float64 }
|
||||||
"pin-m": 32,
|
|
||||||
"pin-l": 40,
|
// 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)
|
// 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 {
|
if !ok {
|
||||||
bs = baseSizes["pin-m"]
|
bs = baseSizes["pin-m"]
|
||||||
}
|
}
|
||||||
size := math.Round(bs * scale)
|
w := math.Round(bs.w * scale)
|
||||||
if size < 12 {
|
h := math.Round(bs.h * scale)
|
||||||
size = 12
|
if w < 10 {
|
||||||
|
w = 10
|
||||||
|
}
|
||||||
|
if h < 15 {
|
||||||
|
h = 15
|
||||||
}
|
}
|
||||||
|
|
||||||
// Geometrie: schlanker Google-Maps-Pin
|
// mapbox-lib.js setzt: iconAnchor = [w/2, h/2]
|
||||||
// r/d ≈ 0.35 → Öffnungswinkel ~20° → Pin sieht spitz aus, nicht oval
|
// → geografische Koordinate liegt bei Pixel (w/2, h/2) = Bildmitte
|
||||||
cx := size / 2
|
// → Spitze MUSS bei tipY = h/2 liegen, untere Hälfte bleibt transparent
|
||||||
r := size * 0.24 // kleiner Kreis → langer, schlanker Schwanz
|
//
|
||||||
cy := r + size*0.02 // Kreismittelpunkt oben
|
// r muss entsprechend kleiner sein: r = w*0.33 → r/d = 10/(35-11) ≈ 0.41 → 24° Öffnung
|
||||||
tipY := size * 0.975 // Spitze am unteren Rand
|
cx := w / 2
|
||||||
borderW := math.Max(1.5, size*0.05) // Randbreite
|
r := w * 0.33 // schlanker Kreis; r/d ≈ 0.41 @ tipY=h/2
|
||||||
innerR := r * 0.40 // weißer Innenpunkt (etwas größer für Sichtbarkeit)
|
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)
|
fillBase := hexToColor(hexColor)
|
||||||
fill := color.RGBA{R: fillBase.R, G: fillBase.G, B: fillBase.B, 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: 220}
|
white := color.RGBA{R: 255, G: 255, B: 255, A: 255}
|
||||||
whiteDot := color.RGBA{R: 255, G: 255, B: 255, A: 204}
|
|
||||||
|
|
||||||
iSize := int(math.Ceil(size))
|
img := image.NewRGBA(image.Rect(0, 0, int(math.Ceil(w)), int(math.Ceil(h))))
|
||||||
img := image.NewRGBA(image.Rect(0, 0, iSize, iSize))
|
|
||||||
|
|
||||||
// 1. Weißer Rand
|
// 1. Weißer Rand (Pin mit borderW größerem Radius)
|
||||||
borderPts := pinOutline(cx, cy, r+borderW, tipY, 64)
|
borderPts := pinOutline(cx, cy, r+borderW, tipY, 64)
|
||||||
fillPolygon(img, borderPts, white)
|
fillPolygon(img, borderPts, white)
|
||||||
|
|
||||||
|
|
@ -182,8 +195,8 @@ func generateMarker(pinType, hexColor string, scale float64) ([]byte, error) {
|
||||||
fillPts := pinOutline(cx, cy, r, tipY, 64)
|
fillPts := pinOutline(cx, cy, r, tipY, 64)
|
||||||
fillPolygon(img, fillPts, fill)
|
fillPolygon(img, fillPts, fill)
|
||||||
|
|
||||||
// 3. Weißer Innenpunkt
|
// 3. Weißer Innenpunkt – vollständig opak für gute Sichtbarkeit
|
||||||
drawCircle(img, cx, cy, innerR, whiteDot)
|
drawCircle(img, cx, cy, innerR, white)
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := png.Encode(&buf, img); err != nil {
|
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("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)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue