diff --git a/Makefile b/Makefile index 2711313..9a8783b 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/gpxtrackposter/calendar_drawer.py b/gpxtrackposter/calendar_drawer.py index fe549df..a8a521d 100644 --- a/gpxtrackposter/calendar_drawer.py +++ b/gpxtrackposter/calendar_drawer.py @@ -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 diff --git a/gpxtrackposter/circular_drawer.py b/gpxtrackposter/circular_drawer.py index 46b394a..a61848b 100644 --- a/gpxtrackposter/circular_drawer.py +++ b/gpxtrackposter/circular_drawer.py @@ -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 diff --git a/gpxtrackposter/exceptions.py b/gpxtrackposter/exceptions.py index 7b2cad3..cb8fd53 100644 --- a/gpxtrackposter/exceptions.py +++ b/gpxtrackposter/exceptions.py @@ -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 diff --git a/gpxtrackposter/github_drawer.py b/gpxtrackposter/github_drawer.py index 0f2916f..4cfc5ee 100644 --- a/gpxtrackposter/github_drawer.py +++ b/gpxtrackposter/github_drawer.py @@ -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 diff --git a/gpxtrackposter/grid_drawer.py b/gpxtrackposter/grid_drawer.py index f9e9ad2..6843dd9 100644 --- a/gpxtrackposter/grid_drawer.py +++ b/gpxtrackposter/grid_drawer.py @@ -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 diff --git a/gpxtrackposter/heatmap_drawer.py b/gpxtrackposter/heatmap_drawer.py index 59ce156..fae3050 100644 --- a/gpxtrackposter/heatmap_drawer.py +++ b/gpxtrackposter/heatmap_drawer.py @@ -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 @@ -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 @@ -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): @@ -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 @@ -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. @@ -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 @@ -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: @@ -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 diff --git a/gpxtrackposter/localization.py b/gpxtrackposter/localization.py index ce8125d..098fafe 100644 --- a/gpxtrackposter/localization.py +++ b/gpxtrackposter/localization.py @@ -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 diff --git a/gpxtrackposter/poster.py b/gpxtrackposter/poster.py index 9f6fcdf..5063f2b 100644 --- a/gpxtrackposter/poster.py +++ b/gpxtrackposter/poster.py @@ -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 @@ -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 @@ -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: @@ -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) diff --git a/gpxtrackposter/quantity_range.py b/gpxtrackposter/quantity_range.py index 5b6fe4f..762c053 100644 --- a/gpxtrackposter/quantity_range.py +++ b/gpxtrackposter/quantity_range.py @@ -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 diff --git a/gpxtrackposter/timezone_adjuster.py b/gpxtrackposter/timezone_adjuster.py index e75101b..00057c1 100644 --- a/gpxtrackposter/timezone_adjuster.py +++ b/gpxtrackposter/timezone_adjuster.py @@ -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 diff --git a/gpxtrackposter/track.py b/gpxtrackposter/track.py index de08780..fa13a33 100644 --- a/gpxtrackposter/track.py +++ b/gpxtrackposter/track.py @@ -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 diff --git a/gpxtrackposter/tracks_drawer.py b/gpxtrackposter/tracks_drawer.py index 145e9f8..160a1bf 100644 --- a/gpxtrackposter/tracks_drawer.py +++ b/gpxtrackposter/tracks_drawer.py @@ -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 @@ -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. diff --git a/gpxtrackposter/units.py b/gpxtrackposter/units.py index e8628c7..b184aaa 100644 --- a/gpxtrackposter/units.py +++ b/gpxtrackposter/units.py @@ -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 diff --git a/gpxtrackposter/utils.py b/gpxtrackposter/utils.py index e6269e8..713ab10 100644 --- a/gpxtrackposter/utils.py +++ b/gpxtrackposter/utils.py @@ -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 diff --git a/gpxtrackposter/value_range.py b/gpxtrackposter/value_range.py index f82df7a..79352c7 100644 --- a/gpxtrackposter/value_range.py +++ b/gpxtrackposter/value_range.py @@ -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 diff --git a/gpxtrackposter/xy.py b/gpxtrackposter/xy.py index bb09670..af00100 100644 --- a/gpxtrackposter/xy.py +++ b/gpxtrackposter/xy.py @@ -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 diff --git a/gpxtrackposter/year_range.py b/gpxtrackposter/year_range.py index 1637825..e283012 100644 --- a/gpxtrackposter/year_range.py +++ b/gpxtrackposter/year_range.py @@ -1,4 +1,5 @@ """Represent a range of years, with ability to update based on a track""" + # Copyright 2016-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style @@ -123,5 +124,4 @@ def iter(self) -> Generator[int, None, None]: return assert self.to_year is not None - for year in range(self.from_year, self.to_year + 1): - yield year + yield from range(self.from_year, self.to_year + 1) diff --git a/requirements-dev.txt b/requirements-dev.txt index 559b37b..afb2307 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -32,3 +32,4 @@ Pygments python-dateutil types-python-dateutil types-pytz +types-setuptools diff --git a/setup.py b/setup.py index 55bd102..246d8c2 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,11 @@ def _read_reqs(relpath: str) -> list: """Read requirements""" fullpath = os.path.join(os.path.dirname(__file__), relpath) with open(fullpath, encoding="utf-8") as f: - return [s.strip() for s in f.readlines() if (s.strip() and not s.startswith("#") and not s.startswith("--"))] + return [ + s.strip() + for s in f.readlines() + if (s.strip() and not s.startswith("#") and not s.startswith("--") and not s.startswith("git+https")) + ] setuptools.setup( diff --git a/tests/conftest.py b/tests/conftest.py index 6629c6c..1e9210f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ """ ConfTest """ + # Copyright 2022-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/tests/test_calendar_drawer.py b/tests/test_calendar_drawer.py index 0a24bdd..c2ca5ab 100644 --- a/tests/test_calendar_drawer.py +++ b/tests/test_calendar_drawer.py @@ -1,6 +1,7 @@ """ Several tests for CalendarDrawer """ + # Copyright 2022-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/tests/test_circular_drawer.py b/tests/test_circular_drawer.py index 91d15be..fd453c4 100644 --- a/tests/test_circular_drawer.py +++ b/tests/test_circular_drawer.py @@ -1,6 +1,7 @@ """ Several tests for CircularDrawer """ + # Copyright 2021-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/tests/test_cli.py b/tests/test_cli.py index d5be56b..fa90fd6 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,6 +1,7 @@ """ Several tests for entry point cli.py """ + # Copyright 2021-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/tests/test_github_drawer.py b/tests/test_github_drawer.py index db66837..306ce45 100644 --- a/tests/test_github_drawer.py +++ b/tests/test_github_drawer.py @@ -1,6 +1,7 @@ """ Several tests for GithubDrawer """ + # Copyright 2022-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/tests/test_grid_drawer.py b/tests/test_grid_drawer.py index fab9bd4..879b628 100644 --- a/tests/test_grid_drawer.py +++ b/tests/test_grid_drawer.py @@ -1,6 +1,7 @@ """ Several tests for GridDrawer """ + # Copyright 2022-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/tests/test_heatmap_drawer.py b/tests/test_heatmap_drawer.py index 416440d..0e2bb65 100644 --- a/tests/test_heatmap_drawer.py +++ b/tests/test_heatmap_drawer.py @@ -1,6 +1,7 @@ """ Several tests for HeatmapDrawer """ + # Copyright 2021-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/tests/test_localization.py b/tests/test_localization.py index 9504247..5afb502 100644 --- a/tests/test_localization.py +++ b/tests/test_localization.py @@ -1,6 +1,7 @@ """ Several tests for Localization """ + # Copyright 2021-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/tests/test_quantity_range.py b/tests/test_quantity_range.py index d2a1296..c539ca7 100644 --- a/tests/test_quantity_range.py +++ b/tests/test_quantity_range.py @@ -1,6 +1,7 @@ """ Several tests for QuantityRange """ + # Copyright 2021-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/tests/test_timezone_adjuster.py b/tests/test_timezone_adjuster.py index 7f1e1ec..06c606a 100644 --- a/tests/test_timezone_adjuster.py +++ b/tests/test_timezone_adjuster.py @@ -1,6 +1,7 @@ """ Several tests for TimezoneAdjuster """ + # Copyright 2020-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/tests/test_track.py b/tests/test_track.py index 393b35f..917d522 100644 --- a/tests/test_track.py +++ b/tests/test_track.py @@ -1,6 +1,7 @@ """ Several tests for Track """ + # Copyright 2022-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/tests/test_track_loader.py b/tests/test_track_loader.py index 9cc277f..fe99076 100644 --- a/tests/test_track_loader.py +++ b/tests/test_track_loader.py @@ -1,6 +1,7 @@ """ Several tests for TrackLoader """ + # Copyright 2020-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/tests/test_utils.py b/tests/test_utils.py index b8b89f6..1c6b857 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,7 @@ """ Several tests for utils """ + # Copyright 2018-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/tests/test_value_range.py b/tests/test_value_range.py index 35074f2..5a43fcc 100644 --- a/tests/test_value_range.py +++ b/tests/test_value_range.py @@ -1,6 +1,7 @@ """ Several tests for ValueRange """ + # Copyright 2021-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/tests/test_xy.py b/tests/test_xy.py index 0b99b5d..2e7d3b5 100644 --- a/tests/test_xy.py +++ b/tests/test_xy.py @@ -1,6 +1,7 @@ """ Several tests for XY """ + # Copyright 2021-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/tests/test_year_range.py b/tests/test_year_range.py index 17c604d..0744e48 100644 --- a/tests/test_year_range.py +++ b/tests/test_year_range.py @@ -1,6 +1,7 @@ """ Several tests for YearRange """ + # Copyright 2021-2023 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style