Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add python 3.12 #14

Open
wants to merge 3 commits into
base: main_ext
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
max-parallel: 4
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- name: Git config
run: git config --global core.autocrlf input
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ help: ## Print help for each target
| sort | awk 'BEGIN {FS=":.* ## "}; {printf "%-25s %s\n", $$1, $$2};'

clean: ## Cleanup
@rm -rf ./.env
@rm -f ./*.pyc
@rm -rf ./__pycache__
@rm -f $(SRC_CORE)/*.pyc
Expand Down
1 change: 1 addition & 0 deletions gpxtrackposter/calendar_drawer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Draw a calendar poster."""

# Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand Down
1 change: 1 addition & 0 deletions gpxtrackposter/circular_drawer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Draw a circular Poster."""

# Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand Down
1 change: 1 addition & 0 deletions gpxtrackposter/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Exceptions"""

# Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand Down
1 change: 1 addition & 0 deletions gpxtrackposter/github_drawer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Draw a GitHub style poster."""

# Copyright 2020-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand Down
1 change: 1 addition & 0 deletions gpxtrackposter/grid_drawer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Draw a grid poster."""

# Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand Down
173 changes: 173 additions & 0 deletions gpxtrackposter/heatmap_drawer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Draw a heatmap poster."""

# Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand All @@ -7,11 +8,17 @@
import argparse
import logging
import math
import os
import sys
import uuid
from operator import itemgetter
from typing import Dict, List, Optional, Tuple

import s2sphere # type: ignore
import staticmaps # type: ignore
import svgwrite # type: ignore
from geopy.distance import distance # type: ignore
from PIL import Image # type: ignore

from gpxtrackposter import utils
from gpxtrackposter.exceptions import ParameterError, PosterError
Expand All @@ -38,6 +45,7 @@ class HeatmapDrawer(TracksDrawer):
create_args: Create arguments for heatmap.
fetch_args: Get arguments passed.
draw: Draw the heatmap based on the Poster's tracks.
draw_background: Draw the heatmaps background image if requested.
"""

def __init__(self, the_poster: Poster):
Expand All @@ -49,6 +57,11 @@ def __init__(self, the_poster: Poster):
self._heatmap_line_width_lower: List[Tuple[float, float]] = [(0.10, 5.0), (0.20, 2.0), (1.0, 0.30)]
self._heatmap_line_width_upper: List[Tuple[float, float]] = [(0.02, 0.5), (0.05, 0.2), (1.0, 0.05)]
self._heatmap_line_width: Optional[List[Tuple[float, float]]] = None
self._heatmap_renderer: str = "pillow"
self._tile_provider: Optional[staticmaps.TileProvider] = None
self._tile_context: staticmaps.Context = staticmaps.Context()
self._bg_max_size: int = 1200
self._transformer: Optional[staticmaps.Transformer] = None

def create_args(self, args_parser: argparse.ArgumentParser) -> None:
"""Add arguments to the parser
Expand Down Expand Up @@ -80,6 +93,36 @@ def create_args(self, args_parser: argparse.ArgumentParser) -> None:
help="Define three transparency and width tuples for the heatmap lines or set it to "
"`automatic` for automatic calculation (default: 0.1,5.0, 0.2,2.0, 1.0,0.3).",
)
tile_provider = staticmaps.default_tile_providers.keys()
group.add_argument(
"--heatmap-tile-provider",
dest="heatmap_tile_provider",
metavar="TILE_PROVIDER",
type=str,
choices=tile_provider,
help="Optionally, choose a tile provider from the list for a background map image: "
f"{', '.join(tile_provider)}. (Default: None)",
)
group.add_argument(
"--heatmap-tile-max-size",
dest="heatmap_tile_max_size",
metavar="PIXEL",
type=int,
default=1200,
help="Set the maximum background image size (which is afterwards scaled to the poster size). "
"This setting defines how much details will be shown on the map. "
"Be sure to choose a reasonable value! (default: 1200 px)",
)
bg_renderer = ["pillow", "cairo"]
group.add_argument(
"--heatmap-tile-renderer",
dest="heatmap_renderer",
metavar="RENDERER",
choices=bg_renderer,
default=self._heatmap_renderer,
help=f"Choose a renderer for generating the background image, one of {', '.join(bg_renderer)}. "
f"(default: {self._heatmap_renderer})",
)

def fetch_args(self, args: argparse.Namespace) -> None:
"""Get arguments that were passed, and also perform basic validation on them.
Expand All @@ -93,6 +136,23 @@ def fetch_args(self, args: argparse.Namespace) -> None:
self._center = self.validate_heatmap_center(args.heatmap_center)
self._radius = self.validate_heatmap_radius(args.heatmap_radius)
self._heatmap_line_width = self.validate_heatmap_line_width(args.heatmap_line_width)
self._center = self.validate_heatmap_center(args.heatmap_center)
self._radius = self.validate_heatmap_radius(args.heatmap_radius)
self._heatmap_line_width = self.validate_heatmap_line_width(args.heatmap_line_width)

if args.heatmap_tile_provider:
self._tile_provider = args.heatmap_tile_provider
if args.heatmap_tile_max_size:
self._bg_max_size = args.heatmap_tile_max_size
if args.heatmap_tile_max_size > 4800:
msg = (
f"A size of < {args.heatmap_tile_max_size} > pixels for the background image is very high.\n"
"Fetching large tiles takes time and consumes much disk space.\n"
"Consider choosing a smaller size!"
)
log.warning(msg)
# set background image renderer
self._heatmap_renderer = args.heatmap_renderer

def get_line_transparencies_and_widths(self, bbox: s2sphere.sphere.LatLngRect) -> List[Tuple[float, float]]:
"""Get a list of tuples of line widths and transparencies
Expand Down Expand Up @@ -174,6 +234,7 @@ def draw(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, size: XY, offs
if len(self.poster.tracks) == 0:
raise PosterError("No tracks to draw.")
bbox = self._determine_bbox()
size, offset = self._get_tracks_size_offset(bbox, size, offset)
line_transparencies_and_widths = self.get_line_transparencies_and_widths(bbox)
year_groups: Dict[int, svgwrite.container.Group] = {}
for tr in self.poster.tracks:
Expand Down Expand Up @@ -281,3 +342,115 @@ def validate_heatmap_line_width(
raise ParameterError(f"Not three valid TRANSPARENCY,WIDTH pairs: {heatmap_line_width}") from e
return self._heatmap_line_width
return None

def draw_background(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, size: XY, offset: XY) -> None:
"""Draw background with background static map if requested

Args:
dr: svg drawing
g: svg group
size: Size
offset: Offset
"""
super().draw_background(dr, g, size, offset)
if not self._tile_provider:
return

# retrieve static map
bbox = self._determine_bbox()
self._tile_context.set_tile_provider(staticmaps.default_tile_providers[self._tile_provider])
self._tile_context.set_center(bbox.get_center())
# remove padding from poster size to retrieve background image size
size = size - XY(
self.poster.padding["l"] + self.poster.padding["r"], self.poster.padding["t"] + self.poster.padding["b"]
)
offset = offset + XY(self.poster.padding["l"], self.poster.padding["t"])
bg_size = size.scale_to_max_value(self._bg_max_size).round()

# get maximum track line width, scale and add to background image boundary
scale = max([bg_size.x / size.x, bg_size.y / size.y])
self._heatmap_line_width = self.get_line_transparencies_and_widths(bbox)
half_stroke = round(scale * (max(self._heatmap_line_width, key=itemgetter(1))[1] / 2))
bbox_corner_list = [bbox.get_vertex(0), bbox.get_vertex(1), bbox.get_vertex(2), bbox.get_vertex(3)]
bounds = staticmaps.Bounds(bbox_corner_list, half_stroke)
self._tile_context.add_object(bounds)
# tighten the background map to bounds
self._tile_context.set_tighten_to_bounds(True)

# set transformer with center and zoom
center, zoom = self._tile_context.determine_center_zoom(bg_size.x, bg_size.y)
self._transformer = staticmaps.Transformer(
bg_size.x,
bg_size.y,
zoom,
center,
staticmaps.default_tile_providers[self._tile_provider].tile_size(),
)

# TODOX: remove testing code
from staticmaps.color import BLACK, RED # type: ignore

self._tile_context.add_object(staticmaps.Line([bbox.lo(), bbox.hi()], RED, 1))
self._tile_context.add_object(
staticmaps.Line(
[
s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo()),
s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_hi()),
s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_hi()),
s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_lo()),
s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo()),
],
BLACK,
1,
)
)

try:
# generate a unique filename
tmp_file = f"{uuid.uuid4()}.png"

# render background image based on command line argument
if self._heatmap_renderer == "cairo":
try:
__import__("cairo")
except ImportError:
msg = (
"The cairo module cannot be imported. "
"Please consider choosing 'pillow' as background image renderer instead!"
)
sys.exit(msg)
image = self._tile_context.render_cairo(bg_size.x, bg_size.y)
image.write_to_png(tmp_file)
else:
image = self._tile_context.render_pillow(bg_size.x, bg_size.y)
image.save(tmp_file)
with open(tmp_file, "rb") as f:
img_inl = staticmaps.SvgRenderer.create_inline_image(f.read())
dr.add(dr.image(img_inl, insert=(offset.x, offset.y), size=(size.x, size.y)))
os.remove(tmp_file)
except (Image.DecompressionBombError, FileNotFoundError):
print("Something went wrong generating the background image!")

def _get_tracks_size_offset(self, bbox: s2sphere.LatLngRect, size: XY, offset: XY) -> Tuple[XY, XY]:
if not self._tile_provider:
return size, offset

# background image size
bg_size = size.scale_to_max_value(self._bg_max_size)
tracks_scale = size / bg_size

transformer = self._transformer
assert transformer is not None
tracks_width = math.fabs(transformer.ll2pixel(bbox.hi())[0] - transformer.ll2pixel(bbox.lo())[0])
tracks_height = math.fabs(transformer.ll2pixel(bbox.hi())[1] - transformer.ll2pixel(bbox.lo())[1])
# add maximum track line width
assert self._heatmap_line_width
max_stroke = max(self._heatmap_line_width, key=itemgetter(1))[1]
half_stroke = max_stroke / 2
tracks_size = XY(tracks_width, tracks_height) + max_stroke
tracks_size_scaled = tracks_scale * tracks_size
tracks_offset = offset + tracks_scale * (
XY(math.fabs(transformer.ll2pixel(bbox.lo())[0]), math.fabs(transformer.ll2pixel(bbox.hi())[1]))
- half_stroke
)
return tracks_size_scaled, tracks_offset
1 change: 1 addition & 0 deletions gpxtrackposter/localization.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Localization helpers."""

# Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand Down
17 changes: 16 additions & 1 deletion gpxtrackposter/poster.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Create a poster from track data."""

# Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand Down Expand Up @@ -76,6 +77,7 @@ def __init__(self) -> None:
self.special_distance: Dict[str, float] = {"special_distance1": 10, "special_distance2": 20}
self.width: int = 200
self.height: int = 300
self.padding: Dict[str, int] = {"l": 10, "t": 30, "r": 10, "b": 30}
self.years: YearRange = YearRange()
self.tracks_drawer: Optional["TracksDrawer"] = None
self._trans: Optional[Callable[[str], str]] = None
Expand Down Expand Up @@ -220,9 +222,14 @@ def draw(self, drawer: "TracksDrawer", output: str) -> None:
d = svgwrite.Drawing(output, (f"{self.width}mm", f"{self.height}mm"))
d.viewbox(width=self.width, height=self.height)
d.add(d.rect((0, 0), (self.width, self.height), fill=self.colors["background"]))
self._draw_background(d, XY(self.width, self.height), XY(0.0, 0.0))
self._draw_header(d)
self._draw_footer(d)
self._draw_tracks(d, XY(self.width - 20, self.height - 30 - 30), XY(10, 30))
self._draw_tracks(
d,
XY(self.width - self.padding["l"] - self.padding["r"], self.height - self.padding["t"] - self.padding["b"]),
XY(self.padding["l"], self.padding["t"]),
)
d.save()

def m2u(self, m: pint.Quantity) -> float:
Expand Down Expand Up @@ -267,6 +274,14 @@ def _draw_tracks(self, d: svgwrite.Drawing, size: XY, offset: XY) -> None:

self.tracks_drawer.draw(d, g, size, offset)

def _draw_background(self, d: svgwrite.Drawing, size: XY, offset: XY) -> None:
assert self.tracks_drawer

g = d.g(id="background")
d.add(g)

self.tracks_drawer.draw_background(d, g, size, offset)

def _draw_header(self, d: svgwrite.Drawing) -> None:
g = d.g(id="header")
d.add(g)
Expand Down
1 change: 1 addition & 0 deletions gpxtrackposter/quantity_range.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Represent a range of pint quantities"""

# Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand Down
1 change: 1 addition & 0 deletions gpxtrackposter/timezone_adjuster.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""TimezoneAdjuster"""

# Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand Down
1 change: 1 addition & 0 deletions gpxtrackposter/track.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Create and maintain info about a given activity track (corresponding to one GPX file)."""

# Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand Down
12 changes: 12 additions & 0 deletions gpxtrackposter/tracks_drawer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Contains the base class TracksDrawer, which other Drawers inherit from."""

# Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand Down Expand Up @@ -36,6 +37,17 @@ def fetch_args(self, args: argparse.Namespace) -> None:

"""

def draw_background(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, size: XY, offset: XY) -> None:
"""Draw background for all poster types - rectangle with 'background' color

Args:
dr: svg drawing
g: svg group
size: Size
offset: Offset
"""
g.add(dr.rect((offset.x, offset.y), (size.x, size.y), fill=self.poster.colors["background"]))

def draw(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, size: XY, offset: XY) -> None:
"""Draw the circular Poster using distances broken down by time.

Expand Down
1 change: 1 addition & 0 deletions gpxtrackposter/units.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Units"""

# Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand Down
1 change: 1 addition & 0 deletions gpxtrackposter/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Assorted utility methods for use in creating posters."""

# Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand Down
1 change: 1 addition & 0 deletions gpxtrackposter/value_range.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Represent a range of numerical values"""

# Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand Down
1 change: 1 addition & 0 deletions gpxtrackposter/xy.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Represent x,y values with properly overloaded operations."""

# Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved.
#
# Use of this source code is governed by a MIT-style
Expand Down
Loading
Loading