Compare commits
No commits in common. "8feaa6bd1590b97fdccfd3da7a0418c6fd619934" and "9367ba5b6bbe398463103e32bb8cbc8717f5a261" have entirely different histories.
8feaa6bd15
...
9367ba5b6b
4 changed files with 32 additions and 92 deletions
|
|
@ -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/`
|
||||
|
||||
|
|
|
|||
|
|
@ -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 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
|
||||
Verifiziert mit blauen Test-Pin (±4285F4): sieht jetzt aus wie Google-Maps-Pin.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue