Add clickable SVG links
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
s3lph 2022-10-11 02:29:45 +02:00
parent 7c954242eb
commit 2610a54fe1
Signed by: s3lph
GPG key ID: 8AC98A811E5BEFF5
4 changed files with 89 additions and 21 deletions

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
{"Erlangen": [11.0028028, 49.6000372], "Paderborn": [8.7479251, 51.7171873], "Hamburg": [9.9443486, 53.5583644], "Aachen": [6.091774700480718, 50.7715109], "Basel": [7.6342977, 47.5323068], "Berlin": [13.38283, 52.5217046], "Bremen": [8.7879452, 53.0864586], "K\u00f6ln": [6.912896, 50.9505492], "Darmstadt": [8.6511275, 49.8708409], "Dresden": [13.7288095, 51.0811269], "D\u00fcsseldorf": [6.7996122, 51.2125738], "Frankfurt am Main": [8.6362035, 50.1241797], "Freiburg": [7.8405746483681735, 47.99297755], "G\u00f6ttingen": [9.9446185, 51.5453725], "Hannover": [9.717936908887404, 52.38812135], "Karlsruhe": [8.4073787, 49.0066019], "Mannheim": [8.4886818, 49.4636517], "M\u00fcnchen": [11.5607949, 48.153629], "Salzburg": [13.0561199, 47.7939873], "Stuttgart": [9.1800132, 48.7784485], "Ulm": [9.9910884, 48.4005863], "Wien": [16.35611792351422, 48.209451099999995], "Wiesbaden": [8.2295012, 50.0831784], "Z\u00fcrich": [8.5203159, 47.3869751], "Siegen": [8.0044503, 50.8689203], "Kaiserslautern": [7.762277299724986, 49.44049735], "Essen": [7.024639594585695, 51.43860565], "Dortmund": [7.464966783180763, 51.52768425], "Fulda": [9.6775152, 50.5588931], "W\u00fcrzburg": [9.923678752181619, 49.80223915], "Bamberg": [10.89268892402827, 49.90189135], "Kassel": [9.484939, 51.3183203]} {"Erlangen": {"location": [11.0028028, 49.6000372], "web": "https://erlangen.ccc.de/", "name": "Bits'n'Bugs"}, "Paderborn": {"location": [8.7479251, 51.7171873], "web": "https://c3pb.de/", "name": "Chaos Computer Club Paderborn"}, "Hamburg": {"location": [9.9443486, 53.5583644], "web": "https://hamburg.ccc.de/", "name": "Chaos Computer Club Hamburg"}, "Aachen": {"location": [6.091774700480718, 50.7715109], "web": "https://aachen.ccc.de/", "name": "Chaos Computer Club Aachen"}, "Basel": {"location": [7.6342977, 47.5323068], "web": "https://www.ccc-basel.ch/", "name": "Chaos Computer Club Basel"}, "Berlin": {"location": [13.38283, 52.5217046], "web": "https://berlin.ccc.de/", "name": "Chaos Computer Club Berlin"}, "Bremen": {"location": [8.7879452, 53.0864586], "web": "https://ccchb.de/", "name": "Chaos Computer Club Bremen"}, "K\u00f6ln": {"location": [6.912896, 50.9505492], "web": "https://koeln.ccc.de/", "name": "Chaos Computer Club Cologne"}, "Darmstadt": {"location": [8.6511275, 49.8708409], "web": "https://www.chaos-darmstadt.de/", "name": "Chaos Computer Club Darmstadt"}, "Dresden": {"location": [13.7288095, 51.0811269], "web": "https://c3d2.de/", "name": "Chaos Computer Club Dresden"}, "D\u00fcsseldorf": {"location": [6.7996122, 51.2125738], "web": "https://chaosdorf.de/", "name": "Chaos Computer Club D\u00fcsseldorf"}, "Frankfurt am Main": {"location": [8.6362035, 50.1241797], "web": "https://ccc-ffm.de/", "name": "Chaos Computer Club Frankfurt"}, "Freiburg": {"location": [7.8405746483681735, 47.99297755], "web": "https://cccfr.de/", "name": "Chaos Computer Club Freiburg"}, "G\u00f6ttingen": {"location": [9.9446185, 51.5453725], "web": "https://cccgoe.de/", "name": "Chaos Computer Club G\u00f6ttingen"}, "Hannover": {"location": [9.717936908887404, 52.38812135], "web": "https://hannover.ccc.de/", "name": "Chaos Computer Club Hannover"}, "Karlsruhe": {"location": [8.4073787, 49.0066019], "web": "https://entropia.de/", "name": "Chaos Computer Club Karlsruhe"}, "Mannheim": {"location": [8.4886818, 49.4636517], "web": "https://www.ccc-mannheim.de/", "name": "Chaos Computer Club Mannheim"}, "M\u00fcnchen": {"location": [11.5607949, 48.153629], "web": "https://www.muc.ccc.de/", "name": "Chaos Computer Club M\u00fcnchen"}, "Salzburg": {"location": [13.0561199, 47.7939873], "web": "https://cccsbg.at", "name": "Chaos Computer Club Salzburg"}, "Stuttgart": {"location": [9.1800132, 48.7784485], "web": "https://cccs.de/", "name": "Chaos Computer Club Stuttgart"}, "Ulm": {"location": [9.9910884, 48.4005863], "web": "https://ulm.ccc.de/", "name": "Chaos Computer Club Ulm"}, "Wien": {"location": [16.35611792351422, 48.209451099999995], "web": "https://c3w.at/", "name": "Chaos Computer Club Wien"}, "Wiesbaden": {"location": [8.2295012, 50.0831784], "web": "https://cccwi.de/", "name": "Chaos Computer Club Wiesbaden"}, "Z\u00fcrich": {"location": [8.5203159, 47.3869751], "web": "https://www.ccczh.ch/", "name": "Chaos Computer Club Z\u00fcrich"}, "Siegen": {"location": [8.0044503, 50.8689203], "web": "https://chaos-siegen.de/", "name": "Chaos Computer Club Siegen"}, "Kaiserslautern": {"location": [7.762277299724986, 49.44049735], "web": "http://www.chaos-inkl.de", "name": "Chaos inKL."}, "Essen": {"location": [7.024639594585695, 51.43860565], "web": "https://chaospott.de/"}, "Dortmund": {"location": [7.464966783180763, 51.52768425], "web": "https://www.chaostreff-dortmund.de/", "name": "Chaostreff Dortmund"}, "L\u00fcbeck": {"location": [10.6713232, 53.8687751], "web": "https://chaotikum.org/", "name": "Chaotikum"}, "Fulda": {"location": [9.6775152, 50.5588931], "web": "https://maglab.space/", "name": "Magrathea Laboratoratories"}, "W\u00fcrzburg": {"location": [9.923678752181619, 49.80223915], "web": "https://nerd2nerd.org/", "name": "Nerd2Nerd"}, "Bamberg": {"location": [10.89268892402827, 49.90189135], "web": "https://www.hackerspace-bamberg.de/", "name": "backspace"}, "Kassel": {"location": [9.484939, 51.3183203], "web": "https://flipdot.org/", "name": "flipdot"}}

