Fix floating point numbers.

This commit is contained in:
Sergey Vartanov 2021-11-11 23:52:46 +03:00
parent 6e20668e24
commit ed0e0384b7
23 changed files with 358 additions and 332 deletions

View file

@ -35,7 +35,7 @@ def get_gradient_color(
scale: list[Color] = colors + [Color("black")] scale: list[Color] = colors + [Color("black")]
range_coefficient: float = ( range_coefficient: float = (
0 if bounds.is_empty() else (value - bounds.min_) / bounds.delta() 0.0 if bounds.is_empty() else (value - bounds.min_) / bounds.delta()
) )
# If value is out of range, set it to boundary value. # If value is out of range, set it to boundary value.
range_coefficient = min(1.0, max(0.0, range_coefficient)) range_coefficient = min(1.0, max(0.0, range_coefficient))

View file

@ -189,7 +189,7 @@ class Constructor:
self.craters: list[Crater] = [] self.craters: list[Crater] = []
self.direction_sectors: list[DirectionSector] = [] self.direction_sectors: list[DirectionSector] = []
self.heights: set[float] = {2, 4} self.heights: set[float] = {2.0, 4.0}
def add_building(self, building: Building) -> None: def add_building(self, building: Building) -> None:
"""Add building and update levels.""" """Add building and update levels."""
@ -327,10 +327,10 @@ class Constructor:
style: dict[str, Any] = { style: dict[str, Any] = {
"fill": "none", "fill": "none",
"stroke": Color("red").hex, "stroke": Color("red").hex,
"stroke-width": 1, "stroke-width": 1.0,
} }
figure: StyledFigure = StyledFigure( figure: StyledFigure = StyledFigure(
line.tags, inners, outers, LineStyle(style, 1000) line.tags, inners, outers, LineStyle(style, 1000.0)
) )
self.figures.append(figure) self.figures.append(figure)
@ -453,6 +453,7 @@ class Constructor:
) )
if icon_set is None: if icon_set is None:
return return
labels: list[Label] = self.scheme.construct_text( labels: list[Label] = self.scheme.construct_text(
tags, processed, self.configuration.label_mode tags, processed, self.configuration.label_mode
) )
@ -498,7 +499,7 @@ def check_level_overground(tags: Tags) -> bool:
if "level" in tags: if "level" in tags:
try: try:
for level in map(float, tags["level"].replace(",", ".").split(";")): for level in map(float, tags["level"].replace(",", ".").split(";")):
if level < 0: if level < 0.0:
return False return False
except ValueError: except ValueError:
pass pass

View file

@ -206,7 +206,7 @@ class MapMachineHTML(MapMachineMoire, DefaultHTML):
def icon(self, arg: Arguments) -> str: def icon(self, arg: Arguments) -> str:
"""Image with Röntgen icon.""" """Image with Röntgen icon."""
size: str = self.clear(arg[1]) if len(arg) > 1 else 16 size: str = self.clear(arg[1]) if len(arg) > 1 else "16"
return ( return (
f'<img class="icon" style="width: {size}px; height: {size}px;" ' f'<img class="icon" style="width: {size}px; height: {size}px;" '
f'src="out/icons_by_id/{self.clear(arg[0])}.svg" />' f'src="out/icons_by_id/{self.clear(arg[0])}.svg" />'
@ -261,7 +261,7 @@ class MapMachineOSMWiki(MapMachineMoire, DefaultWiki):
def icon(self, arg: Arguments) -> str: def icon(self, arg: Arguments) -> str:
"""Image with Röntgen icon.""" """Image with Röntgen icon."""
size: str = self.clear(arg[1]) if len(arg) > 1 else 16 size: str = self.clear(arg[1]) if len(arg) > 1 else "16"
shape_id: str = self.clear(arg[0]) shape_id: str = self.clear(arg[0])
name: str = self.extractor.get_shape(shape_id).name name: str = self.extractor.get_shape(shape_id).name
return f"[[File:Röntgen {name}.svg|{size}px]]" return f"[[File:Röntgen {name}.svg|{size}px]]"

View file

@ -27,7 +27,7 @@ class Style:
fill: Optional[Color] = None fill: Optional[Color] = None
stroke: Optional[Color] = None stroke: Optional[Color] = None
width: float = 1 width: float = 1.0
def update_svg_element(self, element: BaseElement) -> None: def update_svg_element(self, element: BaseElement) -> None:
"""Set style for SVG element.""" """Set style for SVG element."""
@ -41,7 +41,7 @@ class Style:
def draw_png_fill(self, context: Context) -> None: def draw_png_fill(self, context: Context) -> None:
"""Set style for context and draw fill.""" """Set style for context and draw fill."""
context.set_source_rgba( context.set_source_rgba(
self.fill.get_red(), self.fill.get_green(), self.fill.get_blue(), 1 self.fill.get_red(), self.fill.get_green(), self.fill.get_blue()
) )
context.fill() context.fill()
@ -51,7 +51,6 @@ class Style:
self.stroke.get_red(), self.stroke.get_red(),
self.stroke.get_green(), self.stroke.get_green(),
self.stroke.get_blue(), self.stroke.get_blue(),
1,
) )
context.set_line_width(self.width) context.set_line_width(self.width)
context.stroke() context.stroke()
@ -176,7 +175,7 @@ class PNGDrawing(Drawing):
def _do_path(self, commands: PathCommands) -> None: def _do_path(self, commands: PathCommands) -> None:
"""Draw path.""" """Draw path."""
current: np.ndarray = np.array((0, 0)) current: np.ndarray = np.array((0.0, 0.0))
start_point: Optional[np.ndarray] = None start_point: Optional[np.ndarray] = None
command: str = "M" command: str = "M"
is_absolute: bool = True is_absolute: bool = True
@ -231,14 +230,14 @@ class PNGDrawing(Drawing):
point: np.ndarray point: np.ndarray
if is_absolute: if is_absolute:
if command == "v": if command == "v":
point = np.array((0, commands[index])) point = np.array((0.0, commands[index]))
else: else:
point = np.array((commands[index], 0)) point = np.array((commands[index], 0.0))
else: else:
if command == "v": if command == "v":
point = current + np.array((0, commands[index])) point = current + np.array((0.0, commands[index]))
else: else:
point = current + np.array((commands[index], 0)) point = current + np.array((commands[index], 0.0))
current = point current = point
self.context.line_to(point[0], point[1]) self.context.line_to(point[0], point[1])
if start_point is None: if start_point is None:

View file

@ -51,13 +51,13 @@ def draw_element(options: argparse.Namespace) -> None:
labels, labels,
tags, tags,
processed, processed,
np.array((32, 32)), np.array((32.0, 32.0)),
is_for_node=is_for_node, is_for_node=is_for_node,
draw_outline=is_for_node, draw_outline=is_for_node,
) )
border: np.ndarray = np.array((16, 16)) border: np.ndarray = np.array((16.0, 16.0))
size: np.ndarray = point.get_size() + border size: np.ndarray = point.get_size() + border
point.point = np.array((size[0] / 2, 16 / 2 + border[1] / 2)) point.point = np.array((size[0] / 2.0, 16.0 / 2.0 + border[1] / 2.0))
output_file_path: Path = workspace.output_path / "element.svg" output_file_path: Path = workspace.output_path / "element.svg"
svg: svgwrite.Drawing = svgwrite.Drawing( svg: svgwrite.Drawing = svgwrite.Drawing(

View file

@ -11,9 +11,9 @@ from map_machine.drawing import PathCommands
__author__ = "Sergey Vartanov" __author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru" __email__ = "me@enzet.ru"
SHIFT: float = -np.pi / 2 SHIFT: float = -np.pi / 2.0
SMALLEST_ANGLE: float = np.pi / 15 SMALLEST_ANGLE: float = np.pi / 15.0
DEFAULT_ANGLE: float = np.pi / 30 DEFAULT_ANGLE: float = np.pi / 30.0
def parse_vector(text: str) -> Optional[np.ndarray]: def parse_vector(text: str) -> Optional[np.ndarray]:
@ -66,13 +66,13 @@ class Sector:
parts: list[str] = text.split("-") parts: list[str] = text.split("-")
self.start = parse_vector(parts[0]) self.start = parse_vector(parts[0])
self.end = parse_vector(parts[1]) self.end = parse_vector(parts[1])
self.main_direction = (self.start + self.end) / 2 self.main_direction = (self.start + self.end) / 2.0
else: else:
result_angle: float result_angle: float
if angle is None: if angle is None:
result_angle = DEFAULT_ANGLE result_angle = DEFAULT_ANGLE
else: else:
result_angle = max(SMALLEST_ANGLE, np.radians(angle) / 2) result_angle = max(SMALLEST_ANGLE, np.radians(angle) / 2.0)
vector: Optional[np.ndarray] = parse_vector(text) vector: Optional[np.ndarray] = parse_vector(text)
self.main_direction = vector self.main_direction = vector
@ -105,9 +105,9 @@ class Sector:
None otherwise. None otherwise.
""" """
if self.main_direction is not None: if self.main_direction is not None:
if np.allclose(self.main_direction[0], 0): if np.allclose(self.main_direction[0], 0.0):
return None return None
if self.main_direction[0] > 0: if self.main_direction[0] > 0.0:
return True return True
return False return False

View file

@ -435,7 +435,7 @@ class Road(Tagged):
lane.get_width(self.scale) lane.get_width(self.scale)
for lane in self.lanes[: lane_number - 1] for lane in self.lanes[: lane_number - 1]
) )
- self.width * self.scale / 2 - self.width * self.scale / 2.0
) )
if place == "left_of": if place == "left_of":
pass pass
@ -525,7 +525,7 @@ class Road(Tagged):
"""Get road main color.""" """Get road main color."""
color: Color = self.matcher.color color: Color = self.matcher.color
if self.tags.get("tunnel") == "yes": if self.tags.get("tunnel") == "yes":
color = Color(color, luminance=min(1, color.luminance + 0.2)) color = Color(color, luminance=min(1.0, color.luminance + 0.2))
return color return color
def get_border_color(self) -> Color: def get_border_color(self) -> Color:
@ -546,7 +546,7 @@ class Road(Tagged):
for index in range(1, len(self.lanes)): for index in range(1, len(self.lanes)):
lane_offset: float = self.scale * ( lane_offset: float = self.scale * (
-self.width / 2 + index * self.width / len(self.lanes) -self.width / 2.0 + index * self.width / len(self.lanes)
) )
path: Path = Path( path: Path = Path(
d=self.line.get_path(self.placement_offset + lane_offset) d=self.line.get_path(self.placement_offset + lane_offset)
@ -555,7 +555,7 @@ class Road(Tagged):
"fill": "none", "fill": "none",
"stroke": color.hex, "stroke": color.hex,
"stroke-linejoin": "round", "stroke-linejoin": "round",
"stroke-width": 1, "stroke-width": 1.0,
"opacity": 0.5, "opacity": 0.5,
} }
path.update(style) path.update(style)
@ -568,7 +568,7 @@ class Road(Tagged):
return return
path: Path = svg.path( path: Path = svg.path(
d=self.line.get_path(self.placement_offset + 3), fill="none" d=self.line.get_path(self.placement_offset + 3.0), fill="none"
) )
svg.add(path) svg.add(path)
@ -580,7 +580,7 @@ class Road(Tagged):
method="align", method="align",
spacing="exact", spacing="exact",
font_family="Roboto", font_family="Roboto",
font_size=10, font_size=10.0,
) )
text.add(text_path) text.add(text_path)
@ -668,7 +668,7 @@ class SimpleConnector(Connector):
"""Draw connection fill.""" """Draw connection fill."""
circle: Circle = svg.circle( circle: Circle = svg.circle(
self.point, self.point,
self.road_1.width * self.scale / 2, self.road_1.width * self.scale / 2.0,
fill=self.road_1.get_color().hex, fill=self.road_1.get_color().hex,
) )
svg.add(circle) svg.add(circle)
@ -677,7 +677,7 @@ class SimpleConnector(Connector):
"""Draw connection outline.""" """Draw connection outline."""
circle: Circle = svg.circle( circle: Circle = svg.circle(
self.point, self.point,
self.road_1.width * self.scale / 2 + 1, self.road_1.width * self.scale / 2.0 + 1.0,
fill=self.road_1.matcher.border_color.hex, fill=self.road_1.matcher.border_color.hex,
) )
svg.add(circle) svg.add(circle)
@ -706,7 +706,7 @@ class ComplexConnector(Connector):
point_1: np.ndarray = flinger.fling(node_1.coordinates) point_1: np.ndarray = flinger.fling(node_1.coordinates)
node_2: OSMNode = self.road_2.nodes[self.index_2] node_2: OSMNode = self.road_2.nodes[self.index_2]
point_2: np.ndarray = flinger.fling(node_2.coordinates) point_2: np.ndarray = flinger.fling(node_2.coordinates)
point = (point_1 + point_2) / 2 point = (point_1 + point_2) / 2.0
points_1: list[np.ndarray] = get_curve_points( points_1: list[np.ndarray] = get_curve_points(
self.road_1, self.road_1,
@ -765,7 +765,9 @@ class SimpleIntersection(Connector):
node: OSMNode = self.road_1.nodes[self.index_1] node: OSMNode = self.road_1.nodes[self.index_1]
point: np.ndarray = self.flinger.fling(node.coordinates) point: np.ndarray = self.flinger.fling(node.coordinates)
circle: Circle = svg.circle( circle: Circle = svg.circle(
point, road.width * self.scale / 2, fill=road.matcher.color.hex point,
road.width * self.scale / 2.0,
fill=road.matcher.color.hex,
) )
svg.add(circle) svg.add(circle)
@ -776,7 +778,7 @@ class SimpleIntersection(Connector):
point: np.ndarray = self.flinger.fling(node.coordinates) point: np.ndarray = self.flinger.fling(node.coordinates)
circle: Circle = svg.circle( circle: Circle = svg.circle(
point, point,
road.width * self.scale / 2 + 1, road.width * self.scale / 2.0 + 1.0,
fill=road.matcher.border_color.hex, fill=road.matcher.border_color.hex,
) )
svg.add(circle) svg.add(circle)

View file

@ -42,7 +42,7 @@ class Figure(Tagged):
) )
def get_path( def get_path(
self, flinger: Flinger, offset: np.ndarray = np.array((0, 0)) self, flinger: Flinger, offset: np.ndarray = np.array((0.0, 0.0))
) -> str: ) -> str:
""" """
Get SVG path commands. Get SVG path commands.
@ -365,13 +365,13 @@ def is_clockwise(polygon: list[OSMNode]) -> bool:
:param polygon: list of OpenStreetMap nodes :param polygon: list of OpenStreetMap nodes
""" """
count: float = 0 count: float = 0.0
for index, node in enumerate(polygon): for index, node in enumerate(polygon):
next_index: int = 0 if index == len(polygon) - 1 else index + 1 next_index: int = 0 if index == len(polygon) - 1 else index + 1
count += (polygon[next_index].coordinates[0] - node.coordinates[0]) * ( count += (polygon[next_index].coordinates[0] - node.coordinates[0]) * (
polygon[next_index].coordinates[1] + node.coordinates[1] polygon[next_index].coordinates[1] + node.coordinates[1]
) )
return count >= 0 return count >= 0.0
def make_clockwise(polygon: list[OSMNode]) -> list[OSMNode]: def make_clockwise(polygon: list[OSMNode]) -> list[OSMNode]:

View file

@ -95,15 +95,15 @@ class BoundaryBox:
n: float = 2.0 ** (zoom_level + 8.0) n: float = 2.0 ** (zoom_level + 8.0)
x: int = int((coordinates[1] + 180.0) / 360.0 * n) x: int = int((coordinates[1] + 180.0) / 360.0 * n)
left: float = (x - width / 2) / n * 360.0 - 180.0 left: float = (x - width / 2.0) / n * 360.0 - 180.0
right: float = (x + width / 2) / n * 360.0 - 180.0 right: float = (x + width / 2.0) / n * 360.0 - 180.0
y: int = (1.0 - np.arcsinh(np.tan(lat_rad)) / np.pi) / 2.0 * n y: int = (1.0 - np.arcsinh(np.tan(lat_rad)) / np.pi) / 2.0 * n
bottom_radians = np.arctan( bottom_radians = np.arctan(
np.sinh((1.0 - (y + height / 2) * 2.0 / n) * np.pi) np.sinh((1.0 - (y + height / 2.0) * 2.0 / n) * np.pi)
) )
top_radians = np.arctan( top_radians = np.arctan(
np.sinh((1.0 - (y - height / 2) * 2.0 / n) * np.pi) np.sinh((1.0 - (y - height / 2.0) * 2.0 / n) * np.pi)
) )
return cls( return cls(
@ -131,17 +131,17 @@ class BoundaryBox:
def round(self) -> "BoundaryBox": def round(self) -> "BoundaryBox":
"""Round boundary box.""" """Round boundary box."""
self.left = round(self.left * 1000) / 1000 - 0.001 self.left = round(self.left * 1000.0) / 1000.0 - 0.001
self.bottom = round(self.bottom * 1000) / 1000 - 0.001 self.bottom = round(self.bottom * 1000.0) / 1000.0 - 0.001
self.right = round(self.right * 1000) / 1000 + 0.001 self.right = round(self.right * 1000.0) / 1000.0 + 0.001
self.top = round(self.top * 1000) / 1000 + 0.001 self.top = round(self.top * 1000.0) / 1000.0 + 0.001
return self return self
def center(self) -> np.ndarray: def center(self) -> np.ndarray:
"""Return center point of boundary box.""" """Return center point of boundary box."""
return np.array( return np.array(
((self.top + self.bottom) / 2, (self.left + self.right) / 2) ((self.top + self.bottom) / 2.0, (self.left + self.right) / 2.0)
) )
def get_format(self) -> str: def get_format(self) -> str:
@ -150,10 +150,10 @@ class BoundaryBox:
<longitude 1>,<latitude 1>,<longitude 2>,<latitude 2>. Coordinates are <longitude 1>,<latitude 1>,<longitude 2>,<latitude 2>. Coordinates are
rounded to three digits after comma. rounded to three digits after comma.
""" """
left: float = np.floor(self.left * 1000) / 1000 left: float = np.floor(self.left * 1000.0) / 1000.0
bottom: float = np.floor(self.bottom * 1000) / 1000 bottom: float = np.floor(self.bottom * 1000.0) / 1000.0
right: float = np.ceil(self.right * 1000) / 1000 right: float = np.ceil(self.right * 1000.0) / 1000.0
top: float = np.ceil(self.top * 1000) / 1000 top: float = np.ceil(self.top * 1000.0) / 1000.0
return f"{left:.3f},{bottom:.3f},{right:.3f},{top:.3f}" return f"{left:.3f},{bottom:.3f},{right:.3f},{top:.3f}"

View file

@ -20,7 +20,9 @@ def pseudo_mercator(coordinates: np.ndarray) -> np.ndarray:
:return: position on the plane in the form of (x, y) :return: position on the plane in the form of (x, y)
""" """
y: float = ( y: float = (
180 / np.pi * np.log(np.tan(np.pi / 4 + coordinates[0] * np.pi / 360)) 180.0
/ np.pi
* np.log(np.tan(np.pi / 4.0 + coordinates[0] * np.pi / 360.0))
) )
return np.array((coordinates[1], y)) return np.array((coordinates[1], y))
@ -36,7 +38,7 @@ def osm_zoom_level_to_pixels_per_meter(
function allows any non-negative float value function allows any non-negative float value
:param equator_length: celestial body equator length in meters :param equator_length: celestial body equator length in meters
""" """
return 2 ** zoom_level / equator_length * 256 return 2.0 ** zoom_level / equator_length * 256.0
class Flinger: class Flinger:
@ -54,7 +56,7 @@ class Flinger:
:param equator_length: celestial body equator length in meters :param equator_length: celestial body equator length in meters
""" """
self.geo_boundaries: BoundaryBox = geo_boundaries self.geo_boundaries: BoundaryBox = geo_boundaries
self.ratio: float = 2 ** zoom_level * 256 / 360 self.ratio: float = 2.0 ** zoom_level * 256.0 / 360.0
self.size: np.ndarray = self.ratio * ( self.size: np.ndarray = self.ratio * (
pseudo_mercator(self.geo_boundaries.max_()) pseudo_mercator(self.geo_boundaries.max_())
- pseudo_mercator(self.geo_boundaries.min_()) - pseudo_mercator(self.geo_boundaries.min_())
@ -90,5 +92,5 @@ class Flinger:
# Get pixels per meter ratio for the center of the boundary box. # Get pixels per meter ratio for the center of the boundary box.
coordinates = self.geo_boundaries.center() coordinates = self.geo_boundaries.center()
scale_factor: float = abs(1 / np.cos(coordinates[0] / 180 * np.pi)) scale_factor: float = abs(1.0 / np.cos(coordinates[0] / 180.0 * np.pi))
return self.pixels_per_meter * scale_factor return self.pixels_per_meter * scale_factor

View file

@ -14,14 +14,14 @@ def compute_angle(vector: np.ndarray) -> float:
For the given vector compute an angle between it and (1, 0) vector. The For the given vector compute an angle between it and (1, 0) vector. The
result is in [0, 2π]. result is in [0, 2π].
""" """
if vector[0] == 0: if vector[0] == 0.0:
if vector[1] > 0: if vector[1] > 0.0:
return np.pi / 2 return np.pi / 2.0
return np.pi + np.pi / 2 return np.pi + np.pi / 2.0
if vector[0] < 0: if vector[0] < 0.0:
return np.arctan(vector[1] / vector[0]) + np.pi return np.arctan(vector[1] / vector[0]) + np.pi
if vector[1] < 0: if vector[1] < 0.0:
return np.arctan(vector[1] / vector[0]) + 2 * np.pi return np.arctan(vector[1] / vector[0]) + 2.0 * np.pi
return np.arctan(vector[1] / vector[0]) return np.arctan(vector[1] / vector[0])
@ -46,10 +46,10 @@ class Polyline:
def __init__(self, points: list[np.ndarray]) -> None: def __init__(self, points: list[np.ndarray]) -> None:
self.points: list[np.ndarray] = points self.points: list[np.ndarray] = points
def get_path(self, parallel_offset: float = 0) -> str: def get_path(self, parallel_offset: float = 0.0) -> str:
"""Construct SVG path commands.""" """Construct SVG path commands."""
points: list[np.ndarray] points: list[np.ndarray]
if np.allclose(parallel_offset, 0): if np.allclose(parallel_offset, 0.0):
points = self.points points = self.points
else: else:
try: try:
@ -98,12 +98,12 @@ class Line:
def is_parallel(self, other: "Line") -> bool: def is_parallel(self, other: "Line") -> bool:
"""If lines are parallel or equal.""" """If lines are parallel or equal."""
return np.allclose(other.a * self.b - self.a * other.b, 0) return np.allclose(other.a * self.b - self.a * other.b, 0.0)
def get_intersection_point(self, other: "Line") -> np.ndarray: def get_intersection_point(self, other: "Line") -> np.ndarray:
"""Get point of intersection current line with other.""" """Get point of intersection current line with other."""
if other.a * self.b - self.a * other.b == 0: if other.a * self.b - self.a * other.b == 0.0:
return np.array((0, 0)) return np.array((0.0, 0.0))
x: float = -(self.b * other.c - other.b * self.c) / ( x: float = -(self.b * other.c - other.b * self.c) / (
other.a * self.b - self.a * other.b other.a * self.b - self.a * other.b

View file

@ -166,7 +166,7 @@ class MapCSSWriter:
return return
for index, stage_of_decay in enumerate(STAGES_OF_DECAY): for index, stage_of_decay in enumerate(STAGES_OF_DECAY):
opacity: float = 0.6 - 0.4 * index / (len(STAGES_OF_DECAY) - 1) opacity: float = 0.6 - 0.4 * index / (len(STAGES_OF_DECAY) - 1.0)
for matcher in self.point_matchers: for matcher in self.point_matchers:
if len(matcher.tags) > 1: if len(matcher.tags) > 1:
continue continue

View file

@ -54,7 +54,7 @@ class Map:
def draw(self, constructor: Constructor) -> None: def draw(self, constructor: Constructor) -> None:
"""Draw map.""" """Draw map."""
self.svg.add( self.svg.add(
Rect((0, 0), self.flinger.size, fill=self.background_color) Rect((0.0, 0.0), self.flinger.size, fill=self.background_color)
) )
ways: list[StyledFigure] = sorted( ways: list[StyledFigure] = sorted(
constructor.figures, key=lambda x: x.line_style.priority constructor.figures, key=lambda x: x.line_style.priority
@ -130,7 +130,7 @@ class Map:
building.draw_shade(building_shade, self.flinger) building.draw_shade(building_shade, self.flinger)
self.svg.add(building_shade) self.svg.add(building_shade)
previous_height: float = 0 previous_height: float = 0.0
for height in sorted(constructor.heights): for height in sorted(constructor.heights):
for building in constructor.buildings: for building in constructor.buildings:
if building.height < height or building.min_height > height: if building.height < height or building.min_height > height:

View file

@ -93,8 +93,8 @@ class Shape:
def get_path( def get_path(
self, self,
point: np.ndarray, point: np.ndarray,
offset: np.ndarray = np.array((0, 0)), offset: np.ndarray = np.array((0.0, 0.0)),
scale: np.ndarray = np.array((1, 1)), scale: np.ndarray = np.array((1.0, 1.0)),
) -> SVGPath: ) -> SVGPath:
""" """
Draw icon into SVG file. Draw icon into SVG file.
@ -108,7 +108,7 @@ class Shape:
transformations.append(f"translate({shift[0]},{shift[1]})") transformations.append(f"translate({shift[0]},{shift[1]})")
if not np.allclose(scale, np.array((1, 1))): if not np.allclose(scale, np.array((1.0, 1.0))):
transformations.append(f"scale({scale[0]},{scale[1]})") transformations.append(f"scale({scale[0]},{scale[1]})")
transformations.append(f"translate({self.offset[0]},{self.offset[1]})") transformations.append(f"translate({self.offset[0]},{self.offset[1]})")
@ -221,7 +221,7 @@ class ShapeExtractor:
def get_offset(value: str) -> float: def get_offset(value: str) -> float:
"""Get negated icon offset from the origin.""" """Get negated icon offset from the origin."""
return ( return (
-int(float(value) / GRID_STEP) * GRID_STEP - GRID_STEP / 2 -int(float(value) / GRID_STEP) * GRID_STEP - GRID_STEP / 2.0
) )
point: np.ndarray = np.array( point: np.ndarray = np.array(
@ -259,7 +259,7 @@ class ShapeSpecification:
shape: Shape shape: Shape
color: Color = DEFAULT_COLOR color: Color = DEFAULT_COLOR
offset: np.ndarray = np.array((0, 0)) offset: np.ndarray = np.array((0.0, 0.0))
flip_horizontally: bool = False flip_horizontally: bool = False
flip_vertically: bool = False flip_vertically: bool = False
use_outline: bool = True use_outline: bool = True
@ -401,7 +401,7 @@ class Icon:
shape_specification.color = color shape_specification.color = color
shape_specification.draw( shape_specification.draw(
svg, svg,
np.array((8, 8)), np.array((8.0, 8.0)),
outline=outline, outline=outline,
outline_opacity=outline_opacity, outline_opacity=outline_opacity,
) )
@ -409,7 +409,7 @@ class Icon:
for shape_specification in self.shape_specifications: for shape_specification in self.shape_specifications:
if color: if color:
shape_specification.color = color shape_specification.color = color
shape_specification.draw(svg, np.array((8, 8))) shape_specification.draw(svg, np.array((8.0, 8.0)))
with file_name.open("w", encoding="utf-8") as output_file: with file_name.open("w", encoding="utf-8") as output_file:
svg.write(output_file) svg.write(output_file)

View file

@ -161,7 +161,7 @@ class IconCollection:
self, self,
file_name: Path, file_name: Path,
columns: int = 16, columns: int = 16,
step: float = 24, step: float = 24.0,
background_color: Color = Color("white"), background_color: Color = Color("white"),
scale: float = 1.0, scale: float = 1.0,
) -> None: ) -> None:
@ -174,19 +174,19 @@ class IconCollection:
:param background_color: background color :param background_color: background color
:param scale: scale icon by the magnitude :param scale: scale icon by the magnitude
""" """
point: np.ndarray = np.array((step / 2 * scale, step / 2 * scale)) point: np.ndarray = np.array((step / 2.0 * scale, step / 2.0 * scale))
width: float = step * columns * scale width: float = step * columns * scale
height: int = int(int(len(self.icons) / columns + 1) * step * scale) height: int = int(int(len(self.icons) / columns + 1.0) * step * scale)
svg: Drawing = Drawing(str(file_name), (width, height)) svg: Drawing = Drawing(str(file_name), (width, height))
svg.add(svg.rect((0, 0), (width, height), fill=background_color.hex)) svg.add(svg.rect((0, 0), (width, height), fill=background_color.hex))
for icon in self.icons: for icon in self.icons:
icon.draw(svg, point, scale=scale) icon.draw(svg, point, scale=scale)
point += np.array((step * scale, 0)) point += np.array((step * scale, 0.0))
if point[0] > width - 8: if point[0] > width - 8.0:
point[0] = step / 2 * scale point[0] = step / 2.0 * scale
point += np.array((0, step * scale)) point += np.array((0.0, step * scale))
height += step * scale height += step * scale
with file_name.open("w", encoding="utf-8") as output_file: with file_name.open("w", encoding="utf-8") as output_file:

View file

@ -32,13 +32,13 @@ class Occupied:
def check(self, point: np.ndarray) -> bool: def check(self, point: np.ndarray) -> bool:
"""Check whether point is already occupied by other elements.""" """Check whether point is already occupied by other elements."""
if 0 <= point[0] < self.width and 0 <= point[1] < self.height: if 0.0 <= point[0] < self.width and 0.0 <= point[1] < self.height:
return self.matrix[point[0], point[1]] return self.matrix[point[0], point[1]]
return True return True
def register(self, point: np.ndarray) -> None: def register(self, point: np.ndarray) -> None:
"""Register that point is occupied by an element.""" """Register that point is occupied by an element."""
if 0 <= point[0] < self.width and 0 <= point[1] < self.height: if 0.0 <= point[0] < self.width and 0.0 <= point[1] < self.height:
self.matrix[point[0], point[1]] = True self.matrix[point[0], point[1]] = True
assert self.matrix[point[0], point[1]] assert self.matrix[point[0], point[1]]
@ -57,7 +57,7 @@ class Point(Tagged):
tags: dict[str, str], tags: dict[str, str],
processed: set[str], processed: set[str],
point: np.ndarray, point: np.ndarray,
priority: float = 0, priority: float = 0.0,
is_for_node: bool = True, is_for_node: bool = True,
draw_outline: bool = True, draw_outline: bool = True,
add_tooltips: bool = False, add_tooltips: bool = False,
@ -71,12 +71,12 @@ class Point(Tagged):
self.processed: set[str] = processed self.processed: set[str] = processed
self.point: np.ndarray = point self.point: np.ndarray = point
self.priority: float = priority self.priority: float = priority
self.layer: float = 0 self.layer: float = 0.0
self.is_for_node: bool = is_for_node self.is_for_node: bool = is_for_node
self.draw_outline: bool = draw_outline self.draw_outline: bool = draw_outline
self.add_tooltips: bool = add_tooltips self.add_tooltips: bool = add_tooltips
self.y = 0 self.y: float = 0.0
self.main_icon_painted: bool = False self.main_icon_painted: bool = False
def draw_main_shapes( def draw_main_shapes(
@ -91,7 +91,7 @@ class Point(Tagged):
): ):
return return
position: np.ndarray = self.point + np.array((0, self.y)) position: np.ndarray = self.point + np.array((0.0, self.y))
tags: Optional[dict[str, str]] = ( tags: Optional[dict[str, str]] = (
self.tags if self.add_tooltips else None self.tags if self.add_tooltips else None
) )
@ -99,7 +99,7 @@ class Point(Tagged):
svg, self.icon_set.main_icon, position, occupied, tags=tags svg, self.icon_set.main_icon, position, occupied, tags=tags
) )
if self.main_icon_painted: if self.main_icon_painted:
self.y += 16 self.y += 16.0
def draw_extra_shapes( def draw_extra_shapes(
self, svg: svgwrite.Drawing, occupied: Optional[Occupied] = None self, svg: svgwrite.Drawing, occupied: Optional[Occupied] = None
@ -110,7 +110,7 @@ class Point(Tagged):
is_place_for_extra: bool = True is_place_for_extra: bool = True
if occupied: if occupied:
left: float = -(len(self.icon_set.extra_icons) - 1) * 8 left: float = -(len(self.icon_set.extra_icons) - 1.0) * 8.0
for _ in self.icon_set.extra_icons: for _ in self.icon_set.extra_icons:
point: np.ndarray = np.array( point: np.ndarray = np.array(
(int(self.point[0] + left), int(self.point[1] + self.y)) (int(self.point[0] + left), int(self.point[1] + self.y))
@ -118,16 +118,16 @@ class Point(Tagged):
if occupied.check(point): if occupied.check(point):
is_place_for_extra = False is_place_for_extra = False
break break
left += 16 left += 16.0
if is_place_for_extra: if is_place_for_extra:
left: float = -(len(self.icon_set.extra_icons) - 1) * 8 left: float = -(len(self.icon_set.extra_icons) - 1.0) * 8.0
for icon in self.icon_set.extra_icons: for icon in self.icon_set.extra_icons:
point: np.ndarray = self.point + np.array((left, self.y)) point: np.ndarray = self.point + np.array((left, self.y))
self.draw_point_shape(svg, icon, point, occupied=occupied) self.draw_point_shape(svg, icon, point, occupied=occupied)
left += 16 left += 16.0
if self.icon_set.extra_icons: if self.icon_set.extra_icons:
self.y += 16 self.y += 16.0
def draw_point_shape( def draw_point_shape(
self, self,
@ -180,7 +180,7 @@ class Point(Tagged):
text = text.replace("&quot;", '"') text = text.replace("&quot;", '"')
text = text.replace("&amp;", "&") text = text.replace("&amp;", "&")
text = text[:26] + ("..." if len(text) > 26 else "") text = text[:26] + ("..." if len(text) > 26 else "")
point = self.point + np.array((0, self.y + 2)) point = self.point + np.array((0.0, self.y + 2.0))
self.draw_text( self.draw_text(
svg, text, point, occupied, label.fill, size=label.size svg, text, point, occupied, label.fill, size=label.size
) )
@ -212,9 +212,9 @@ class Point(Tagged):
if occupied: if occupied:
is_occupied: bool = False is_occupied: bool = False
for i in range(-int(length / 2), int(length / 2)): for i in range(-int(length / 2.0), int(length / 2.0)):
text_position: np.ndarray = np.array( text_position: np.ndarray = np.array(
(int(point[0] + i), int(point[1] - 4)) (int(point[0] + i), int(point[1] - 4.0))
) )
if occupied.check(text_position): if occupied.check(text_position):
is_occupied = True is_occupied = True
@ -223,7 +223,7 @@ class Point(Tagged):
if is_occupied: if is_occupied:
return return
for i in range(-int(length / 2), int(length / 2)): for i in range(-int(length / 2.0), int(length / 2.0)):
for j in range(-12, 5): for j in range(-12, 5):
occupied.register( occupied.register(
np.array((int(point[0] + i), int(point[1] + j))) np.array((int(point[0] + i), int(point[1] + j)))
@ -233,24 +233,40 @@ class Point(Tagged):
if out_fill_2: if out_fill_2:
text_element = svg.text( text_element = svg.text(
text, point, font_size=size, text_anchor="middle", text,
font_family=DEFAULT_FONT, fill=out_fill_2.hex, point,
stroke_linejoin="round", stroke_width=5, stroke=out_fill_2.hex, font_size=size,
opacity=out_opacity_2 text_anchor="middle",
) # fmt: skip font_family=DEFAULT_FONT,
fill=out_fill_2.hex,
stroke_linejoin="round",
stroke_width=5.0,
stroke=out_fill_2.hex,
opacity=out_opacity_2,
)
svg.add(text_element) svg.add(text_element)
if out_fill: if out_fill:
text_element = svg.text( text_element = svg.text(
text, point, font_size=size, text_anchor="middle", text,
font_family=DEFAULT_FONT, fill=out_fill.hex, point,
stroke_linejoin="round", stroke_width=3, stroke=out_fill.hex, font_size=size,
text_anchor="middle",
font_family=DEFAULT_FONT,
fill=out_fill.hex,
stroke_linejoin="round",
stroke_width=3.0,
stroke=out_fill.hex,
opacity=out_opacity, opacity=out_opacity,
) # fmt: skip )
svg.add(text_element) svg.add(text_element)
text_element = svg.text( text_element = svg.text(
text, point, font_size=size, text_anchor="middle", text,
font_family=DEFAULT_FONT, fill=fill.hex, point,
) # fmt: skip font_size=size,
text_anchor="middle",
font_family=DEFAULT_FONT,
fill=fill.hex,
)
svg.add(text_element) svg.add(text_element)
self.y += 11 self.y += 11
@ -264,7 +280,9 @@ class Point(Tagged):
width: int = icon_size * ( width: int = icon_size * (
1 + max(2, len(self.icon_set.extra_icons) - 1) 1 + max(2, len(self.icon_set.extra_icons) - 1)
) )
height: int = icon_size * (1 + int(len(self.icon_set.extra_icons) / 3)) height: int = icon_size * (
1 + np.ceil(len(self.icon_set.extra_icons) / 3.0)
)
if len(self.labels): if len(self.labels):
height += 4 + 11 * len(self.labels) height += 4 + 11 * len(self.labels)
return np.array((width, height)) return np.array((width, height))

View file

@ -289,16 +289,16 @@ class RoadMatcher(Matcher):
if "color" in structure: if "color" in structure:
self.color = Color(scheme.get_color(structure["color"])) self.color = Color(scheme.get_color(structure["color"]))
self.default_width: float = structure["default_width"] self.default_width: float = structure["default_width"]
self.priority: float = 0 self.priority: float = 0.0
if "priority" in structure: if "priority" in structure:
self.priority = structure["priority"] self.priority = structure["priority"]
def get_priority(self, tags: Tags) -> float: def get_priority(self, tags: Tags) -> float:
"""Get priority for drawing order.""" """Get priority for drawing order."""
layer: float = 0 layer: float = 0.0
if "layer" in tags: if "layer" in tags:
layer = float(tags.get("layer")) layer = float(tags.get("layer"))
return 1000 * layer + self.priority return 1000.0 * layer + self.priority
class Scheme: class Scheme:
@ -598,7 +598,7 @@ class Scheme:
""" """
shape: Shape = extractor.get_shape(DEFAULT_SHAPE_ID) shape: Shape = extractor.get_shape(DEFAULT_SHAPE_ID)
color: Color = color color: Color = color
offset: np.ndarray = np.array((0, 0)) offset: np.ndarray = np.array((0.0, 0.0))
flip_horizontally: bool = False flip_horizontally: bool = False
flip_vertically: bool = False flip_vertically: bool = False
use_outline: bool = True use_outline: bool = True

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,7 @@ from typing import Optional
import cairosvg import cairosvg
from map_configuration import MapConfiguration from map_machine.map_configuration import MapConfiguration
from map_machine.slippy.tile import Tile from map_machine.slippy.tile import Tile
from map_machine.workspace import workspace from map_machine.workspace import workspace

View file

@ -151,7 +151,7 @@ def construct_text(
processed.add("route_ref") processed.add("route_ref")
if "cladr:code" in tags: if "cladr:code" in tags:
texts.append(Label(tags["cladr:code"], size=7)) texts.append(Label(tags["cladr:code"], size=7.0))
processed.add("cladr:code") processed.add("cladr:code")
if "website" in tags: if "website" in tags:

View file

@ -293,7 +293,7 @@ def add_render_arguments(parser: argparse.ArgumentParser) -> None:
type=float, type=float,
metavar="<float>", metavar="<float>",
help="OSM zoom level", help="OSM zoom level",
default=18, default=18.0,
) )
parser.add_argument( parser.add_argument(
"-c", "-c",
@ -354,6 +354,7 @@ def progress_bar(
fill_length: int = int(parts / BOXES_LENGTH) fill_length: int = int(parts / BOXES_LENGTH)
box: str = BOXES[int(parts - fill_length * BOXES_LENGTH)] box: str = BOXES[int(parts - fill_length * BOXES_LENGTH)]
sys.stdout.write( sys.stdout.write(
f"{str(int(int(ratio * 1000) / 10)):>3} % {fill_length * ''}{box}" f"{str(int(int(ratio * 1000.0) / 10.0)):>3} % "
f"{fill_length * ''}{box}"
f"{int(length - fill_length - 1) * ' '}{text}\n\033[F" f"{int(length - fill_length - 1) * ' '}{text}\n\033[F"
) )

View file

@ -26,7 +26,7 @@ class MinMax:
def center(self) -> Any: def center(self) -> Any:
"""Get middle point between minimum and maximum.""" """Get middle point between minimum and maximum."""
return (self.min_ + self.max_) / 2 return (self.min_ + self.max_) / 2.0
def is_empty(self) -> bool: def is_empty(self) -> bool:
"""Check if interval is empty.""" """Check if interval is empty."""

View file

@ -5,5 +5,6 @@ from map_machine.ui.completion import completion_commands
def test_completion() -> None: def test_completion() -> None:
"""Test Fish shell completion generation."""
commands: str = completion_commands() commands: str = completion_commands()
assert commands.startswith("set -l") assert commands.startswith("set -l")