Skip to content

Commit 7fcb5b8

Browse files
committed
Add an example with cistom 'text labels'. Closes #10.
1 parent da06ff9 commit 7fcb5b8

File tree

4 files changed

+142
-1
lines changed

4 files changed

+142
-1
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ format:
3232

3333
.PHONY: run-examples
3434
run-examples:
35+
(cd examples && PYTHONPATH=.. ../.env/bin/python custom_objects.py)
3536
(cd examples && PYTHONPATH=.. ../.env/bin/python draw_gpx.py running.gpx)
3637
(cd examples && PYTHONPATH=.. ../.env/bin/python frankfurt_newyork.py)
3738
(cd examples && PYTHONPATH=.. ../.env/bin/python freiburg_area.py)

examples/custom_objects.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env python
2+
3+
# py-staticmaps
4+
# Copyright (c) 2021 Florian Pigorsch; see /LICENSE for licensing information
5+
6+
import cairo # type: ignore
7+
import s2sphere # type: ignore
8+
import staticmaps
9+
10+
11+
class TextLabel(staticmaps.Object):
12+
def __init__(self, latlng: s2sphere.LatLng, text: str) -> None:
13+
staticmaps.Object.__init__(self)
14+
self._latlng = latlng
15+
self._text = text
16+
self._margin = 4
17+
self._arrow = 16
18+
self._font_size = 12
19+
20+
def latlng(self) -> s2sphere.LatLng:
21+
return self._latlng
22+
23+
def bounds(self) -> s2sphere.LatLngRect:
24+
return s2sphere.LatLngRect.from_point(self._latlng)
25+
26+
def extra_pixel_bounds(self) -> staticmaps.PixelBoundsT:
27+
# Guess text extents.
28+
tw = len(self._text) * self._font_size * 0.5
29+
th = self._font_size * 1.2
30+
w = max(self._arrow, tw + 2.0 * self._margin)
31+
return (int(w / 2.0), int(th + 2.0 * self._margin + self._arrow), int(w / 2), 0)
32+
33+
def render_svg(self, renderer: staticmaps.SvgRenderer) -> None:
34+
x, y = renderer.transformer().ll2pixel(self.latlng())
35+
36+
# guess text extents
37+
tw = len(self._text) * self._font_size * 0.5
38+
th = self._font_size * 1.2
39+
40+
w = max(self._arrow, tw + 2 * self._margin)
41+
h = th + 2 * self._margin
42+
43+
path = renderer.drawing().path(
44+
fill="#ffffff",
45+
stroke="#ff0000",
46+
stroke_width=1,
47+
opacity=1.0,
48+
)
49+
path.push(f"M {x} {y}")
50+
path.push(f" l {self._arrow / 2} {-self._arrow}")
51+
path.push(f" l {w / 2 - self._arrow / 2} 0")
52+
path.push(f" l 0 {-h}")
53+
path.push(f" l {-w} 0")
54+
path.push(f" l 0 {h}")
55+
path.push(f" l {w / 2 - self._arrow / 2} 0")
56+
path.push("Z")
57+
renderer.group().add(path)
58+
59+
renderer.group().add(
60+
renderer.drawing().text(
61+
self._text,
62+
text_anchor="middle",
63+
dominant_baseline="central",
64+
insert=(x, y - self._arrow - h / 2),
65+
font_family="sans-serif",
66+
font_size=f"{self._font_size}px",
67+
fill="#000000",
68+
)
69+
)
70+
71+
def render_cairo(self, renderer: staticmaps.CairoRenderer) -> None:
72+
x, y = renderer.transformer().ll2pixel(self.latlng())
73+
74+
ctx = renderer.context()
75+
ctx.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
76+
77+
ctx.set_font_size(self._font_size)
78+
x_bearing, y_bearing, tw, th, _, _ = ctx.text_extents(self._text)
79+
80+
w = max(self._arrow, tw + 2 * self._margin)
81+
h = th + 2 * self._margin
82+
83+
path = [
84+
(x, y),
85+
(x + self._arrow / 2, y - self._arrow),
86+
(x + w / 2, y - self._arrow),
87+
(x + w / 2, y - self._arrow - h),
88+
(x - w / 2, y - self._arrow - h),
89+
(x - w / 2, y - self._arrow),
90+
(x - self._arrow / 2, y - self._arrow),
91+
]
92+
93+
ctx.set_source_rgb(1, 1, 1)
94+
ctx.new_path()
95+
for p in path:
96+
ctx.line_to(*p)
97+
ctx.close_path()
98+
ctx.fill()
99+
100+
ctx.set_source_rgb(1, 0, 0)
101+
ctx.set_line_width(1)
102+
ctx.new_path()
103+
for p in path:
104+
ctx.line_to(*p)
105+
ctx.close_path()
106+
ctx.stroke()
107+
108+
ctx.set_source_rgb(0, 0, 0)
109+
ctx.set_line_width(1)
110+
ctx.move_to(x - tw / 2 - x_bearing, y - self._arrow - h / 2 - y_bearing - th / 2)
111+
ctx.show_text(self._text)
112+
ctx.stroke()
113+
114+
115+
context = staticmaps.Context()
116+
117+
p1 = staticmaps.create_latlng(48.005774, 7.834042)
118+
p2 = staticmaps.create_latlng(47.988716, 7.868804)
119+
p3 = staticmaps.create_latlng(47.985958, 7.824601)
120+
121+
context.add_object(TextLabel(p1, "X"))
122+
context.add_object(TextLabel(p2, "Label"))
123+
context.add_object(TextLabel(p3, "This is a very long text label"))
124+
125+
context.set_tile_provider(staticmaps.tile_provider_CartoDarkNoLabels)
126+
127+
# render png
128+
image = context.render_cairo(800, 500)
129+
image.write_to_png("custom_objects.png")
130+
131+
# render svg
132+
svg_image = context.render_svg(800, 500)
133+
with open("custom_objects.svg", "w", encoding="utf-8") as f:
134+
svg_image.write(f, pretty=True)

staticmaps/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
# flake8: noqa
55
from .area import Area
6+
from .cairo_renderer import CairoRenderer
67
from .circle import Circle
78
from .color import (
89
parse_color,
@@ -26,13 +27,18 @@
2627
from .marker import Marker
2728
from .meta import GITHUB_URL, LIB_NAME, VERSION
2829
from .object import Object, PixelBoundsT
30+
from .svg_renderer import SvgRenderer
2931
from .tile_downloader import TileDownloader
3032
from .tile_provider import (
3133
TileProvider,
3234
default_tile_providers,
3335
tile_provider_OSM,
3436
tile_provider_StamenTerrain,
3537
tile_provider_StamenToner,
38+
tile_provider_StamenTonerLite,
3639
tile_provider_ArcGISWorldImagery,
40+
tile_provider_CartoNoLabels,
41+
tile_provider_CartoDarkNoLabels,
42+
tile_provider_None,
3743
)
3844
from .transformer import Transformer

staticmaps/cairo_renderer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def render_attribution(self, attribution: typing.Optional[str]) -> None:
8484
return
8585
width, height = self._trans.image_size()
8686
self._context.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
87-
font_size = 12.0
87+
font_size = 9.0
8888
while True:
8989
self._context.set_font_size(font_size)
9090
_, f_descent, f_height, _, _ = self._context.font_extents()

0 commit comments

Comments
 (0)