""" Simple OpenStreetMap renderer. Author: Sergey Vartanov (me@enzet.ru). """ import os import random import sys import yaml import numpy as np from roentgen import extract_icon from roentgen import process from roentgen import ui from roentgen import svg from roentgen.flinger import GeoFlinger, Geo from roentgen.osm_reader import OSMReader, OSMWay from roentgen.osm_getter import get_osm from datetime import datetime from typing import List, Optional, Set icons_file_name = "icons/icons.svg" tags_file_name = "data/tags.yml" colors_file_name = "data/colors.yml" missing_tags_file_name = "missing_tags.yml" class Node: def __init__(self, shapes, tags, x, y, color, path, processed, priority=0): self.shapes = shapes self.tags = tags self.x = x self.y = y self.color = color self.path = path self.processed = processed self.priority = priority self.layer = 0 class Way: def __init__( self, kind, nodes, path, style, layer=0.0, priority=0, levels=None): self.kind = kind self.nodes = nodes self.path = path self.style = style self.layer = layer self.priority = priority self.levels = levels def get_path(nodes, shift, map_, flinger: GeoFlinger): path = "" prev_node = None for node_id in nodes: node = map_.node_map[node_id] flinged1 = np.add(flinger.fling(Geo(node.lat, node.lon)), shift) if prev_node: path += f"L {flinged1[0]},{flinged1[1]} " else: path += f"M {flinged1[0]},{flinged1[1]} " prev_node = map_.node_map[node_id] if nodes[0] == nodes[-1]: path += "Z" return path def line_center(nodes, flinger: GeoFlinger): ma = [0, 0] mi = [10000, 10000] for node in nodes: flinged = flinger.fling(Geo(node.lat, node.lon)) if flinged[0] > ma[0]: ma[0] = flinged[0] if flinged[1] > ma[1]: ma[1] = flinged[1] if flinged[0] < mi[0]: mi[0] = flinged[0] if flinged[1] < mi[1]: mi[1] = flinged[1] return [(ma[0] + mi[0]) / 2.0, (ma[1] + mi[1]) / 2.0] def get_float(string): try: return float(string) except ValueError: return 0 def get_user_color(user, seed): if user == "": return "000000" rgb = hex(abs(hash(seed + user)))[-6:] r = int(rgb[0:2], 16) g = int(rgb[2:4], 16) b = int(rgb[4:6], 16) c = (r + g + b) / 3. cc = 0 r = r * (1 - cc) + c * cc g = g * (1 - cc) + c * cc b = b * (1 - cc) + c * cc h = hex(int(r))[2:] + hex(int(g))[2:] + hex(int(b))[2:] return "0" * (6 - len(h)) + h def get_time_color(time): if not time: return "000000" time = datetime.strptime(time, "%Y-%m-%dT%H:%M:%SZ") delta = (datetime.now() - time).total_seconds() time_color = hex(0xFF - min(0xFF, int(delta / 500000.)))[2:] i_time_color = hex(min(0xFF, int(delta / 500000.)))[2:] if len(time_color) == 1: time_color = "0" + time_color if len(i_time_color) == 1: i_time_color = "0" + i_time_color return time_color + "AA" + i_time_color def glue(ways: List[OSMWay]): """ Try to glue ways that share nodes. """ result: List[List[int]] = [] to_process: Set[OSMWay] = set() for way in ways: # type: OSMWay if way.is_cycle(): result.append(way.nodes) else: to_process.add(way) while to_process: way: OSMWay = to_process.pop() glued: Optional[OSMWay] = None for other_way in to_process: # type: OSMWay glued = way.try_to_glue(other_way) if glued: break if glued: to_process.remove(other_way) if glued.is_cycle(): result.append(glued.nodes) else: to_process.add(glued) else: result.append(way.nodes) return result class Constructor: def __init__(self, check_level, mode, seed, map_, flinger, scheme): self.check_level = check_level self.mode = mode self.seed = seed self.map_ = map_ self.flinger = flinger self.scheme = scheme self.nodes: List[Node] = [] self.ways: List[Way] = [] def color(self, name: str): return self.scheme["colors"][name] def construct_ways(self): for way_id in self.map_.way_map: way = self.map_.way_map[way_id] tags = way.tags if not self.check_level(tags): continue self.construct_way(way.nodes, tags, None, way.user, way.timestamp) def construct_way(self, nodes, tags, path, user, time): """ Way construction. Params: :param drawing: structure for drawing elements. :param nodes: way node list. :param tags: way tag dictionary. :param path: way path (if there is no nodes). :param user: author name. :param user: way update time. """ layer: float = 0 level: float = 0 if "layer" in tags: layer = get_float(tags["layer"]) if "level" in tags: levels = list(map(lambda x: float(x), tags["level"].split(";"))) level = sum(levels) / len(levels) layer = 100 * level + 0.01 * layer if nodes: c = line_center( map(lambda x: self.map_.node_map[x], nodes), self.flinger) if self.mode == "user-coloring": user_color = get_user_color(user, self.seed) self.ways.append( Way("way", nodes, path, f"fill:none;stroke:#{self.color('user_color')};" f"stroke-width:1;")) return if self.mode == "time": if not time: return time_color = get_time_color(time) self.ways.append( Way("way", nodes, path, f"fill:none;stroke:#{self.color('time_color')};" f"stroke-width:1;")) return # Indoor features if "indoor" in tags: v = tags["indoor"] style = \ f"stroke:#{self.color('indoor_border_color')};" \ f"stroke-width:1;" if v == "area": style += f"fill:#{self.color('indoor_color')};" layer += 10 elif v == "corridor": style += f"fill:#{self.color('indoor_color')};" layer += 11 elif v in ["yes", "room", "elevator"]: style += f"fill:#{self.color('indoor_color')};" layer += 12 elif v == "column": style += f"fill:#{self.color('indoor_border_color')};" layer += 13 self.ways.append(Way("way", nodes, path, style, layer, 50)) # Natural if "natural" in tags: v = tags["natural"] style = "stroke:none;" if v == "wood": style += f"fill:#{self.color('wood_color')};" layer += 21 elif v == "grassland": style = f"fill:#{self.color('grass_color')};stroke:#{self.color('grass_border_color')};" layer += 20 elif v == "scrub": style += f"fill:#{self.color('wood_color')};" layer += 21 elif v == "sand": style += f"fill:#{self.color('sand_color')};" layer += 20 elif v == "beach": style += f"fill:#{self.color('beach_color')};" layer += 20 elif v == "desert": style += f"fill:#{self.color('desert_color')};" layer += 20 elif v == "forest": style += f"fill:#{self.color('wood_color')};" layer += 21 elif v == "tree_row": style += f"fill:none;stroke:#{self.color('wood_color')};stroke-width:5;" layer += 21 elif v == "water": style = f"fill:#{self.color('water_color')};stroke:#{self.color('water_border_color')};stroke-width:1.0;" layer += 21 self.ways.append(Way("way", nodes, path, style, layer, 50)) # Landuse if "landuse" in tags: style = "fill:none;stroke:none;" if tags["landuse"] == "grass": style = \ f"fill:#{self.color('grass_color')};" \ f"stroke:#{self.color('grass_border_color')};" layer += 20 elif tags["landuse"] == "conservation": style = f"fill:#{self.color('grass_color')};stroke:none;" layer += 20 elif tags["landuse"] == "forest": style = f"fill:#{self.color('wood_color')};stroke:none;" layer += 20 elif tags["landuse"] == "garages": style = f"fill:#{self.color('parking_color')};stroke:none;" layer += 21 shapes, fill, processed = \ process.get_icon(tags, self.scheme, "444444") if nodes: self.nodes.append(Node( shapes, tags, c[0], c[1], fill, path, processed)) elif tags["landuse"] == "construction": layer += 20 style = f"fill:#{self.color('construction_color')};stroke:none;" elif tags["landuse"] in ["residential", "commercial"]: return self.ways.append(Way("way", nodes, path, style, layer, 50)) # Building if "building" in tags: layer += 40 levels = 1 if "building:levels" in tags: levels = float(tags["building:levels"]) style = \ f"fill:#{self.color('building_color')};" \ f"stroke:#{self.color('building_border_color')};" \ f"opacity:1.0;" shapes, fill, processed = \ process.get_icon(tags, self.scheme, "444444") if "height" in tags: try: layer += float(tags["height"]) except ValueError: pass if nodes: self.nodes.append( Node(shapes, tags, c[0], c[1], fill, path, processed, 1)) self.ways.append(Way( "building", nodes, path, style, layer, 50, levels)) # Amenity if "amenity" in tags: style = "fill:none;stroke:none;" layer += 21 if tags["amenity"] == "parking": style = \ f"fill:#{self.color('parking_color')};" \ f"stroke:none;opacity:0.5;" shapes, fill, processed = \ process.get_icon(tags, self.scheme, "444444") if nodes: self.nodes.append(Node( shapes, tags, c[0], c[1], fill, path, processed, 1)) self.ways.append(Way("way", nodes, path, style, layer, 50)) # Waterway if "waterway" in tags: style = "fill:none;stroke:none;" layer += 21 if tags["waterway"] == "riverbank": style = \ f"fill:#{self.color('water_color')};" \ f"stroke:#{self.color('water_border_color')};" \ f"stroke-width:1.0;" elif tags["waterway"] == "river": style = "fill:none;stroke:#" + self.color('water_color') + ";stroke-width:10.0;" self.ways.append(Way("way", nodes, path, style, layer, 50)) # Railway if "railway" in tags: style = "fill:none;stroke:none;" layer += 41 v = tags["railway"] style = "fill:none;stroke-dasharray:none;stroke-linejoin:round;" + \ "stroke-linecap:round;stroke-width:" if v == "subway": style += "10;stroke:#DDDDDD;" if v in ["narrow_gauge", "tram"]: style += "2;stroke:#000000;" if v == "platform": style = \ f"fill:#{self.color('platform_color')};" \ f"stroke:#{self.color('platform_border_color')};" \ f"stroke-width:1;" else: return self.ways.append(Way("way", nodes, path, style, layer, 50)) # Highway if "highway" in tags: layer += 42 v = tags["highway"] style = \ f"fill:none;stroke:#{self.color('road_border_color')};" \ f"stroke-dasharray:none;stroke-linejoin:round;" \ f"stroke-linecap:round;stroke-width:" # Highway outline if v == "motorway": style += "33" elif v == "trunk": style += "31" elif v == "primary": style += f"29;stroke:#{self.color('primary_border_color')};" elif v == "secondary": style += "27" elif v == "tertiary": style += "25" elif v == "unclassified": style += "17" elif v == "residential": style += "17" elif v == "service": if "service" in tags and tags["service"] == "parking_aisle": style += "7" else: style += "11" elif v == "track": style += "3" elif v in ["footway", "pedestrian", "cycleway"]: if not ("area" in tags and tags["area"] == "yes"): style += f"3;stroke:#{self.color('foot_border_color')};" elif v in ["steps"]: style += \ f"6;stroke:#{self.color('foot_border_color')};" \ f"stroke-linecap:butt;" else: style = None if style: style += ";" self.ways.append(Way("way", nodes, path, style, layer + 41, 50)) # Highway main shape style = "fill:none;stroke:#FFFFFF;stroke-linecap:round;" + \ "stroke-linejoin:round;stroke-width:" priority = 50 if v == "motorway": style += "31" elif v == "trunk": style += "29" elif v == "primary": style += "27;stroke:#" + self.color('primary_color') elif v == "secondary": style += "25" elif v == "tertiary": style += "23" elif v == "unclassified": style += "15" elif v == "residential": style += "15" elif v == "service": if "service" in tags and tags["service"] == "parking_aisle": style += "5" else: style += "9" elif v == "cycleway": style += \ f"1;stroke-dasharray:8,2;istroke-linecap:butt;" \ f"stroke:#{self.color('cycle_color')}" elif v in ["footway", "pedestrian"]: priority = 55 if "area" in tags and tags["area"] == "yes": style += "1;stroke:none;fill:#DDDDDD" layer -= 55 # FIXME! else: style += "1.5;stroke-dasharray:7,3;stroke-linecap:round;" + \ "stroke:#" if "guide_strips" in tags and tags["guide_strips"] == "yes": style += self.color('guide_strips_color') else: style += self.color('foot_color') elif v == "steps": style += "5;stroke-dasharray:1.5,2;stroke-linecap:butt;" + \ "stroke:#" if "conveying" in tags: style += "888888" else: style += self.color('foot_color') elif v == "path": style += "1;stroke-dasharray:5,5;stroke-linecap:butt;" + \ "stroke:#" + self.color('foot_color') style += ";" self.ways.append(Way("way", nodes, path, style, layer + 42, 50)) if "oneway" in tags and tags["oneway"] == "yes" or \ "conveying" in tags and tags["conveying"] == "forward": for k in range(7): self.ways.append(Way( "way", nodes, path, f"fill:none;stroke:#EEEEEE;stroke-linecap:butt;" f"stroke-width:{7 - k};stroke-dasharray:{k},{40 - k};", layer + 43, 50)) if "access" in tags and tags["access"] == "private": self.ways.append(Way( "way", nodes, path, f"fill:none;stroke:#{self.color('private_access_color')};" f"stroke-linecap:butt;stroke-width:10;stroke-dasharray:1,5;" f"opacity:0.4;", layer + 0.1, 50)) # Leisure if "leisure" in tags: style = "fill:none;stroke:none;" layer += 21 if tags["leisure"] == "playground": style = f"fill:#{self.color('playground_color')};opacity:0.2;" # FIXME!!!!!!!!!!!!!!!!!!!!! # if nodes: # self.draw_point_shape("toy_horse", c[0], c[1], "444444") elif tags["leisure"] == "garden": style = f"fill:#{self.color('grass_color')};" elif tags["leisure"] == "pitch": style = f"fill:#{self.color('playground_color')};opacity:0.2;" elif tags["leisure"] == "park": return else: style = "fill:#FF0000;opacity:0.2;" self.ways.append(Way("way", nodes, path, style, layer, 50)) # Barrier if "barrier" in tags: style = "fill:none;stroke:none;" layer += 40 if tags["barrier"] == "hedge": style += \ f"fill:none;stroke:#{self.color('wood_color')};" \ f"stroke-width:4;" elif tags["barrier"] == "fense": style += "fill:none;stroke:#000000;stroke-width:1;opacity:0.4;" elif tags["barrier"] == "kerb": style += "fill:none;stroke:#000000;stroke-width:1;opacity:0.2;" else: style += "fill:none;stroke:#000000;stroke-width:1;opacity:0.3;" self.ways.append(Way("way", nodes, path, style, layer, 50)) # Border if "border" in tags: style = "fill:none;stroke:none;" style += "fill:none;stroke:#FF0000;stroke-width:0.5;" + \ "stroke-dahsarray:10,20;" self.ways.append(Way("way", nodes, path, style, layer, 50)) if "area:highway" in tags: style = "fill:none;stroke:none;" if tags["area:highway"] == "yes": style += "fill:#FFFFFF;stroke:#DDDDDD;stroke-width:1;" self.ways.append(Way("way", nodes, path, style, layer, 50)) # drawing["ways"].append({"kind": "way", "nodes": nodes, "layer": layer, # "priority": 50, "style": style, "path": path}) """ if False: if "highway" in tags and tags["highway"] != "steps" and not ( "surface" in tags): drawing["ways"].append({"kind": "way", "nodes": nodes, "layer": layer + 0.1, "priority": 50, "path": path, "style": "fill:none;" + \ "stroke:#FF0000;stroke-linecap:butt;" + \ "stroke-width:5;opacity:0.4;"}) # draw_text("no surface", cx, cy, "FF0000", out_opacity="1.0", # out_fill_2="FF0000", out_opacity_2=1.0) """ def construct_relations(self): for relation_id in self.map_.relation_map: relation = self.map_.relation_map[relation_id] tags = relation.tags if not self.check_level(tags): continue if "type" in tags and tags["type"] == "multipolygon": inners, outers = [], [] for member in relation.members: if member["type"] == "way": if member["role"] == "inner": if member["ref"] in self.map_.way_map: inners.append(self.map_.way_map[member["ref"]]) elif member["role"] == "outer": if member["ref"] in self.map_.way_map: outers.append(self.map_.way_map[member["ref"]]) p = "" inners_path = glue(inners) outers_path = glue(outers) for way in outers_path: path = get_path(way, [0, 0], self.map_, self.flinger) p += path + " " for way in inners_path: way.reverse() path = get_path(way, [0, 0], self.map_, self.flinger) p += path + " " self.construct_way(None, tags, p, "", None) def construct_nodes(self): """ Draw nodes. """ print("Draw nodes...") start_time = datetime.now() node_number = 0 # processed_tags = 0 # skipped_tags = 0 s = sorted(self.map_.node_map.keys(), key=lambda x: -self.map_.node_map[x].lat) for node_id in s: node_number += 1 ui.write_line(node_number, len(self.map_.node_map)) node = self.map_.node_map[node_id] flinged = self.flinger.fling(Geo(node.lat, node.lon)) x = flinged[0] y = flinged[1] tags = node.tags if not self.check_level(tags): continue shapes, fill, processed = process.get_icon(tags, self.scheme) if self.mode == "user-coloring": fill = get_user_color(node.user, self.seed) if self.mode == "time": fill = get_time_color(node.timestamp) # for k in tags: # if k in processed or self.no_draw(k): # processed_tags += 1 # else: # skipped_tags += 1 # for k in []: # tags: # if to_write(k): # draw_text(k + ": " + tags[k], x, y + 18 + text_y, "444444") # text_y += 10 # if show_missing_tags: # for k in tags: # v = tags[k] # if not no_draw(k) and not k in processed: # if ("node " + k + ": " + v) in missing_tags: # missing_tags["node " + k + ": " + v] += 1 # else: # missing_tags["node " + k + ": " + v] = 1 if shapes == [] and tags != {}: shapes = [["no"]] self.nodes.append(Node( shapes, tags, x, y, fill, None, processed)) ui.write_line(-1, len(self.map_.node_map)) print("Nodes painted in " + str(datetime.now() - start_time) + ".") # print("Tags processed: " + str(processed_tags) + ", tags skipped: " + # str(skipped_tags) + " (" + # str(processed_tags / float( # processed_tags + skipped_tags) * 100) + " %).") # Nodes drawing class Painter: def __init__( self, show_missing_tags, overlap, draw_nodes, mode, draw_captions, map_, flinger, output_file, icons, scheme): self.show_missing_tags = show_missing_tags self.overlap = overlap self.draw_nodes = draw_nodes self.mode = mode self.draw_captions = draw_captions self.map_ = map_ self.flinger = flinger self.output_file = output_file self.icons = icons self.scheme = scheme def draw_raw_nodes(self): for node_id in self.map_.node_map: node = self.map_.node_map[node_id] flinged = self.flinger.fling(node) self.output_file.circle(flinged[0], flinged[1], 0.2, color="FFFFFF") def no_draw(self, key): if key in self.scheme["tags_to_write"] or \ key in self.scheme["tags_to_skip"]: return True for prefix in \ self.scheme["prefix_to_write"] + self.scheme["prefix_to_skip"]: if key[:len(prefix) + 1] == prefix + ":": return True return False def to_write(self, key): if key in self.scheme["tags_to_skip"]: return False if key in self.scheme["tags_to_write"]: return True for prefix in self.scheme["prefix_to_write"]: if key[:len(prefix) + 1] == prefix + ":": return True return False def draw_shapes(self, shapes, points, x, y, fill, tags, processed): xxx = -(len(shapes) - 1) * 8 if self.overlap != 0: for shape in shapes: has_space = True for p in points[-1000:]: if x + xxx - self.overlap <= p[0] <= x + xxx + self.overlap and \ y - self.overlap <= p[1] <= y + self.overlap: has_space = False break if has_space: self.draw_point_shape(shape, x + xxx, y, fill, tags=tags) points.append([x + xxx, y]) xxx += 16 else: for shape in shapes: self.draw_point_shape(shape, x + xxx, y, fill, tags=tags) xxx += 16 def draw_texts(self, shapes, points, x, y, fill, tags, processed): if self.draw_captions == "no": return text_y: float = 0 write_tags = self.construct_text(tags, processed) for text_struct in write_tags: fill = text_struct["fill"] if "fill" in text_struct else "444444" size = text_struct["size"] if "size" in text_struct else 10 text_y += size + 1 self.wr(text_struct["text"], x, y, fill, text_y, size=size) if self.show_missing_tags: for k in tags: if not self.no_draw(k) and k not in processed: text = k + ": " + tags[k] self.draw_text(text, x, float(y) + text_y + 18, "734A08") text_y += 10 def draw_text(self, text: str, x, y, fill, size=10, out_fill="FFFFFF", out_opacity=1.0, out_fill_2=None, out_opacity_2=1.0): """ Drawing text. ###### ### outline 2 #------# --- outline 1 #| Text |# #------# ###### """ text = text.replace("&", "and") if out_fill_2: self.output_file.write( f'' + text + "") if out_fill: self.output_file.write( f'' + text + "") self.output_file.write( f'' + text + "") def wr(self, text, x, y, fill, text_y, size=10): text = text[:26] + ("..." if len(text) > 26 else "") self.draw_text(text, x, float(y) + text_y + 8, fill, size=size) def construct_text(self, tags, processed): for key in tags: tags[key] = tags[key].replace(""", '"') texts = [] address: List[str] = [] name = None alt_name = None if "name" in tags: name = tags["name"] tags.pop("name", None) if "name:ru" in tags: if not name: name = tags["name:ru"] tags.pop("name:ru", None) tags.pop("name:ru", None) if "name:en" in tags: if not name: name = tags["name:en"] tags.pop("name:en", None) tags.pop("name:en", None) if "alt_name" in tags: if alt_name: alt_name += ", " else: alt_name = "" alt_name += tags["alt_name"] tags.pop("alt_name") if "old_name" in tags: if alt_name: alt_name += ", " else: alt_name = "" alt_name += "бывш. " + tags["old_name"] if "addr:postcode" in tags and self.draw_captions != "main": address.append(tags["addr:postcode"]) tags.pop("addr:postcode", None) if "addr:country" in tags and self.draw_captions != "main": address.append(tags["addr:country"]) tags.pop("addr:country", None) if "addr:city" in tags and self.draw_captions != "main": address.append(tags["addr:city"]) tags.pop("addr:city", None) if "addr:street" in tags and self.draw_captions != "main": street = tags["addr:street"] if street.startswith("улица "): street = "ул. " + street[len("улица "):] address.append(street) tags.pop("addr:street", None) if "addr:housenumber" in tags: address.append(tags["addr:housenumber"]) tags.pop("addr:housenumber", None) if name: texts.append({"text": name, "fill": "000000"}) if alt_name: texts.append({"text": "(" + alt_name + ")"}) if address: texts.append({"text": ", ".join(address)}) if self.draw_captions == "main": return texts if "route_ref" in tags: texts.append({"text": tags["route_ref"].replace(";", " ")}) tags.pop("route_ref", None) if "cladr:code" in tags: texts.append({"text": tags["cladr:code"], "size": 7}) tags.pop("cladr:code", None) if "website" in tags: link = tags["website"] if link[:7] == "http://": link = link[7:] if link[:8] == "https://": link = link[8:] if link[:4] == "www.": link = link[4:] if link[-1] == "/": link = link[:-1] link = link[:25] + ("..." if len(tags["website"]) > 25 else "") texts.append({"text": link, "fill": "000088"}) tags.pop("website", None) for k in ["phone"]: if k in tags: texts.append({"text": tags[k], "fill": "444444"}) tags.pop(k) for tag in tags: if self.to_write(tag) and not (tag in processed): # texts.append({"text": tag + ": " + tags[tag]}) texts.append({"text": tags[tag]}) return texts def draw_building_walls(self, stage, color, ways): for way in ways: if way.kind != "building": continue if stage == 1: shift_1 = [0, 0] shift_2 = [0, -1] elif stage == 2: shift_1 = [0, -1] shift_2 = [0, -2] else: shift_1 = [0, -2] if way.levels: shift_2 = [0, min(-3, -1 * way.levels)] else: shift_2 = [0, -3] if way.nodes: for i in range(len(way.nodes) - 1): node_1 = self.map_.node_map[way.nodes[i]] node_2 = self.map_.node_map[way.nodes[i + 1]] flinged_1 = self.flinger.fling(Geo(node_1.lat, node_1.lon)) flinged_2 = self.flinger.fling(Geo(node_2.lat, node_2.lon)) self.output_file.write( f'\n') elif way.path: # TODO: implement pass def draw(self, nodes, ways, points): ways = sorted(ways, key=lambda x: x.layer) for way in ways: if way.kind == "way": if way.nodes: path = get_path(way.nodes, [0, 0], self.map_, self.flinger) self.output_file.write(f'\n') else: self.output_file.write('\n') # Building shade self.output_file.write('\n') for way in ways: if way.kind == "building": if way.nodes: shift = [-5, 5] if way.levels: shift = [-5 * way.levels, 5 * way.levels] for i in range(len(way.nodes) - 1): node_1 = self.map_.node_map[way.nodes[i]] node_2 = self.map_.node_map[way.nodes[i + 1]] flinged_1 = self.flinger.fling(Geo(node_1.lat, node_1.lon)) flinged_2 = self.flinger.fling(Geo(node_2.lat, node_2.lon)) self.output_file.write( f'\n') self.output_file.write("\n") # Building walls self.draw_building_walls(1, "AAAAAA", ways) self.draw_building_walls(2, "C3C3C3", ways) self.draw_building_walls(3, "DDDDDD", ways) # Building roof for way in ways: if way.kind == "building": if way.nodes: shift = [0, -3] if way.levels: shift = [0 * way.levels, min(-3, -1 * way.levels)] path = get_path(way.nodes, shift, self.map_, self.flinger) self.output_file.write('\n') else: self.output_file.write( '\n') # Trees for node in nodes: if not("natural" in node.tags and node.tags["natural"] == "tree" and "diameter_crown" in node.tags): continue self.output_file.circle( float(node.x) + (random.random() - 0.5) * 10, float(node.y) + (random.random() - 0.5) * 10, float(node.tags["diameter_crown"]) * 1.2, color='688C44', fill='688C44', opacity=0.3) # All other nodes nodes = sorted(nodes, key=lambda x: x.layer) for node in nodes: if "natural" in node.tags and \ node.tags["natural"] == "tree" and \ "diameter_crown" in node.tags: continue self.draw_shapes(node.shapes, points, node.x, node.y, node.color, node.tags, node.processed) for node in nodes: self.draw_texts(node.shapes, points, node.x, node.y, node.color, node.tags, node.processed) def draw_point_shape(self, name, x, y, fill, tags=None): if not isinstance(name, list): name = [name] for one_name in name: shape, xx, yy = self.icons.get_path(one_name) self.draw_point_outline(shape, x, y, fill, mode=self.mode, size=16, xx=xx, yy=yy) for one_name in name: shape, xx, yy = self.icons.get_path(one_name) self.draw_point(shape, x, y, fill, size=16, xx=xx, yy=yy, tags=tags) def draw_point(self, shape, x, y, fill, size=16, xx=0, yy=0, tags=None): x = int(float(x)) y = int(float(y)) self.output_file.write( '') if tags: self.output_file.write("") self.output_file.write( "\n".join(map(lambda x: x + ": " + tags[x], tags))) self.output_file.write("") self.output_file.write("\n") def draw_point_outline(self, shape, x, y, fill, mode="default", size=16, xx=0, yy=0): x = int(float(x)) y = int(float(y)) opacity = 0.5 stroke_width = 2.2 outline_fill = outline_color if mode not in ["user-coloring", "time"]: r = int(fill[0:2], 16) g = int(fill[2:4], 16) b = int(fill[4:6], 16) Y = 0.2126 * r + 0.7152 * g + 0.0722 * b if Y > 200: outline_fill = "000000" opacity = 0.7 self.output_file.write( '\n') def check_level_number(tags, level): if "level" in tags: levels = map(lambda x: float(x), tags["level"].replace(",", ".").split(";")) if level not in levels: return False else: return False return True def check_level_overground(tags): if "level" in tags: levels = \ map(lambda x: float(x), tags["level"].replace(",", ".").split(";")) for level in levels: if level <= 0: return False return True def main(): options = ui.parse_options(sys.argv) if not options: sys.exit(1) background_color = "EEEEEE" if options.mode in ["user-coloring", "time"]: background_color = "111111" if options.input_file_name: input_file_name = options.input_file_name else: content = get_osm(options.boundary_box) if not content: ui.error("cannot download OSM data") input_file_name = os.path.join("map", options.boundary_box + ".osm") if not os.path.isfile(input_file_name): print("Fatal: no such file: " + input_file_name + ".") sys.exit(1) boundary_box = list(map( lambda x: float(x.replace('m', '-')), options.boundary_box.split(','))) full = False # Full keys getting if options.mode in ["user-coloring", "time"]: full = True osm_reader = OSMReader(input_file_name) map_ = osm_reader.parse_osm_file( parse_ways=options.draw_ways, parse_relations=options.draw_ways, full=full) output_file = svg.SVG(open(options.output_file_name, "w+")) w, h = list(map(lambda x: float(x), options.size.split(","))) output_file.begin(w, h) output_file.write( "Rӧntgen\n") output_file.rect(0, 0, w, h, color=background_color) min1 = Geo(boundary_box[1], boundary_box[0]) max1 = Geo(boundary_box[3], boundary_box[2]) authors = {} missing_tags = {} points = [] scheme = yaml.load(open(tags_file_name), Loader=yaml.FullLoader) scheme["cache"] = {} w3c_colors = yaml.load(open(colors_file_name), Loader=yaml.FullLoader) for color_name in w3c_colors: scheme["colors"][color_name] = w3c_colors[color_name] flinger = GeoFlinger(min1, max1, [0, 0], [w, h]) icons = extract_icon.IconExtractor(icons_file_name) check_level = lambda x: True if options.level: if options.level == "overground": check_level = check_level_overground elif options.level == "underground": check_level = lambda x: not check_level_overground(x) else: check_level = lambda x: check_level_number(x, float(options.level)) constructor = Constructor( check_level, options.mode, options.seed, map_, flinger, scheme) if options.draw_ways: constructor.construct_ways() constructor.construct_relations() constructor.construct_nodes() painter = Painter( show_missing_tags=options.show_missing_tags, overlap=options.overlap, draw_nodes=options.draw_nodes, mode=options.mode, draw_captions=options.draw_captions, map_=map_, flinger=flinger, output_file=output_file, icons=icons, scheme=scheme) painter.draw(constructor.nodes, constructor.ways, points) if flinger.space[0] == 0: output_file.rect(0, 0, w, flinger.space[1], color="FFFFFF") output_file.rect(0, h - flinger.space[1], w, flinger.space[1], color="FFFFFF") if flinger.space[1] == 0: output_file.rect(0, 0, flinger.space[0], h, color="FFFFFF") output_file.rect(w - flinger.space[0], 0, flinger.space[0], h, color="FFFFFF") if options.show_index: print(min1.lon, max1.lon) print(min1.lat, max1.lat) lon_step = 0.001 lat_step = 0.001 matrix = [] lat_number = int((max1.lat - min1.lat) / lat_step) + 1 lon_number = int((max1.lon - min1.lon) / lon_step) + 1 for i in range(lat_number): row = [] for j in range(lon_number): row.append(0) matrix.append(row) for node_id in map_.node_map: node = map_.node_map[node_id] i = int((node.lat - min1.lat) / lat_step) j = int((node.lon - min1.lon) / lon_step) if (0 <= i < lat_number) and (0 <= j < lon_number): matrix[i][j] += 1 if "tags" in node: matrix[i][j] += len(node.tags) for way_id in map_.way_map: way = map_.way_map[way_id] if "tags" in way: for node_id in way.nodes: node = map_.node_map[node_id] i = int((node.lat - min1.lat) / lat_step) j = int((node.lon - min1.lon) / lon_step) if (0 <= i < lat_number) and (0 <= j < lon_number): matrix[i][j] += len(way.tags) / float( len(way.nodes)) for i in range(lat_number): for j in range(lon_number): b = int(matrix[i][j] / 1) a = "%2x" % min(255, b) color = a + a + a color = color.replace(" ", "0") t1 = flinger.fling(Geo(min1.lat + i * lat_step, min1.lon + j * lon_step)) t2 = flinger.fling(Geo(min1.lat + (i + 1) * lat_step, min1.lon + (j + 1) * lon_step)) # output_file.write("\n") output_file.text(((t1 + t2) * 0.5)[0], ((t1 + t2) * 0.5)[1] + 40, str(int(matrix[i][j])), size=80, color="440000", opacity=0.1, align="center") output_file.end() top_missing_tags = \ sorted(missing_tags.keys(), key=lambda x: -missing_tags[x]) missing_tags_file = open(missing_tags_file_name, "w+") for tag in top_missing_tags: missing_tags_file.write( f'- {{tag: "{tag}", count: {missing_tags[tag]}}}\n') missing_tags_file.close() top_authors = sorted(authors.keys(), key=lambda x: -authors[x]) for author in top_authors: print(author + ": " + str(authors[author]))