| Server IP : 202.61.199.114 / Your IP : 216.73.217.139 Web Server : nginx/1.22.1 System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64 User : web20 ( 1018) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /tmp/ |
Upload File : |
# -*- coding: utf-8 -*-
import json, csv, math, unicodedata, io
def norm(s):
s=unicodedata.normalize('NFKD',s or '').encode('ascii','ignore').decode('ascii')
return s.lower().replace(' ','').replace('-','').replace('.','')
# ---------- 1) Kennzeichen-Lookup (DE/AT/CH), DE-Priorität bei Kollision ----------
def read_csv(path):
with open(path,encoding='utf-8') as f:
return list(csv.DictReader(f))
de=read_csv('/tmp/kfz_de.csv'); at=read_csv('/tmp/kfz_at.csv'); ch=read_csv('/tmp/kfz_ch.csv')
# Name->ISO je Land (für Karten-Highlight-Matching)
de_name2iso={};
for r in de:
de_name2iso[norm(r['Bundesland.Name'])]=r['Bundesland.Iso3166-2']
at_name2iso={}
for r in at:
at_name2iso[norm(r['Bundesland.Name'])]=r['Bundesland.Iso3166-2']
ch_name2iso={}
for r in ch:
iso=r['Kanton.Iso3166-2']
for key in ('Kanton','Kanton.FR','Kanton.IT','Kanton.RM'):
v=r.get(key)
if v: ch_name2iso[norm(v)]=iso
ch_name2iso['stgallen']='CH-SG' # GeoJSON-Schreibweise "St. Gallen"
import re
def titlecase(s):
return re.sub(r'[A-Za-zÄÖÜäöüß]+', lambda m: m.group(0)[:1].upper()+m.group(0)[1:].lower(), s)
def derive_ort(sk,h):
"""Ort aus der Herleitung gewinnen (z. B. AA 'AAlen' -> Aalen), sofern es ein Ort ist und vom Kreis abweicht."""
h=(h or '').strip()
if not h: return None
t=titlecase(h)
t=re.sub(r'\s*\([^)]*\)\s*$','',t).strip() # nachgestellte Klammer-Hinweise weg, z. B. "(bis 2007)"
if not t: return None
if 'kreis' in t.lower(): return None # Herleitung ist selbst ein (Land)kreis -> kein Ort
if t==sk.strip(): return None
return t
de_codes={}
for r in de:
code=r['Unterscheidungszeichen'].strip().upper()
if not code: continue
sk=r['StadtOderKreis'].strip()
e={'c':'DE','r':sk,'s':r['Bundesland.Name'],'iso':r['Bundesland.Iso3166-2']}
o=derive_ort(sk, r['Herleitung'])
if o: e['o']=o
de_codes[code]=e
at_codes={}
for r in at:
code=r['Unterscheidungszeichen'].strip().upper()
if not code: continue
at_codes[code]={'c':'AT','r':r['Zulassungsbezirk'].strip(),'s':r['Bundesland.Name'],'iso':r['Bundesland.Iso3166-2']}
ch_codes={}
for r in ch:
code=r['Unterscheidungszeichen'].strip().upper()
if not code: continue
ch_codes[code]={'c':'CH','r':r['Kanton'],'s':r['Kanton'],'iso':r['Kanton.Iso3166-2']}
codes={}
for code in (set(de_codes)|set(at_codes)|set(ch_codes)):
present=[x for x in (de_codes.get(code),at_codes.get(code),ch_codes.get(code)) if x] # Priorität DE>AT>CH
e=dict(present[0])
alts=[{'c':x['c'],'r':x.get('o') or x['r'],'s':x['s']} for x in present[1:]]
if alts: e['alts']=alts
codes[code]=e
def php_str(s): return "'"+str(s).replace('\\','\\\\').replace("'","\\'")+"'"
def php_entry(e):
p=["'c'=>"+php_str(e['c']),"'r'=>"+php_str(e['r']),"'s'=>"+php_str(e['s']),"'iso'=>"+php_str(e['iso'])]
if e.get('o'): p.append("'o'=>"+php_str(e['o']))
if e.get('alts'):
a=",".join("array('c'=>%s,'r'=>%s,'s'=>%s)"%(php_str(x['c']),php_str(x['r']),php_str(x['s'])) for x in e['alts'])
p.append("'alts'=>array("+a+")")
return "array("+",".join(p)+")"
with open('/var/www/seaside.pacim.de/web/includes/data/plate-codes.php','w',encoding='utf-8') as f:
f.write("<?php\n/* Generiert aus openpotato/kfz-kennzeichen (DE/AT/CH). Offline-Lookup, DE-Priorität bei Kollision.\n r=Ort/Kreis, o=Ort (aus Herleitung, wenn vom Kreis abweichend), alts=auch-in-anderem-Land. */\n")
f.write("$PLATE_CODES = array(\n")
for code in sorted(codes):
f.write(" %s=>%s,\n"%(php_str(code),php_entry(codes[code])))
f.write(");\n")
ort_n=sum(1 for e in codes.values() if e.get('o')); alt_n=sum(1 for e in codes.values() if e.get('alts'))
print('codes:',len(codes),' mit Ort:',ort_n,' mit alts:',alt_n)
# ---------- 2) Karten (SVG-Pfade + Zentroide je Region) ----------
def rings_of(geom):
t=geom['type']; cs=geom['coordinates']
if t=='Polygon': return [cs[0]]+[] # nur Außenringe relevant für Form
if t=='MultiPolygon': return [poly[0] for poly in cs]
return []
def ring_area(ring):
a=0.0
for i in range(len(ring)-1):
x1,y1=ring[i]; x2,y2=ring[i+1]; a+=x1*y2-x2*y1
return a/2.0
def ring_centroid(ring):
a=0.0; cx=0.0; cy=0.0
for i in range(len(ring)-1):
x1,y1=ring[i]; x2,y2=ring[i+1]; cr=x1*y2-x2*y1; a+=cr; cx+=(x1+x2)*cr; cy+=(y1+y2)*cr
if a==0:
xs=[p[0] for p in ring]; ys=[p[1] for p in ring]; return (sum(xs)/len(xs),sum(ys)/len(ys))
a*=0.5; return (cx/(6*a),cy/(6*a))
def dp(points,eps):
if len(points)<3: return points
dmax=0;idx=0; a=points[0]; b=points[-1]
for i in range(1,len(points)-1):
d=perp(points[i],a,b)
if d>dmax: dmax=d; idx=i
if dmax>eps:
l=dp(points[:idx+1],eps); r=dp(points[idx:],eps); return l[:-1]+r
return [a,b]
def perp(p,a,b):
if a==b: return math.hypot(p[0]-a[0],p[1]-a[1])
dx=b[0]-a[0]; dy=b[1]-a[1]; t=((p[0]-a[0])*dx+(p[1]-a[1])*dy)/(dx*dx+dy*dy)
px=a[0]+t*dx; py=a[1]+t*dy; return math.hypot(p[0]-px,p[1]-py)
def build_country(geojson_path, name2iso, size=560, pad=8, eps=1.1, min_area_frac=0.0008):
d=json.load(open(geojson_path,encoding='utf-8'))
feats=d['features']
# alle Lon/Lat sammeln für bbox + latMid
allc=[]
for ft in feats:
for ring in rings_of(ft['geometry']): allc+=ring
lons=[c[0] for c in allc]; lats=[c[1] for c in allc]
latmid=(min(lats)+max(lats))/2.0; k=math.cos(math.radians(latmid))
def pr(c): return (c[0]*k, -c[1])
pall=[pr(c) for c in allc]
xs=[p[0] for p in pall]; ys=[p[1] for p in pall]
minx,maxx,miny,maxy=min(xs),max(xs),min(ys),max(ys)
w=maxx-minx; h=maxy-miny; scale=(size-2*pad)/max(w,h)
W=round(w*scale+2*pad); H=round(h*scale+2*pad)
def tf(c):
px,py=pr(c); return (round((px-minx)*scale+pad,1), round((py-miny)*scale+pad,1))
total_area=0.0
regions={}
for ft in feats:
nm=ft['properties'].get('name') or ft['properties'].get('NAME') or ''
iso=name2iso.get(norm(nm))
if not iso:
# AT ginseng: nutze 'iso' Nummer
isn=ft['properties'].get('iso')
if isn is not None: iso='AT-'+str(isn)
if not iso:
print(' ! kein iso für', nm); continue
rings=rings_of(ft['geometry'])
# größter Ring -> Zentroid
pr_rings=[[tf(c) for c in ring] for ring in rings]
areas=[abs(ring_area(r)) for r in pr_rings]
amax=max(areas) if areas else 0
keep=[r for r,a in zip(pr_rings,areas) if a>=amax*0.02 or a>amax*0] # kleine Inseln raus
keep=[r for r,a in zip(pr_rings,areas) if a>=amax*0.03] or pr_rings[:1]
# Pfad
dparts=[]
for r in keep:
s=dp(r,eps)
if len(s)<3: continue
dparts.append('M'+' '.join('%g,%g'%(p[0],p[1]) for p in s)+'Z')
big=pr_rings[areas.index(amax)]
cx,cy=ring_centroid(big); cx=round(cx,1); cy=round(cy,1)
regions[iso]={'d':''.join(dparts),'cx':cx,'cy':cy}
return {'w':W,'h':H,'regions':regions}
DE=build_country('/tmp/geo_germany.json',de_name2iso,eps=1.1)
CH=build_country('/tmp/geo_switzerland.json',ch_name2iso,eps=0.7)
AT=build_country('/tmp/geo_austria.json',at_name2iso,eps=0.7)
print('DE regions',len(DE['regions']),'CH',len(CH['regions']),'AT',len(AT['regions']))
def emit(geo):
out="array('w'=>%d,'h'=>%d,'regions'=>array(\n"%(geo['w'],geo['h'])
for iso in sorted(geo['regions']):
r=geo['regions'][iso]
out+=" %s=>array('d'=>%s,'cx'=>%g,'cy'=>%g),\n"%(php_str(iso),php_str(r['d']),r['cx'],r['cy'])
out+=" ))"
return out
with open('/var/www/seaside.pacim.de/web/includes/data/plate-geo.php','w',encoding='utf-8') as f:
f.write("<?php\n/* Vereinfachte Landeskarten (Bundeslaender/Kantone) als SVG-Pfade + Zentroide. Generiert offline. */\n")
f.write("$PLATE_GEO = array(\n")
f.write(" 'DE'=>"+emit(DE)+",\n")
f.write(" 'AT'=>"+emit(AT)+",\n")
f.write(" 'CH'=>"+emit(CH)+",\n")
f.write(");\n")
import os
print('codes file', os.path.getsize('/var/www/seaside.pacim.de/web/includes/data/plate-codes.php'))
print('geo file', os.path.getsize('/var/www/seaside.pacim.de/web/includes/data/plate-geo.php'))