Generate standalone SVG
This commit is contained in:
parent
286534188a
commit
d8fe6a9d46
2 changed files with 49 additions and 29 deletions
|
@ -46,7 +46,10 @@ class OutputPaths:
|
||||||
raise AttributeError(f'Path exists but is not a directory: {path}')
|
raise AttributeError(f'Path exists but is not a directory: {path}')
|
||||||
os.makedirs(path, exist_ok=True)
|
os.makedirs(path, exist_ok=True)
|
||||||
self.path = path
|
self.path = path
|
||||||
self.svg_path = os.path.join(path, 'map.svg')
|
self.svg_web_path = os.path.join(path, 'web.svg')
|
||||||
|
self.svg_standalone_path = os.path.join(path, 'standalone.svg')
|
||||||
|
self.font_path = os.path.join(path, 'style/font.ttf')
|
||||||
|
self.css_path = os.path.join(path, 'style/erfamap.css')
|
||||||
self.png_path = os.path.join(path, 'map.png')
|
self.png_path = os.path.join(path, 'map.png')
|
||||||
self.html_path = os.path.join(path, 'erfamap.html')
|
self.html_path = os.path.join(path, 'erfamap.html')
|
||||||
self.imagemap_path = os.path.join(path, 'imagemap.html')
|
self.imagemap_path = os.path.join(path, 'imagemap.html')
|
||||||
|
@ -258,6 +261,8 @@ class Erfa(Drawable):
|
||||||
self.lat = lat
|
self.lat = lat
|
||||||
self.x, self.y = ns.projection(lon, lat)
|
self.x, self.y = ns.projection(lon, lat)
|
||||||
self.display_name = display_name if display_name is not None else name
|
self.display_name = display_name if display_name is not None else name
|
||||||
|
if city.lower() not in self.display_name.lower():
|
||||||
|
self.display_name += f' ({city})'
|
||||||
self.web = web
|
self.web = web
|
||||||
self.radius = radius
|
self.radius = radius
|
||||||
|
|
||||||
|
@ -322,7 +327,7 @@ class Erfa(Drawable):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_api(cls, name, attr, radius, ns):
|
def from_api(cls, ns, name, attr, radius):
|
||||||
city = attr['Chaostreff-City'][0]
|
city = attr['Chaostreff-City'][0]
|
||||||
location = cls.address_lookup(attr)
|
location = cls.address_lookup(attr)
|
||||||
if location is None:
|
if location is None:
|
||||||
|
@ -393,6 +398,7 @@ class Erfa(Drawable):
|
||||||
erfa = cls.from_api(ns, name, attr['printouts'], radius)
|
erfa = cls.from_api(ns, name, attr['printouts'], radius)
|
||||||
erfas.append(erfa)
|
erfas.append(erfa)
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
|
breakpoint()
|
||||||
print(e)
|
print(e)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -615,10 +621,9 @@ def compute_bbox(ns):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def optimize_text_layout(ns, erfas, chaostreffs, width, svg):
|
def optimize_text_layout(ns, font, erfas, chaostreffs, width, svg):
|
||||||
|
|
||||||
# Load the font and measure its various different heights
|
# Measure the various different font heights
|
||||||
font = ImageFont.truetype(ns.font, ns.font_size)
|
|
||||||
pil = ImageDraw(Image.new('P', (1, 1)))
|
pil = ImageDraw(Image.new('P', (1, 1)))
|
||||||
xheight = -pil.textbbox((0,0), 'x', font=font, anchor='ls')[1]
|
xheight = -pil.textbbox((0,0), 'x', font=font, anchor='ls')[1]
|
||||||
capheight = -pil.textbbox((0,0), 'A', font=font, anchor='ls')[1]
|
capheight = -pil.textbbox((0,0), 'A', font=font, anchor='ls')[1]
|
||||||
|
@ -763,6 +768,9 @@ def create_imagemap(ns, size, parent, erfas, chaostreffs, texts):
|
||||||
def create_svg(ns, bbox):
|
def create_svg(ns, bbox):
|
||||||
print('Creating SVG image')
|
print('Creating SVG image')
|
||||||
|
|
||||||
|
# Load the font used for the labels
|
||||||
|
font = ImageFont.truetype(ns.font, ns.font_size)
|
||||||
|
|
||||||
# Convert from WGS84 lon, lat to chosen projection
|
# Convert from WGS84 lon, lat to chosen projection
|
||||||
svg_box = ns.projection.setup(ns.scale_x, ns.scale_y, bbox=bbox)
|
svg_box = ns.projection.setup(ns.scale_x, ns.scale_y, bbox=bbox)
|
||||||
rectbox = [0, 0, svg_box[0], svg_box[1]]
|
rectbox = [0, 0, svg_box[0], svg_box[1]]
|
||||||
|
@ -786,22 +794,27 @@ def create_svg(ns, bbox):
|
||||||
rectbox[3] = max(y, rectbox[3])
|
rectbox[3] = max(y, rectbox[3])
|
||||||
|
|
||||||
print('Copying stylesheet and font')
|
print('Copying stylesheet and font')
|
||||||
dst = os.path.join(ns.output_directory.path, ns.stylesheet)
|
os.makedirs(os.path.dirname(ns.output_directory.font_path), exist_ok=True)
|
||||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
shutil.copyfile(ns.font, ns.output_directory.font_path)
|
||||||
shutil.copyfile(ns.stylesheet, dst)
|
os.makedirs(os.path.dirname(ns.output_directory.css_path), exist_ok=True)
|
||||||
dst = os.path.join(ns.output_directory.path, ns.font)
|
fontrelpath = os.path.relpath(ns.output_directory.font_path,
|
||||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
start=os.path.dirname(ns.output_directory.css_path))
|
||||||
shutil.copyfile(ns.font, dst)
|
with open(ns.stylesheet, 'r') as src:
|
||||||
|
with open(ns.output_directory.css_path, 'w') as dst:
|
||||||
|
dst.write(f'''
|
||||||
|
@font-face {{
|
||||||
|
font-family: "{font.font.family}";
|
||||||
|
src: url("{fontrelpath}");
|
||||||
|
}}
|
||||||
|
|
||||||
|
''')
|
||||||
|
dst.write(src.read())
|
||||||
|
|
||||||
svg = etree.Element('svg',
|
svg = etree.Element('svg',
|
||||||
xmlns='http://www.w3.org/2000/svg',
|
xmlns='http://www.w3.org/2000/svg',
|
||||||
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]))
|
||||||
|
|
||||||
style = etree.Element('style', type='text/css')
|
|
||||||
style.text = f'@import url({ns.stylesheet})'
|
|
||||||
svg.append(style)
|
|
||||||
|
|
||||||
bg = etree.Element('rect',
|
bg = etree.Element('rect',
|
||||||
id='background',
|
id='background',
|
||||||
x=str(rectbox[0]),
|
x=str(rectbox[0]),
|
||||||
|
@ -813,7 +826,7 @@ def create_svg(ns, bbox):
|
||||||
|
|
||||||
# This can take some time, especially if lots of candidates are generated
|
# This can take some time, especially if lots of candidates are generated
|
||||||
print('Layouting labels')
|
print('Layouting labels')
|
||||||
texts = optimize_text_layout(ns, erfas, chaostreffs, width=svg_box[0], svg=svg)
|
texts = optimize_text_layout(ns, font, erfas, chaostreffs, width=svg_box[0], svg=svg)
|
||||||
|
|
||||||
# Render shortest shapes last s.t. Berlin, Hamburg and Bremen are rendered on top of their surrounding states
|
# Render shortest shapes last s.t. Berlin, Hamburg and Bremen are rendered on top of their surrounding states
|
||||||
states = sorted(states, key=lambda x: -len(x))
|
states = sorted(states, key=lambda x: -len(x))
|
||||||
|
@ -827,24 +840,36 @@ def create_svg(ns, bbox):
|
||||||
|
|
||||||
# Generate SVG, PNG and HTML output files
|
# Generate SVG, PNG and HTML output files
|
||||||
|
|
||||||
print('Writing SVG')
|
print('Writing Web SVG')
|
||||||
with open(ns.output_directory.svg_path, 'wb') as mapfile:
|
with open(ns.output_directory.svg_web_path, 'wb') as mapfile:
|
||||||
root = etree.ElementTree(svg)
|
root = etree.ElementTree(svg)
|
||||||
root.write(mapfile)
|
root.write(mapfile)
|
||||||
|
|
||||||
|
style = etree.Element('style', type='text/css')
|
||||||
|
with open(ns.stylesheet, 'r') as css:
|
||||||
|
style.text = css.read()
|
||||||
|
svg.insert(0, style)
|
||||||
|
|
||||||
|
print('Writing Standalone SVG')
|
||||||
|
with open(ns.output_directory.svg_standalone_path, 'wb') as mapfile:
|
||||||
|
root = etree.ElementTree(svg)
|
||||||
|
root.write(mapfile)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
print('Writing PNG')
|
print('Writing PNG')
|
||||||
cairosvg.svg2png(url=ns.output_directory.svg_path, write_to=ns.output_directory.png_path,
|
cairosvg.svg2png(url=ns.output_directory.svg_web_path, write_to=ns.output_directory.png_path,
|
||||||
scale=ns.png_scale)
|
scale=ns.png_scale)
|
||||||
|
|
||||||
print('Writing HTML SVG page')
|
print('Writing HTML SVG page')
|
||||||
html = etree.Element('html')
|
html = etree.Element('html')
|
||||||
head = etree.Element('head')
|
head = etree.Element('head')
|
||||||
link = etree.Element('link', rel='stylesheet', href=ns.stylesheet)
|
link = etree.Element('link', rel='stylesheet', href=ns.output_directory.rel(ns.output_directory.css_path))
|
||||||
head.append(link)
|
head.append(link)
|
||||||
html.append(head)
|
html.append(head)
|
||||||
body = etree.Element('body')
|
body = etree.Element('body')
|
||||||
obj = etree.Element('object',
|
obj = etree.Element('object',
|
||||||
data=ns.output_directory.rel(ns.output_directory.svg_path),
|
data=ns.output_directory.rel(ns.output_directory.svg_web_path),
|
||||||
width=str(svg_box[0]), height=str(svg_box[1]))
|
width=str(svg_box[0]), height=str(svg_box[1]))
|
||||||
create_imagemap(ns, svg_box, obj, erfas, chaostreffs, texts)
|
create_imagemap(ns, svg_box, obj, erfas, chaostreffs, texts)
|
||||||
body.append(obj)
|
body.append(obj)
|
||||||
|
@ -856,7 +881,7 @@ def create_svg(ns, bbox):
|
||||||
print('Writing HTML Image Map')
|
print('Writing HTML Image Map')
|
||||||
html = etree.Element('html')
|
html = etree.Element('html')
|
||||||
head = etree.Element('head')
|
head = etree.Element('head')
|
||||||
link = etree.Element('link', rel='stylesheet', href=ns.stylesheet)
|
link = etree.Element('link', rel='stylesheet', href=ns.output_directory.rel(ns.output_directory.css_path))
|
||||||
head.append(link)
|
head.append(link)
|
||||||
html.append(head)
|
html.append(head)
|
||||||
body = etree.Element('body')
|
body = etree.Element('body')
|
||||||
|
@ -916,7 +941,7 @@ def main():
|
||||||
if os.path.exists(ns.cache_directory.chaostreff_info):
|
if os.path.exists(ns.cache_directory.chaostreff_info):
|
||||||
os.unlink(ns.cache_directory.chaostreff_info)
|
os.unlink(ns.cache_directory.chaostreff_info)
|
||||||
print('Retrieving Chaostreffs information')
|
print('Retrieving Chaostreffs information')
|
||||||
Chaostreff.fetch(target=ns.cache_directory.chaostreff_info, radius=ns.dotsize_treff)
|
Chaostreff.fetch(ns, target=ns.cache_directory.chaostreff_info, radius=ns.dotsize_treff)
|
||||||
|
|
||||||
bbox = compute_bbox(ns)
|
bbox = compute_bbox(ns)
|
||||||
|
|
||||||
|
|
|
@ -51,13 +51,8 @@ a:hover > text {
|
||||||
fill: #5b8ca7;
|
fill: #5b8ca7;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'concertone';
|
|
||||||
src: url('./concertone-regular.ttf');
|
|
||||||
}
|
|
||||||
|
|
||||||
text.erfalabel {
|
text.erfalabel {
|
||||||
font-family: concertone;
|
font-family: "Concert One";
|
||||||
font-size: 45px;
|
font-size: 45px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
Loading…
Reference in a new issue