View file

@ -37,9 +37,9 @@ class CachePaths:
self.chaostreff_info = os.path.join(path, 'chaostreff-info.json') self.chaostreff_info = os.path.join(path, 'chaostreff-info.json')
ERFA_URL = 'https://doku.ccc.de/Spezial:Semantische_Suche/format%3Djson/limit%3D50/link%3Dall/headers%3Dshow/searchlabel%3DJSON/class%3Dsortable-20wikitable-20smwtable/sort%3D/order%3Dasc/offset%3D0/-5B-5BKategorie:Erfa-2DKreise-5D-5D-20-5B-5BChaostreff-2DActive::wahr-5D-5D/-3FChaostreff-2DCity/-3FChaostreff-2DPhysical-2DAddress/-3FChaostreff-2DPhysical-2DHousenumber/-3FChaostreff-2DPhysical-2DPostcode/-3FChaostreff-2DPhysical-2DCity/-3FChaostreff-2DCountry/mainlabel%3D/prettyprint%3Dtrue/unescape%3Dtrue' ERFA_URL = 'https://doku.ccc.de/Spezial:Semantische_Suche/format%3Djson/limit%3D50/link%3Dall/headers%3Dshow/searchlabel%3DJSON/class%3Dsortable-20wikitable-20smwtable/sort%3D/order%3Dasc/offset%3D0/-5B-5BKategorie:Erfa-2DKreise-5D-5D-20-5B-5BChaostreff-2DActive::wahr-5D-5D/-3FChaostreff-2DCity/-3FChaostreff-2DPhysical-2DAddress/-3FChaostreff-2DPhysical-2DHousenumber/-3FChaostreff-2DPhysical-2DPostcode/-3FChaostreff-2DPhysical-2DCity/-3FChaostreff-2DCountry/-3FPublic-2DWeb/-3FChaostreff-2DLongname/mainlabel%3D/prettyprint%3Dtrue/unescape%3Dtrue'
CHAOSTREFF_URL = 'https://doku.ccc.de/Spezial:Semantische_Suche/format%3Djson/limit%3D50/link%3Dall/headers%3Dshow/searchlabel%3DJSON/class%3Dsortable-20wikitable-20smwtable/sort%3D/order%3Dasc/offset%3D0/-5B-5BKategorie:Chaostreffs-5D-5D-20-5B-5BChaostreff-2DActive::wahr-5D-5D/-3FChaostreff-2DCity/-3FChaostreff-2DPhysical-2DAddress/-3FChaostreff-2DPhysical-2DHousenumber/-3FChaostreff-2DPhysical-2DPostcode/-3FChaostreff-2DPhysical-2DCity/-3FChaostreff-2DCountry/mainlabel%3D/prettyprint%3Dtrue/unescape%3Dtrue' CHAOSTREFF_URL = 'https://doku.ccc.de/Spezial:Semantische_Suche/format%3Djson/limit%3D50/link%3Dall/headers%3Dshow/searchlabel%3DJSON/class%3Dsortable-20wikitable-20smwtable/sort%3D/order%3Dasc/offset%3D0/-5B-5BKategorie:Chaostreffs-5D-5D-20-5B-5BChaostreff-2DActive::wahr-5D-5D/-3FChaostreff-2DCity/-3FChaostreff-2DPhysical-2DAddress/-3FChaostreff-2DPhysical-2DHousenumber/-3FChaostreff-2DPhysical-2DPostcode/-3FChaostreff-2DPhysical-2DCity/-3FChaostreff-2DCountry/-3FPublic-2DWeb/-3FChaostreff-2DLongname/mainlabel%3D/prettyprint%3Dtrue/unescape%3Dtrue'
def sparql_query(query): def sparql_query(query):
@ -205,7 +205,12 @@ def fetch_erfas(target, url):
if location is None: if location is None:
print(f'WARNING: No location for {name}') print(f'WARNING: No location for {name}')
city = erfa['printouts']['Chaostreff-City'][0] city = erfa['printouts']['Chaostreff-City'][0]
erfas[city] = location erfas[city] = {'location': location}
if len(erfa['printouts']['Public-Web']) > 0:
erfas[city]['web'] = erfa['printouts']['Public-Web'][0]
if len(erfa['printouts']['Chaostreff-Longname']) > 0:
erfas[city]['name'] = erfa['printouts']['Chaostreff-Longname'][0]
with open(target, 'w') as f: with open(target, 'w') as f:
json.dump(erfas, f) json.dump(erfas, f)
@ -222,7 +227,10 @@ def compute_bbox(ns):
for path in tqdm.tqdm([ns.cache_directory.erfa_info, ns.cache_directory.chaostreff_info]): for path in tqdm.tqdm([ns.cache_directory.erfa_info, ns.cache_directory.chaostreff_info]):
with open(path, 'r') as f: with open(path, 'r') as f:
erfadata = json.load(f) erfadata = json.load(f)
for lon, lat in erfadata.values(): for data in erfadata.values():
if 'location' not in data:
continue
lon, lat = data['location']
if len(bounds) == 0: if len(bounds) == 0:
bounds.append(lon) bounds.append(lon)
bounds.append(lat) bounds.append(lat)
@ -561,18 +569,30 @@ def create_svg(ns, bbox):
shapes_countries.append((name, ts)) shapes_countries.append((name, ts))
erfas = {} erfas = {}
erfa_urls = {}
erfa_names = {}
with open(ns.cache_directory.erfa_info, 'r') as f: with open(ns.cache_directory.erfa_info, 'r') as f:
ctdata = json.load(f) ctdata = json.load(f)
for city, location in ctdata.items(): for city, data in ctdata.items():
location = data.get('location')
if location is None: if location is None:
continue continue
xt, yt = transformer.transform(*location) xt, yt = transformer.transform(*location)
erfas[city] = (xt*scalex - origin[0], origin[1] - yt*scaley) erfas[city] = (xt*scalex - origin[0], origin[1] - yt*scaley)
web = data.get('web')
if web is not None:
erfa_urls[city] = web
name = data.get('name')
if name is not None:
erfa_names[city] = name
chaostreffs = {} chaostreffs = {}
chaostreff_urls = {}
chaostreff_names = {}
with open(ns.cache_directory.chaostreff_info, 'r') as f: with open(ns.cache_directory.chaostreff_info, 'r') as f:
ctdata = json.load(f) ctdata = json.load(f)
for city, location in ctdata.items(): for city, data in ctdata.items():
location = data.get('location')
if location is None: if location is None:
continue continue
if city in erfas: if city in erfas:
@ -582,7 +602,12 @@ def create_svg(ns, bbox):
continue continue
xt, yt = transformer.transform(*location) xt, yt = transformer.transform(*location)
chaostreffs[city] = (xt*scalex - origin[0], origin[1] - yt*scaley) chaostreffs[city] = (xt*scalex - origin[0], origin[1] - yt*scaley)
web = data.get('web')
if web is not None:
chaostreff_urls[city] = web
name = data.get('name')
if name is not None:
chaostreff_names[city] = name
rectbox = [0, 0, svg_box[0], svg_box[1]] rectbox = [0, 0, svg_box[0], svg_box[1]]
for name, shape in shapes_states + shapes_countries: for name, shape in shapes_states + shapes_countries:
@ -598,6 +623,9 @@ def create_svg(ns, bbox):
viewBox=f'0 0 {svg_box[0]} {svg_box[1]}', viewBox=f'0 0 {svg_box[0]} {svg_box[1]}',
width=str(svg_box[0]), height=str(svg_box[1])) width=str(svg_box[0]), height=str(svg_box[1]))
print('Layouting labels')
texts = optimize_text_layout(ns, erfas, chaostreffs, (int(svg_box[0]), int(svg_box[1])), svg)
defs = etree.Element('defs') defs = etree.Element('defs')
style = etree.Element('style', type='text/css') style = etree.Element('style', type='text/css')
style.text = f'@import url({ns.stylesheet})' style.text = f'@import url({ns.stylesheet})'
@ -629,26 +657,41 @@ def create_svg(ns, bbox):
svg.append(poly) svg.append(poly)
for city, location in erfas.items(): for city, location in erfas.items():
box = texts[city]
if city in erfa_urls:
group = etree.Element('a', href=erfa_urls[city], target='_blank')
else:
group = etree.Element('g')
group.set('data-erfa', city)
circle = etree.Element('circle', cx=str(location[0]), cy=str(location[1]), r=str(ns.dotsize_erfa)) circle = etree.Element('circle', cx=str(location[0]), cy=str(location[1]), r=str(ns.dotsize_erfa))
circle.set('class', 'erfa') circle.set('class', 'erfa')
circle.set('data-erfa', city) circle.set('data-erfa', city)
svg.append(circle) if city in erfa_names:
title = etree.Element('title')
title.text = erfa_names[city]
circle.append(title)
group.append(circle)
text = etree.Element('text', x=str(box.left), y=str(box.top + box.meta['baseline']))
text.set('class', 'erfalabel')
text.set('data-erfa', city)
text.text = box.meta['text']
group.append(text)
svg.append(group)
for city, location in chaostreffs.items(): for city, location in chaostreffs.items():
circle = etree.Element('circle', cx=str(location[0]), cy=str(location[1]), r=str(ns.dotsize_treff)) circle = etree.Element('circle', cx=str(location[0]), cy=str(location[1]), r=str(ns.dotsize_treff))
circle.set('class', 'chaostreff') circle.set('class', 'chaostreff')
circle.set('data-chaostreff', city) circle.set('data-chaostreff', city)
svg.append(circle) if city in chaostreff_names:
title = etree.Element('title')
title.text = chaostreff_names[city]
print('Layouting labels') circle.append(title)
texts = optimize_text_layout(ns, erfas, chaostreffs, (int(svg_box[0]), int(svg_box[1])), svg) if city in chaostreff_urls:
for city, box in texts.items(): a = etree.Element('a', href=chaostreff_urls[city], target='_blank', title='foox')
text = etree.Element('text', x=str(box.left), y=str(box.top + box.meta['baseline'])) a.append(circle)
text.set('class', 'erfalabel') svg.append(a)
text.set('data-erfa', city) else:
text.text = box.meta['text'] svg.append(circle)
svg.append(text)
print('Done, writing SVG') print('Done, writing SVG')
with open('map.svg', 'wb') as mapfile: with open('map.svg', 'wb') as mapfile:

View file

@ -1,4 +1,6 @@
rect.background { rect.background {
fill: #759eb5; fill: #759eb5;
stroke: none; stroke: none;
@ -16,12 +18,32 @@ polygon.state {
stroke-width: 3; stroke-width: 3;
} }
circle.erfa {
z-index: 1;
}
circle.chaostreff {
z-index: 2;
}
circle.erfa, circle.chaostreff { circle.erfa, circle.chaostreff {
fill: #f47e1e; fill: #f47e1e;
stroke: #ffffff; stroke: #ffffff;
stroke-width: 3; stroke-width: 3;
} }
a > circle, a > text {
cursor: pointer;
}
a:hover > circle {
fill: #5b8ca7;
}
a:hover > text {
fill: #5b8ca7;
}
@font-face { @font-face {
font-family: 'concertone'; font-family: 'concertone';
src: url('./concertone-regular.ttf'); src: url('./concertone-regular.ttf');
@ -30,16 +52,19 @@ circle.erfa, circle.chaostreff {
text.erfalabel { text.erfalabel {
font-family: concertone; font-family: concertone;
font-size: 45px; font-size: 45px;
z-index: 100;
} }
rect.debugleft { rect.debugleft {
stroke: red; stroke: red;
stroke-width: 1; stroke-width: 1;
fill: none; fill: none;
z-index: 1000;
} }
rect.debugright { rect.debugright {
stroke: green; stroke: green;
stroke-width: 1; stroke-width: 1;
fill: none; fill: none;
z-index: 1000;
} }