add first shadows to rendering and fix png rendering (fuck rsvg)
This commit is contained in:
parent
4d3fb7786f
commit
ff3a881368
3 changed files with 57 additions and 19 deletions
|
@ -7,9 +7,9 @@ from c3nav.mapdata.utils.svg import SVGImage
|
|||
|
||||
|
||||
def render_svg(level, miny, minx, maxy, maxx, scale=1):
|
||||
svg = SVGImage(bounds=((miny, minx), (maxy, maxx)), scale=scale)
|
||||
svg = SVGImage(bounds=((miny, minx), (maxy, maxx)), scale=scale, buffer=2)
|
||||
|
||||
within_coords = (minx-1, miny-1, maxx+1, maxy+1)
|
||||
within_coords = (minx-2, miny-2, maxx+2, maxy+2)
|
||||
bbox = box(*within_coords)
|
||||
|
||||
levels = Level.objects.filter(Q(on_top_of=level.pk) | Q(base_altitude__lte=level.base_altitude))
|
||||
|
@ -41,4 +41,4 @@ def render_svg(level, miny, minx, maxy, maxx, scale=1):
|
|||
svg.add_geometry(walls_geom, fill_color='#aaaaaa', stroke_px=0.5, stroke_color='#aaaaaa',
|
||||
elevation=level.default_height)
|
||||
|
||||
return svg.get_xml()
|
||||
return svg
|
||||
|
|
|
@ -1,33 +1,34 @@
|
|||
import io
|
||||
import math
|
||||
import re
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as ET
|
||||
from itertools import chain
|
||||
|
||||
from PIL import Image
|
||||
from shapely.affinity import scale, translate
|
||||
from shapely.ops import unary_union
|
||||
|
||||
|
||||
class SVGImage:
|
||||
def __init__(self, bounds, scale: float=1):
|
||||
def __init__(self, bounds, scale: float=1, buffer=0):
|
||||
(self.bottom, self.left), (self.top, self.right) = bounds
|
||||
self.width = self.right-self.left
|
||||
self.height = self.top-self.bottom
|
||||
self.scale = scale
|
||||
self.buffer_px = int(math.ceil(buffer*self.scale))
|
||||
self.buffer = self.buffer_px/self.scale
|
||||
self.g = ET.Element('g', {})
|
||||
self.defs = ET.Element('defs')
|
||||
self.def_i = 0
|
||||
self.altitudes = {}
|
||||
self.last_altitude = None
|
||||
|
||||
blur_filter = ET.Element('filter', {'id': 'wallblur'})
|
||||
blur_filter.append(ET.Element('feGaussianBlur',
|
||||
{'in': 'SourceGraphic',
|
||||
'stdDeviation': str(int(0.7 * self.scale))}))
|
||||
self.defs.append(blur_filter)
|
||||
self.blurs = set()
|
||||
|
||||
def get_element(self):
|
||||
root = ET.Element('svg', {
|
||||
'width': str(self.width*self.scale),
|
||||
'height': str(self.height*self.scale),
|
||||
'width': str(self.width*self.scale+self.buffer_px*2),
|
||||
'height': str(self.height*self.scale+self.buffer_px*2),
|
||||
'xmlns:svg': 'http://www.w3.org/2000/svg',
|
||||
'xmlns': 'http://www.w3.org/2000/svg',
|
||||
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||||
|
@ -39,6 +40,19 @@ class SVGImage:
|
|||
def get_xml(self):
|
||||
return ET.tostring(self.get_element()).decode()
|
||||
|
||||
def get_png(self):
|
||||
p = subprocess.run(('rsvg-convert', '--format', 'png'),
|
||||
input=self.get_xml().encode(), stdout=subprocess.PIPE, check=True)
|
||||
f = io.BytesIO(p.stdout)
|
||||
img = Image.open(f)
|
||||
img = img.crop((self.buffer_px, self.buffer_px,
|
||||
self.buffer_px+int(self.width*self.scale),
|
||||
self.buffer_px+int(self.height*self.scale)))
|
||||
f = io.BytesIO()
|
||||
img.save(f, 'PNG')
|
||||
f.seek(0)
|
||||
return f.read()
|
||||
|
||||
def new_defid(self):
|
||||
defid = 's'+str(self.def_i)
|
||||
self.def_i += 1
|
||||
|
@ -48,7 +62,7 @@ class SVGImage:
|
|||
return re.sub(r'([0-9]+)\.0', r'\1', re.sub(r'([0-9]+\.[0-9])[0-9]+', r'\1', data))
|
||||
|
||||
def _create_geometry(self, geometry):
|
||||
geometry = translate(geometry, xoff=0-self.left, yoff=0-self.bottom)
|
||||
geometry = translate(geometry, xoff=0-self.left+self.buffer, yoff=0-self.bottom-self.buffer)
|
||||
geometry = scale(geometry, xfact=1, yfact=-1, origin=(self.width / 2, self.height / 2))
|
||||
geometry = scale(geometry, xfact=self.scale, yfact=self.scale, origin=(0, 0))
|
||||
element = ET.fromstring(self._trim_decimals(geometry.svg(0, '#FFFFFF')))
|
||||
|
@ -77,6 +91,21 @@ class SVGImage:
|
|||
self.defs.append(element)
|
||||
return defid
|
||||
|
||||
def get_blur(self, elevation):
|
||||
blur_id = 'blur'+str(elevation*100)
|
||||
if elevation not in self.blurs:
|
||||
blur_filter = ET.Element('filter', {'id': blur_id,
|
||||
'width': '200%',
|
||||
'height': '200%',
|
||||
'x': '-50%',
|
||||
'y': '-50%'})
|
||||
blur_filter.append(ET.Element('feGaussianBlur',
|
||||
{'in': 'SourceGraphic',
|
||||
'stdDeviation': str(elevation*self.scale)}))
|
||||
self.defs.append(blur_filter)
|
||||
self.blurs.add(elevation)
|
||||
return blur_id
|
||||
|
||||
def add_clip_path(self, *geometries, inverted=False, subtract=False, defid=None):
|
||||
if defid is None:
|
||||
defid = self.new_defid()
|
||||
|
@ -87,9 +116,11 @@ class SVGImage:
|
|||
return defid
|
||||
|
||||
def clip_altitudes(self, new_geometry, new_altitude=None):
|
||||
for altitude, geometry in self.altitudes.items():
|
||||
for altitude, geometry in tuple(self.altitudes.items()):
|
||||
if altitude != new_altitude:
|
||||
self.altitudes[altitude] = geometry.difference(new_geometry)
|
||||
if self.altitudes[altitude].is_empty:
|
||||
self.altitudes.pop(altitude)
|
||||
if new_altitude is not None:
|
||||
if self.last_altitude is not None and self.last_altitude > new_altitude:
|
||||
raise ValueError('Altitudes have to be ascending.')
|
||||
|
@ -111,6 +142,16 @@ class SVGImage:
|
|||
element = self._create_geometry(geometry)
|
||||
|
||||
if altitude is not None or elevation is not None:
|
||||
blur_radius = float(1 if elevation is None else elevation)
|
||||
|
||||
buffered_geometry = translate(geometry.buffer(blur_radius/20),
|
||||
xoff=blur_radius/40, yoff=-blur_radius/40)
|
||||
shadow_element = self._create_geometry(buffered_geometry)
|
||||
shadow_element.set('fill', '#000000')
|
||||
shadow_element.set('fill-opacity', '0.14')
|
||||
shadow_element.set('filter', 'url(#'+self.get_blur(blur_radius/15)+')')
|
||||
self.g.append(shadow_element)
|
||||
|
||||
self.clip_altitudes(geometry, altitude)
|
||||
|
||||
else:
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import subprocess
|
||||
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from shapely.geometry import box
|
||||
|
@ -30,10 +28,9 @@ def tile(request, level, zoom, x, y, format):
|
|||
svg = render_svg(level, miny, minx, maxy, maxx, scale=2**zoom)
|
||||
|
||||
if format == 'svg':
|
||||
response = HttpResponse(svg, 'image/svg+xml')
|
||||
response = HttpResponse(svg.get_xml(), 'image/svg+xml')
|
||||
elif format == 'png':
|
||||
p = subprocess.run(('rsvg-convert', '--format', 'png'), input=svg.encode(), stdout=subprocess.PIPE, check=True)
|
||||
response = HttpResponse(p.stdout, content_type="image/png")
|
||||
response = HttpResponse(svg.get_png(), 'image/png')
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue