Issue #75: add filter for icons by zoom level.

This commit is contained in:
Sergey Vartanov 2021-08-29 01:50:59 +03:00
parent 9c4873c5ae
commit 39428cad19
13 changed files with 147 additions and 62 deletions

View file

@ -251,21 +251,25 @@ class Constructor:
priority: int priority: int
icon_set: IconSet icon_set: IconSet
icon_set, priority = self.scheme.get_icon( icon_set, priority = self.scheme.get_icon(
self.extractor, line.tags, processed self.extractor,
)
labels: list[Label] = self.scheme.construct_text(
line.tags, "all", processed
)
point: Point = Point(
icon_set,
labels,
line.tags, line.tags,
processed, processed,
center_point, self.configuration.zoom_level,
is_for_node=False, )
priority=priority, if icon_set is not None:
) # fmt: skip labels: list[Label] = self.scheme.construct_text(
self.points.append(point) line.tags, "all", processed
)
point: Point = Point(
icon_set,
labels,
line.tags,
processed,
center_point,
is_for_node=False,
priority=priority,
)
self.points.append(point)
if not line_styles: if not line_styles:
if DEBUG: if DEBUG:
@ -284,16 +288,25 @@ class Constructor:
priority: int priority: int
icon_set: IconSet icon_set: IconSet
icon_set, priority = self.scheme.get_icon( icon_set, priority = self.scheme.get_icon(
self.extractor, line.tags, processed self.extractor,
line.tags,
processed,
self.configuration.zoom_level,
) )
labels: list[Label] = self.scheme.construct_text( if icon_set is not None:
line.tags, "all", processed labels: list[Label] = self.scheme.construct_text(
) line.tags, "all", processed
point: Point = Point( )
icon_set, labels, line.tags, processed, center_point, point: Point = Point(
is_for_node=False, priority=priority, icon_set,
) # fmt: skip labels,
self.points.append(point) line.tags,
processed,
center_point,
is_for_node=False,
priority=priority,
)
self.points.append(point)
def draw_special_mode( def draw_special_mode(
self, self,
@ -385,8 +398,10 @@ class Constructor:
return return
icon_set, priority = self.scheme.get_icon( icon_set, priority = self.scheme.get_icon(
self.extractor, tags, processed self.extractor, tags, processed, self.configuration.zoom_level
) )
if icon_set is None:
return
labels: list[Label] = self.scheme.construct_text(tags, "all", processed) labels: list[Label] = self.scheme.construct_text(tags, "all", processed)
self.scheme.process_ignored(tags, processed) self.scheme.process_ignored(tags, processed)

View file

@ -8,8 +8,8 @@ from typing import Optional, Union
import cairo import cairo
import numpy as np import numpy as np
import svgwrite import svgwrite
from colour import Color
from cairo import Context, ImageSurface from cairo import Context, ImageSurface
from colour import Color
from svgwrite.base import BaseElement from svgwrite.base import BaseElement
from svgwrite.path import Path as SVGPath from svgwrite.path import Path as SVGPath
from svgwrite.shapes import Rect from svgwrite.shapes import Rect

View file

@ -41,7 +41,7 @@ def draw_element(options: argparse.Namespace) -> None:
workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH
) )
processed: set[str] = set() processed: set[str] = set()
icon, priority = scheme.get_icon(extractor, tags, processed) icon, priority = scheme.get_icon(extractor, tags, processed, 18)
is_for_node: bool = target == "node" is_for_node: bool = target == "node"
labels: list[Label] = scheme.construct_text(tags, "all", processed) labels: list[Label] = scheme.construct_text(tags, "all", processed)
point: Point = Point( point: Point = Point(

View file

@ -19,6 +19,9 @@ from roentgen.scheme import LineStyle, RoadMatcher, Scheme
__author__ = "Sergey Vartanov" __author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru" __email__ = "me@enzet.ru"
BUILDING_HEIGHT_SCALE: float = 2.5
BUILDING_MINIMAL_HEIGHT: float = 8.0
class Figure(Tagged): class Figure(Tagged):
""" """
@ -34,13 +37,10 @@ class Figure(Tagged):
super().__init__() super().__init__()
self.tags: dict[str, str] = tags self.tags: dict[str, str] = tags
self.inners: list[list[OSMNode]] = [] self.inners: list[list[OSMNode]] = list(map(make_clockwise, inners))
self.outers: list[list[OSMNode]] = [] self.outers: list[list[OSMNode]] = list(
map(make_counter_clockwise, outers)
for inner_nodes in inners: )
self.inners.append(make_clockwise(inner_nodes))
for outer_nodes in outers:
self.outers.append(make_counter_clockwise(outer_nodes))
def get_path( def get_path(
self, flinger: Flinger, shift: np.ndarray = np.array((0, 0)) self, flinger: Flinger, shift: np.ndarray = np.array((0, 0))
@ -92,16 +92,16 @@ class Building(Figure):
self.parts = sorted(self.parts) self.parts = sorted(self.parts)
self.height: float = 8.0 self.height: float = BUILDING_MINIMAL_HEIGHT
self.min_height: float = 0.0 self.min_height: float = 0.0
levels: Optional[str] = self.get_float("building:levels") levels: Optional[str] = self.get_float("building:levels")
if levels: if levels:
self.height = float(levels) * 2.5 self.height = float(levels) * BUILDING_HEIGHT_SCALE
levels: Optional[str] = self.get_float("building:min_level") levels: Optional[str] = self.get_float("building:min_level")
if levels: if levels:
self.min_height = float(levels) * 2.5 self.min_height = float(levels) * BUILDING_HEIGHT_SCALE
height: Optional[float] = self.get_length("height") height: Optional[float] = self.get_length("height")
if height: if height:

View file

@ -48,6 +48,7 @@ class MapConfiguration:
drawing_mode: DrawingMode = DrawingMode.NORMAL drawing_mode: DrawingMode = DrawingMode.NORMAL
building_mode: BuildingMode = BuildingMode.FLAT building_mode: BuildingMode = BuildingMode.FLAT
label_mode: LabelMode = LabelMode.MAIN label_mode: LabelMode = LabelMode.MAIN
zoom_level: int = 18
overlap: int = 12 overlap: int = 12
level: str = "overground" level: str = "overground"
seed: str = "" seed: str = ""
@ -59,6 +60,7 @@ class MapConfiguration:
DrawingMode(options.mode), DrawingMode(options.mode),
BuildingMode(options.buildings), BuildingMode(options.buildings),
LabelMode(options.label_mode), LabelMode(options.label_mode),
options.zoom,
options.overlap, options.overlap,
options.level, options.level,
options.seed, options.seed,

View file

@ -94,13 +94,19 @@ class Matcher:
Tag matching. Tag matching.
""" """
def __init__(self, structure: dict[str, Any]) -> None: def __init__(
self, structure: dict[str, Any], group: Optional[dict[str, Any]] = None
) -> None:
self.tags: dict[str, str] = structure["tags"] self.tags: dict[str, str] = structure["tags"]
self.exception: dict[str, str] = {} self.exception: dict[str, str] = {}
if "exception" in structure: if "exception" in structure:
self.exception = structure["exception"] self.exception = structure["exception"]
self.start_zoom_level: Optional[int] = None
if group is not None and "start_zoom_level" in group:
self.start_zoom_level = group["start_zoom_level"]
self.replace_shapes: bool = True self.replace_shapes: bool = True
if "replace_shapes" in structure: if "replace_shapes" in structure:
self.replace_shapes = structure["replace_shapes"] self.replace_shapes = structure["replace_shapes"]
@ -109,6 +115,13 @@ class Matcher:
if "location_restrictions" in structure: if "location_restrictions" in structure:
self.location_restrictions = structure["location_restrictions"] self.location_restrictions = structure["location_restrictions"]
def check_zoom_level(self, zoom_level: int):
"""Check whether zoom level is matching."""
return (
self.start_zoom_level is None
or zoom_level >= self.start_zoom_level
)
def is_matched(self, tags: dict[str, str]) -> bool: def is_matched(self, tags: dict[str, str]) -> bool:
""" """
Check whether element tags matches tag matcher. Check whether element tags matches tag matcher.
@ -118,8 +131,6 @@ class Matcher:
if self.location_restrictions: if self.location_restrictions:
return False # FIXME: implement return False # FIXME: implement
matched: bool = True
for config_tag_key in self.tags: for config_tag_key in self.tags:
config_tag_key: str config_tag_key: str
tag_matcher = self.tags[config_tag_key] tag_matcher = self.tags[config_tag_key]
@ -127,8 +138,7 @@ class Matcher:
is_matched_tag(config_tag_key, tag_matcher, tags) is_matched_tag(config_tag_key, tag_matcher, tags)
== MatchingType.NOT_MATCHED == MatchingType.NOT_MATCHED
): ):
matched = False return False
break
if self.exception: if self.exception:
for config_tag_key in self.exception: for config_tag_key in self.exception:
@ -138,10 +148,9 @@ class Matcher:
is_matched_tag(config_tag_key, tag_matcher, tags) is_matched_tag(config_tag_key, tag_matcher, tags)
!= MatchingType.NOT_MATCHED != MatchingType.NOT_MATCHED
): ):
matched = False return False
break
return matched return True
def get_mapcss_selector(self, prefix: str = "") -> str: def get_mapcss_selector(self, prefix: str = "") -> str:
""" """
@ -167,9 +176,11 @@ class NodeMatcher(Matcher):
Tag specification matcher. Tag specification matcher.
""" """
def __init__(self, structure: dict[str, Any]) -> None: def __init__(
self, structure: dict[str, Any], group: dict[str, Any]
) -> None:
# Dictionary with tag keys and values, value lists, or "*" # Dictionary with tag keys and values, value lists, or "*"
super().__init__(structure) super().__init__(structure, group)
self.draw: bool = True self.draw: bool = True
if "draw" in structure: if "draw" in structure:
@ -268,7 +279,7 @@ class Scheme:
self.node_matchers: list[NodeMatcher] = [] self.node_matchers: list[NodeMatcher] = []
for group in content["node_icons"]: for group in content["node_icons"]:
for element in group["tags"]: for element in group["tags"]:
self.node_matchers.append(NodeMatcher(element)) self.node_matchers.append(NodeMatcher(element, group))
self.colors: dict[str, str] = content["colors"] self.colors: dict[str, str] = content["colors"]
self.material_colors: dict[str, str] = content["material_colors"] self.material_colors: dict[str, str] = content["material_colors"]
@ -341,13 +352,15 @@ class Scheme:
extractor: ShapeExtractor, extractor: ShapeExtractor,
tags: dict[str, Any], tags: dict[str, Any],
processed: set[str], processed: set[str],
) -> tuple[IconSet, int]: zoom_level: int,
) -> tuple[Optional[IconSet], int]:
""" """
Construct icon set. Construct icon set.
:param extractor: extractor with icon specifications :param extractor: extractor with icon specifications
:param tags: OpenStreetMap element tags dictionary :param tags: OpenStreetMap element tags dictionary
:param processed: set of already processed tag keys :param processed: set of already processed tag keys
:param zoom_level: zoom level in current context
:return (icon set, icon priority) :return (icon set, icon priority)
""" """
tags_hash: str = ( tags_hash: str = (
@ -365,9 +378,10 @@ class Scheme:
for matcher in self.node_matchers: for matcher in self.node_matchers:
if not matcher.replace_shapes and main_icon: if not matcher.replace_shapes and main_icon:
continue continue
matched: bool = matcher.is_matched(tags) if not matcher.is_matched(tags):
if not matched:
continue continue
if not matcher.check_zoom_level(zoom_level):
return None, 0
matcher_tags: set[str] = set(matcher.tags.keys()) matcher_tags: set[str] = set(matcher.tags.keys())
priority = len(self.node_matchers) - index priority = len(self.node_matchers) - index
if not matcher.draw: if not matcher.draw:

View file

@ -42,14 +42,14 @@ class _Handler(SimpleHTTPRequestHandler):
zoom_level: int = int(parts[2]) zoom_level: int = int(parts[2])
x: int = int(parts[3]) x: int = int(parts[3])
y: int = int(parts[4]) y: int = int(parts[4])
tile: Tile = Tile(x, y, zoom_level)
tile_path: Path = workspace.get_tile_path() tile_path: Path = workspace.get_tile_path()
png_path: Path = tile_path / f"tile_{zoom_level}_{x}_{y}.png" svg_path: Path = tile.get_file_name(tile_path)
png_path: Path = svg_path.with_suffix(".png")
if self.update_cache: if self.update_cache:
svg_path: Path = png_path.with_suffix(".svg")
if not png_path.exists(): if not png_path.exists():
if not svg_path.exists(): if not svg_path.exists():
tile = Tile(x, y, zoom_level)
tile.draw(tile_path, self.cache, self.options) tile.draw(tile_path, self.cache, self.options)
with svg_path.open() as input_file: with svg_path.open() as input_file:
cairosvg.svg2png( cairosvg.svg2png(

View file

@ -9,6 +9,7 @@ from colour import Color
__author__ = "Sergey Vartanov" __author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru" __email__ = "me@enzet.ru"
DEFAULT_FONT_SIZE: float = 10.0
DEFAULT_COLOR: Color = Color("#444444") DEFAULT_COLOR: Color = Color("#444444")
@ -20,7 +21,7 @@ class Label:
text: str text: str
fill: Color = DEFAULT_COLOR fill: Color = DEFAULT_COLOR
size: float = 10.0 size: float = DEFAULT_FONT_SIZE
def get_address( def get_address(

View file

@ -15,15 +15,15 @@ import numpy as np
import svgwrite import svgwrite
from PIL import Image from PIL import Image
from roentgen.boundary_box import BoundaryBox
from roentgen.constructor import Constructor from roentgen.constructor import Constructor
from roentgen.flinger import Flinger from roentgen.flinger import Flinger
from roentgen.icon import ShapeExtractor from roentgen.icon import ShapeExtractor
from roentgen.mapper import Map
from roentgen.map_configuration import MapConfiguration from roentgen.map_configuration import MapConfiguration
from roentgen.mapper import Map
from roentgen.osm_getter import NetworkError, get_osm from roentgen.osm_getter import NetworkError, get_osm
from roentgen.osm_reader import OSMData, OSMReader from roentgen.osm_reader import OSMData, OSMReader
from roentgen.scheme import Scheme from roentgen.scheme import Scheme
from roentgen.boundary_box import BoundaryBox
from roentgen.workspace import workspace from roentgen.workspace import workspace
__author__ = "Sergey Vartanov" __author__ = "Sergey Vartanov"
@ -398,8 +398,6 @@ def parse_zoom_level(zoom_level_specification: str) -> list[int]:
def parse(zoom_level: str) -> int: def parse(zoom_level: str) -> int:
"""Parse zoom level.""" """Parse zoom level."""
parsed_zoom_level: int = int(zoom_level) parsed_zoom_level: int = int(zoom_level)
if parsed_zoom_level <= 0:
raise ScaleConfigurationException("Non positive zoom level.")
if parsed_zoom_level > 20: if parsed_zoom_level > 20:
raise ScaleConfigurationException("Scale is too big.") raise ScaleConfigurationException("Scale is too big.")
return parsed_zoom_level return parsed_zoom_level

View file

@ -97,7 +97,8 @@ node_icons:
- tags: {building: "yes"} - tags: {building: "yes"}
draw: false draw: false
- group: "Transport hubs" - group: "Huge transport hubs"
start_zoom_level: 10
tags: tags:
- tags: {amenity: ferry_terminal} - tags: {amenity: ferry_terminal}
shapes: [anchor] shapes: [anchor]
@ -111,6 +112,10 @@ node_icons:
shapes: [h] shapes: [h]
- tags: {aeroway: spaceport} - tags: {aeroway: spaceport}
shapes: [rocket_on_launch_pad] shapes: [rocket_on_launch_pad]
- group: "Normal transport hubs"
start_zoom_level: 11
tags:
- tags: {aeroway: launchpad} - tags: {aeroway: launchpad}
shapes: [rocket_flying] shapes: [rocket_flying]
- tags: {aeroway: landingpad} - tags: {aeroway: landingpad}
@ -157,6 +162,7 @@ node_icons:
shapes: [taxi] shapes: [taxi]
- group: "Big territory" - group: "Big territory"
start_zoom_level: 12
tags: tags:
- tags: {leisure: fishing} - tags: {leisure: fishing}
shapes: [fishing_angle] shapes: [fishing_angle]
@ -198,6 +204,7 @@ node_icons:
shapes: [{shape: pear, color: orchard_border_color}] shapes: [{shape: pear, color: orchard_border_color}]
- group: "Bigger objects" - group: "Bigger objects"
start_zoom_level: 13
tags: tags:
- tags: {waterway: waterfall} - tags: {waterway: waterfall}
shapes: [{shape: waterfall, color: water_border_color}] shapes: [{shape: waterfall, color: water_border_color}]
@ -281,6 +288,7 @@ node_icons:
shapes: [slide_and_water] shapes: [slide_and_water]
- group: "Important big objects" - group: "Important big objects"
start_zoom_level: 14
tags: tags:
- tags: {amenity: pharmacy} - tags: {amenity: pharmacy}
shapes: [medicine_bottle] shapes: [medicine_bottle]
@ -349,6 +357,7 @@ node_icons:
location_restrictions: {include: jp} location_restrictions: {include: jp}
- group: "Normal big objects" - group: "Normal big objects"
start_zoom_level: 15
tags: tags:
- tags: {shop: supermarket} - tags: {shop: supermarket}
shapes: [supermarket_cart] shapes: [supermarket_cart]
@ -529,6 +538,7 @@ node_icons:
shapes: [table] shapes: [table]
- group: "Big objects not for all" - group: "Big objects not for all"
start_zoom_level: 15
tags: tags:
- tags: {building: apartments} - tags: {building: apartments}
shapes: [apartments] shapes: [apartments]
@ -551,6 +561,7 @@ node_icons:
shapes: [telephone] shapes: [telephone]
- group: "Not important big objects" - group: "Not important big objects"
start_zoom_level: 15
tags: tags:
- tags: {man_made: communications_tower} - tags: {man_made: communications_tower}
location_restrictions: {include: jp} location_restrictions: {include: jp}
@ -571,6 +582,7 @@ node_icons:
shapes: [garages] shapes: [garages]
- group: "Emergency" - group: "Emergency"
start_zoom_level: 15
tags: tags:
- tags: {emergency: defibrillator} - tags: {emergency: defibrillator}
shapes: [{shape: defibrillator, color: emergency_color}] shapes: [{shape: defibrillator, color: emergency_color}]
@ -584,6 +596,7 @@ node_icons:
shapes: [{shape: sos_phone, color: emergency_color}] shapes: [{shape: sos_phone, color: emergency_color}]
- group: "Transport-important middle objects" - group: "Transport-important middle objects"
start_zoom_level: 16
tags: tags:
- tags: {ford: "yes"} - tags: {ford: "yes"}
shapes: [ford] shapes: [ford]
@ -625,6 +638,7 @@ node_icons:
shapes: [bump] shapes: [bump]
- group: "Important middle objects" - group: "Important middle objects"
start_zoom_level: 16
tags: tags:
- tags: {tourism: attraction, attraction: amusement_ride} - tags: {tourism: attraction, attraction: amusement_ride}
shapes: [amusement_ride] shapes: [amusement_ride]
@ -634,6 +648,7 @@ node_icons:
shapes: [shelter] shapes: [shelter]
- group: "Normal middle objects" - group: "Normal middle objects"
start_zoom_level: 17
tags: tags:
- tags: {shop: kiosk} - tags: {shop: kiosk}
shapes: [kiosk] shapes: [kiosk]
@ -653,6 +668,7 @@ node_icons:
shapes: [pipeline] shapes: [pipeline]
- group: "Not important middle objects" - group: "Not important middle objects"
start_zoom_level: 17
tags: tags:
- tags: {building: ventilation_shaft} - tags: {building: ventilation_shaft}
shapes: [ventilation] shapes: [ventilation]
@ -915,6 +931,7 @@ node_icons:
add_shapes: [phone] add_shapes: [phone]
- group: "Important small objects" - group: "Important small objects"
start_zoom_level: 17
tags: tags:
- tags: {historic: cannon} - tags: {historic: cannon}
shapes: [cannon] shapes: [cannon]
@ -999,6 +1016,7 @@ node_icons:
shapes: {christmas_tree} shapes: {christmas_tree}
- group: "Normal small objects" - group: "Normal small objects"
start_zoom_level: 18
tags: tags:
- tags: {amenity: binoculars} - tags: {amenity: binoculars}
shapes: [binoculars_on_pole] shapes: [binoculars_on_pole]
@ -1044,6 +1062,7 @@ node_icons:
shapes: [golf_pin] shapes: [golf_pin]
- group: "Entrances" - group: "Entrances"
start_zoom_level: 18
tags: tags:
- tags: {amenity: parking_entrance} - tags: {amenity: parking_entrance}
shapes: shapes:
@ -1073,6 +1092,7 @@ node_icons:
shapes: [no_door] shapes: [no_door]
- group: "Not important small objects" - group: "Not important small objects"
start_zoom_level: 18
tags: tags:
- tags: {amenity: bench} - tags: {amenity: bench}
shapes: [bench] shapes: [bench]
@ -1228,6 +1248,7 @@ node_icons:
shapes: [bollard] shapes: [bollard]
- group: "Indoor" - group: "Indoor"
start_zoom_level: 18
tags: tags:
- tags: {door: "yes"} - tags: {door: "yes"}
shapes: [entrance] shapes: [entrance]

View file

@ -24,7 +24,7 @@ def test_grid(init_collection: IconCollection) -> None:
def test_icons_by_id(init_collection: IconCollection) -> None: def test_icons_by_id(init_collection: IconCollection) -> None:
"""Test individual icons drawing.""" """Test individual icons drawing."""
init_collection.draw_icons(workspace.get_icons_by_id_path(), by_name=False) init_collection.draw_icons(workspace.get_icons_by_id_path())
def test_icons_by_name(init_collection: IconCollection) -> None: def test_icons_by_name(init_collection: IconCollection) -> None:
@ -35,7 +35,7 @@ def test_icons_by_name(init_collection: IconCollection) -> None:
def get_icon(tags: dict[str, str]) -> IconSet: def get_icon(tags: dict[str, str]) -> IconSet:
"""Construct icon from tags.""" """Construct icon from tags."""
processed: set[str] = set() processed: set[str] = set()
icon, _ = SCHEME.get_icon(SHAPE_EXTRACTOR, tags, processed) icon, _ = SCHEME.get_icon(SHAPE_EXTRACTOR, tags, processed, 18)
return icon return icon

View file

@ -13,7 +13,7 @@ def test_mapcss() -> None:
"""Test MapCSS generation.""" """Test MapCSS generation."""
writer: MapCSSWriter = MapCSSWriter(SCHEME, "icons") writer: MapCSSWriter = MapCSSWriter(SCHEME, "icons")
matcher: NodeMatcher = NodeMatcher( matcher: NodeMatcher = NodeMatcher(
{"tags": {"natural": "tree"}, "shapes": ["tree"]} {"tags": {"natural": "tree"}, "shapes": ["tree"]}, {}
) )
selector = writer.add_selector("node", matcher) selector = writer.add_selector("node", matcher)
assert ( assert (

View file

@ -1,23 +1,57 @@
""" """
Test zoom level specification parsing. Test zoom level specification parsing.
""" """
from roentgen.tile import parse_zoom_level from roentgen.tile import ScaleConfigurationException, parse_zoom_level
def test_zoom_level_1() -> None: def test_zoom_level_1() -> None:
"""Test one zoom level."""
assert parse_zoom_level("18") == [18] assert parse_zoom_level("18") == [18]
def test_zoom_level_list() -> None: def test_zoom_level_list() -> None:
"""Test list of zoom levels."""
assert parse_zoom_level("17,18") == [17, 18] assert parse_zoom_level("17,18") == [17, 18]
assert parse_zoom_level("16,17,18") == [16, 17, 18] assert parse_zoom_level("16,17,18") == [16, 17, 18]
def test_zoom_level_range() -> None: def test_zoom_level_range() -> None:
"""Test range of zoom levels."""
assert parse_zoom_level("16-18") == [16, 17, 18] assert parse_zoom_level("16-18") == [16, 17, 18]
assert parse_zoom_level("18-18") == [18] assert parse_zoom_level("18-18") == [18]
def test_zoom_level_mixed() -> None: def test_zoom_level_mixed() -> None:
"""Test zoom level specification with list of numbers and ranges."""
assert parse_zoom_level("15,16-18") == [15, 16, 17, 18] assert parse_zoom_level("15,16-18") == [15, 16, 17, 18]
assert parse_zoom_level("15,16-18,20") == [15, 16, 17, 18, 20] assert parse_zoom_level("15,16-18,20") == [15, 16, 17, 18, 20]
def test_zoom_level_too_big() -> None:
"""Test too big zoom level."""
try:
parse_zoom_level("21")
except ScaleConfigurationException:
return
assert False
def test_zoom_level_negative() -> None:
"""Test negative zoom level."""
try:
parse_zoom_level("-1")
except ValueError:
return
assert False
def test_zoom_level_wrong() -> None:
"""Test too big zoom level."""
try:
parse_zoom_level(",")
except ValueError:
return
assert False