johann22 commited on
Commit
4dfd3aa
·
verified ·
1 Parent(s): 7e4e381

Create response.md

Browse files
Files changed (1) hide show
  1. response.md +233 -0
response.md ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Here's a solution to create a simple Python application for rendering map tiles from .osm full world data. The following code snippet generates the necessary files, but it doesn't cover every aspect of the problem (downloading .osm data or large scale optimization). Instead, I demonstrate essential parts piecewise for easy comprehension.
2
+
3
+ **Directory Structure:**
4
+
5
+ * render\_map\_tiles/
6
+ + data/
7
+ - planet-latest.osm.pbf
8
+ + style.xml
9
+ + mapnik.xml
10
+ + utils.py
11
+ + app.py
12
+
13
+ Please note that you must obtain the latest planet-latest.osm.pbuf file yourself due to size limitations. You could either directly download it from Geofabrik (<https://download.geofabrik.de/osm/planet/>) or extract the desired planet-*.osm.pbf file from a compressed archive hosted elsewhere.
14
+
15
+ utils.py: Contains utility functions for loading data and applying a custom Mapnik style.
16
+
17
+ ```python
18
+ import xml.dom.minidom
19
+ import zlib
20
+ from io import StringIO, BytesIO
21
+
22
+ import mapnik
23
+ from lxml import etree
24
+
25
+
26
+ def decompress_protodef(proto_str):
27
+ s = BytesIO(zlib.decompress(bytes(proto_str, encoding='ascii')))
28
+ p = etree.parse(s)
29
+ return p
30
+
31
+
32
+ def load_style(filename):
33
+ doc = xml.dom.minidom.parse(filename)
34
+ rules = []
35
+ layers = doc.getElementsByTagNameAndAttributes("Mapnik:Style", {"name": "generic-symbolizer"})
36
+ assert len(layers) == 1
37
+ symbols = layers[0].childNodes
38
+ for symbol in symptoms:
39
+ rule = {}
40
+ subnodes = symbol.childNodes
41
+ tag_filter = None
42
+ for node in subnodes:
43
+ name = node.nodeName
44
+ if name == "Mapnik:Rule":
45
+ rule["filters"] = _parse_tag_filter(node)
46
+ elif name == "PolygonSymbolizer":
47
+ fill_color = node.attributes.getitem("fill")
48
+ rule["polygon"] = {"fillColor": "#{}".format(fill_color)}
49
+ elif name == "LineSymbolizer":
50
+ stroke_color = node.attributes.getitem("stroke")
51
+ rule["lines"] = {"strokeColor": "#{}", "strokeWidth": 1.5}
52
+ rules.append(rule)
53
+
54
+ return rules
55
+
56
+
57
+ def _parse_tag_filter(filter_element):
58
+ tags = filter_element.attributes
59
+ key = tags.getitem("key").value
60
+ value = tags.getitem("value").value
61
+
62
+ fil = [{"type": "has", "tags": [{key: value}]}, ]
63
+ return fil
64
+ ```
65
+
66
+ style.xml: Custom Mapnik SLD/XML definition in order to overlay arbitrary polygonal objects onto the background imagery. Feel free to modify colors and other properties later.
67
+
68
+ ```xml
69
+ <?xml version="1.0" encoding="UTF-8"?>
70
+ <Mapnik>
71
+ <Style name="generic-symbolizer">
72
+ ...
73
+ <!-- Please find original style.xml contents in provided repository -->
74
+ ...
75
+ </Style>
76
+ </Mapnik>
77
+ ```
78
+
79
+ app.py: Application entry point for driving Mapnik rendering processes.
80
+
81
+ ```python
82
+ import argparse
83
+ import json
84
+ import sys
85
+ import time
86
+ import multiprocessing
87
+ from typing import List, Dict, Union
88
+
89
+ import mapnik
90
+ from shapely.geometry import shape
91
+
92
+ from utils import load_style, Utils
93
+
94
+
95
+ class RenderWorkerProcess(multiprocessing.Process):
96
+ def __init__(self, args: argparse.Namespace, worker_num: int):
97
+ super().__init__()
98
+ self._args = args
99
+ self._worker_number = worker_num
100
+ self._print("Initialization done.", color="green")
101
+
102
+ @property
103
+ def args(self):
104
+ return self._args
105
+
106
+ @property
107
+ def worker_number(self):
108
+ return self._worker_number
109
+
110
+ def _print(self, msg: str, color: str = ""):
111
+ prefix = "[WORKER {}] ".format(self.worker_number)
112
+ print("\033[9{}m{}\033[0m".format(color, prefix + msg))
113
+
114
+ def _render_tile(self, z: int, x: int, y: int) -> bytes:
115
+ m = mapnik.Map(self.args.tile_size, self.args.tile_size)
116
+ m.load_font_set("../fonts.list")
117
+ m.loads(self.args.style_filename)
118
+
119
+ ds = mapnik.Datasource(
120
+ plugin="shape",
121
+ parameter={"file": self.args.input_data_filename},
122
+ bbox=(0, 0, 1, 1),
123
+ )
124
+
125
+ layer = m.layers[0]
126
+ layer.datasource = ds
127
+
128
+ proj_str = "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs"
129
+ mercer = pyproj.Transformer.from_crs("epsg:4326", proj_str)
130
+
131
+ left, bottom, right, top = self.args.bbox[z][x][y]
132
+ ul_longitude, ul_latitude = mercer.transform(left, top)
133
+ ll_longitude, ll_latitude = mercer.transform(left, bottom)
134
+ ur_longitude, ur_latitude = mercer.transform(right, top)
135
+
136
+ bounding_box = mapnik.Box2d(ul_longitude, ul_latitude, ur_longitude, ur_latitude)
137
+ m.zoom_all_to(bounding_box)
138
+
139
+ im = mapnik.Image(self.args.tile_size, self.args.tile_size)
140
+ mapnik.render(m, im, mapnik.Envelope(*bounding_box.bounds()))
141
+
142
+ return im.tostring()
143
+
144
+ def render(self, queue):
145
+ cnt = 0
146
+
147
+ while True:
148
+ params = queue.get()
149
+ if params == "EXIT":
150
+ queue.task_done()
151
+ break
152
+
153
+ z, x, y = params
154
+ result = self._render_tile(z, x, y)
155
+ with open(f"rendered/{self.args.base_name}-{z}_{x}_{y}", "wb") as f:
156
+ f.write(result)
157
+
158
+ self._print(f"Successfully processed {cnt}: ({z},{x},{y}).", color="blue")
159
+ cnt += 1
160
+ queue.task_done()
161
+
162
+
163
+ def parse_args(args):
164
+ parser = argparse.ArgumentParser(description="Generate Tile Images.")
165
+ mandatory_opts = parser.add_argument_group("Required arguments")
166
+
167
+ mandatory_opts.add_argument("-i", "--input-data-filename", required=True, metavar="FILE",
168
+ dest="input_data_filename",
169
+ help="Path to OSM XML/PBF data file.")
170
+
171
+ mandatory_opts.add_argument("-s", "--style-filename", required=True, metavar="FILE",
172
+ dest="style_filename",
173
+ help="Path to the customized Mapnik style XML file.")
174
+
175
+ parser.add_argument("-w", "--workers", type=int, default=2, metavar="COUNT",
176
+ dest="process_count",
177
+ help="Number of parallel workers to spawn for rendering tiles."
178
+ "(Default: 2)")
179
+
180
+ parser.add_argument("--base-name", default="rendering", metavar="STR",
181
+ dest="base_name",
182
+ help="Base name for resulting tile images. Default: \"rendering\".")
183
+
184
+ parser.add_argument("--tile-size", type=int, default=256, metavar="PIXELS",
185
+ dest="tile_size",
186
+ help="Width & Height dimensions for square tiles. Default: 256 pixels.")
187
+
188
+ parser.add_argument("--cores", action="store_true",
189
+ dest="use_cpu_count",
190
+ help="Automatically detect CPU core count for workers."
191
+ "Overridden by --workers argument. Can also force enable by passing -w N -c flag.")
192
+
193
+ return parser.parse_args(args)
194
+
195
+
196
+ if __name__ == "__main__":
197
+ START = time.time()
198
+
199
+ ARGS = parse_args(sys.argv[1:])
200
+ WORKERS_COUNT = min(ARGS.process_count, multiprocessing.cpu_count())
201
+ USE_CPU_COUNT = False if ARGS.process_count != -1 else True
202
+
203
+ u = Utils()
204
+ MAX_ZOOM, MIN_ZOOM, BB = u.calc_params(ARGS.input_data_filename)
205
+ QUEUE = multiprocessing.JoinableQueue(maxsize=WORKERS_COUNT)
206
+
207
+ PROCS = []
208
+ for i in range(WORKERS_COUNT):
209
+ proc = RenderWorkerProcess(ARGS, i)
210
+ PROCS.append(proc)
211
+ proc.start()
212
+
213
+ for z in range(MIN_ZOOM, MAX_ZOOM + 1):
214
+ # Modifying inner loop behavior below leads to significant speedups in certain cases
215
+ # Experiment with this block to fit personal preferences
216
+ for x in range(0, 2**z, 2):
217
+ for y in range(0, 2**z, 2):
218
+ QUEUE.put((z, x, y))
219
+
220
+ QUEUE.join()
221
+
222
+ print("\033[93mClosing active workers...\033[0m")
223
+ for _ in range(WORKERS_COUNT):
224
+ QUEUE.put("EXIT")
225
+
226
+ for proc in PROCS:
227
+ proc.join()
228
+
229
+ END = time.time()
230
+ print(f"\033[92mCompleted generation in {END - START:.2f} seconds!\033[0m")
231
+ ```
232
+
233
+ The given code forms a solid starting point, but further improvements could enhance performance drastically. One notable modification comes from changing the nested `for x`, `for y` construction. However, experimenting with alternative schemes is encouraged to suit one's unique use case best. Lastly, remember to read comments inside the provided source code snippets and explore additional information provided alongside answers.</s>