88import cairo # type: ignore
99from PIL import Image # type: ignore
1010
11- from .area import Area
1211from .color import Color , BLACK , WHITE
13- from .image_marker import ImageMarker
14- from .line import Line
15- from .marker import Marker
1612from .renderer import Renderer
1713from .transformer import Transformer
1814
15+ if typing .TYPE_CHECKING :
16+ # avoid circlic import
17+ from .object import Object # pylint: disable=cyclic-import
18+
1919
2020class CairoRenderer (Renderer ):
2121 def __init__ (self , transformer : Transformer ) -> None :
@@ -27,6 +27,29 @@ def __init__(self, transformer: Transformer) -> None:
2727 def image_surface (self ) -> cairo .ImageSurface :
2828 return self ._surface
2929
30+ def context (self ) -> cairo .Context :
31+ return self ._context
32+
33+ @staticmethod
34+ def create_image (image_data : bytes ) -> cairo .ImageSurface :
35+ image = Image .open (io .BytesIO (image_data ))
36+ if image .format == "PNG" :
37+ return cairo .ImageSurface .create_from_png (io .BytesIO (image_data ))
38+ png_bytes = io .BytesIO ()
39+ image .save (png_bytes , format = "PNG" )
40+ png_bytes .flush ()
41+ png_bytes .seek (0 )
42+ return cairo .ImageSurface .create_from_png (png_bytes )
43+
44+ def render_objects (self , objects : typing .List ["Object" ]) -> None :
45+ x_count = math .ceil (self ._trans .image_width () / (2 * self ._trans .world_width ()))
46+ for obj in objects :
47+ for p in range (- x_count , x_count + 1 ):
48+ self ._context .save ()
49+ self ._context .translate (p * self ._trans .world_width (), 0 )
50+ obj .render_cairo (self )
51+ self ._context .restore ()
52+
3053 def render_background (self , color : typing .Optional [Color ]) -> None :
3154 if color is None :
3255 return
@@ -56,82 +79,6 @@ def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optiona
5679 except RuntimeError :
5780 pass
5881
59- def render_marker_object (self , marker : Marker ) -> None :
60- x , y = self ._trans .ll2pixel (marker .latlng ())
61- r = marker .size ()
62- dx = math .sin (math .pi / 3.0 )
63- dy = math .cos (math .pi / 3.0 )
64- x_count = math .ceil (self ._trans .image_width () / (2 * self ._trans .world_width ()))
65- for p in range (- x_count , x_count + 1 ):
66- self ._context .save ()
67-
68- self ._context .translate (p * self ._trans .world_width (), 0 )
69-
70- self ._context .set_source_rgb (* marker .color ().text_color ().float_rgb ())
71- self ._context .arc (x , y - 2 * r , r , 0 , 2 * math .pi )
72- self ._context .fill ()
73- self ._context .new_path ()
74- self ._context .line_to (x , y )
75- self ._context .line_to (x - dx * r , y - 2 * r + dy * r )
76- self ._context .line_to (x + dx * r , y - 2 * r + dy * r )
77- self ._context .close_path ()
78- self ._context .fill ()
79-
80- self ._context .set_source_rgb (* marker .color ().float_rgb ())
81- self ._context .arc (x , y - 2 * r , r - 1 , 0 , 2 * math .pi )
82- self ._context .fill ()
83- self ._context .new_path ()
84- self ._context .line_to (x , y - 1 )
85- self ._context .line_to (x - dx * (r - 1 ), y - 2 * r + dy * (r - 1 ))
86- self ._context .line_to (x + dx * (r - 1 ), y - 2 * r + dy * (r - 1 ))
87- self ._context .close_path ()
88- self ._context .fill ()
89-
90- self ._context .restore ()
91-
92- def render_image_marker_object (self , marker : ImageMarker ) -> None :
93- x , y = self ._trans .ll2pixel (marker .latlng ())
94- image = cairo .ImageSurface .create_from_png (io .BytesIO (marker .image_data ()))
95- x_count = math .ceil (self ._trans .image_width () / (2 * self ._trans .world_width ()))
96- for p in range (- x_count , x_count + 1 ):
97- self ._context .save ()
98-
99- self ._context .translate (p * self ._trans .world_width () + x - marker .origin_x (), y - marker .origin_y ())
100- self ._context .set_source_surface (image )
101- self ._context .paint ()
102-
103- self ._context .restore ()
104-
105- def render_line_object (self , line : Line ) -> None :
106- if line .width () == 0 :
107- return
108- xys = [self ._trans .ll2pixel (latlng ) for latlng in line .interpolate ()]
109- x_count = math .ceil (self ._trans .image_width () / (2 * self ._trans .world_width ()))
110- for p in range (- x_count , x_count + 1 ):
111- self ._context .save ()
112- self ._context .translate (p * self ._trans .world_width (), 0 )
113- self ._context .set_source_rgba (* line .color ().float_rgba ())
114- self ._context .set_line_width (line .width ())
115- self ._context .new_path ()
116- for x , y in xys :
117- self ._context .line_to (x , y )
118- self ._context .stroke ()
119- self ._context .restore ()
120-
121- def render_area_object (self , area : Area ) -> None :
122- xys = [self ._trans .ll2pixel (latlng ) for latlng in area .interpolate ()]
123- x_count = math .ceil (self ._trans .image_width () / (2 * self ._trans .world_width ()))
124- for p in range (- x_count , x_count + 1 ):
125- self ._context .save ()
126- self ._context .translate (p * self ._trans .world_width (), 0 )
127- self ._context .new_path ()
128- for x , y in xys :
129- self ._context .line_to (x , y )
130- self ._context .set_source_rgba (* area .fill_color ().float_rgba ())
131- self ._context .fill ()
132- self ._context .restore ()
133- self .render_line_object (area )
134-
13582 def render_attribution (self , attribution : typing .Optional [str ]) -> None :
13683 if (attribution is None ) or (attribution == "" ):
13784 return
@@ -160,11 +107,4 @@ def fetch_tile(
160107 image_data = download (self ._trans .zoom (), x , y )
161108 if image_data is None :
162109 return None
163- image = Image .open (io .BytesIO (image_data ))
164- if image .format == "PNG" :
165- return cairo .ImageSurface .create_from_png (io .BytesIO (image_data ))
166- png_bytes = io .BytesIO ()
167- image .save (png_bytes , format = "PNG" )
168- png_bytes .flush ()
169- png_bytes .seek (0 )
170- return cairo .ImageSurface .create_from_png (png_bytes )
110+ return self .create_image (image_data )
0 commit comments