Coverage for pyguymer3/openstreetmap/tile.py: 2%
55 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-08 18:47 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-08 18:47 +0000
1#!/usr/bin/env python3
3# Define function ...
4def tile(
5 xtile,
6 ytile,
7 zoom,
8 sess,
9 /,
10 *,
11 chunksize = 1048576,
12 cookies = None,
13 debug = __debug__,
14 exiftoolPath = None,
15 gifsiclePath = None,
16 headers = None,
17 jpegtranPath = None,
18 optipngPath = None,
19 scale = 1,
20 thunderforestKey = None,
21 thunderforestMap = "atlas",
22 timeout = 60.0,
23 verify = True,
24):
25 """Fetch an OpenStreetMap tile
27 This function reads in a tile number and then checks to see if the tile
28 already exists in the local cache. It will load up the PNG image if it
29 exists or download the OpenStreetMap tile (and save it as both a NPY array
30 and a PNG image) if it is missing.
32 Parameters
33 ----------
34 xtile : int
35 the tile x location
36 ytile : int
37 the tile y location
38 zoom : int
39 the OpenStreetMap zoom level
40 sess : requests.Session
41 the session for any requests calls
42 chunksize : int, optional
43 the size of the chunks of any files which are read in (in bytes)
44 cookies : dict, optional
45 extra cookies for any requests calls
46 debug : bool, optional
47 print debug messages
48 exiftoolPath : str, optional
49 the path to the "exiftool" binary (if not provided then Python will
50 attempt to find the binary itself)
51 gifsiclePath : str, optional
52 the path to the "gifsicle" binary (if not provided then Python will
53 attempt to find the binary itself)
54 headers : dict, optional
55 extra headers for any requests calls
56 jpegtranPath : str, optional
57 the path to the "jpegtran" binary (if not provided then Python will
58 attempt to find the binary itself)
59 optipngPath : str, optional
60 the path to the "optipng" binary (if not provided then Python will
61 attempt to find the binary itself)
62 scale : int, optional
63 the scale of the tile
64 thunderforestKey : string, optional
65 your personal API key for the Thunderforest service (if provided then it
66 is assumed that you want to use the Thunderforest service)
67 thunderforestMap : string, optional
68 the Thunderforest map style (see https://www.thunderforest.com/maps/)
69 timeout : float, optional
70 the timeout for any requests/subprocess calls (in seconds)
71 verify : bool, optional
72 verify the server's certificates for any requests calls
74 Returns
75 -------
76 image : PIL.Image
77 the OpenStreetMap tile
79 Notes
80 -----
81 Copyright 2017 Thomas Guymer [1]_
83 References
84 ----------
85 .. [1] PyGuymer3, https://github.com/Guymer/PyGuymer3
86 """
88 # Import standard modules ...
89 import os
90 import time
92 # Import special modules ...
93 try:
94 import numpy
95 except:
96 raise Exception("\"numpy\" is not installed; run \"pip install --user numpy\"") from None
97 try:
98 import PIL
99 import PIL.Image
100 PIL.Image.MAX_IMAGE_PIXELS = 1024 * 1024 * 1024 # [px]
101 except:
102 raise Exception("\"PIL\" is not installed; run \"pip install --user Pillow\"") from None
104 # Import sub-functions ...
105 from ..download_file import download_file
106 from ..image import optimise_image
108 # Check inputs ...
109 if not 0 <= xtile < pow(2, zoom):
110 raise Exception(f"\"xtile\" is not in the required range ({xtile:d})") from None
111 if not 0 <= ytile < pow(2, zoom):
112 raise Exception(f"\"ytile\" is not in the required range ({ytile:d})") from None
113 if not 0 <= zoom <= 19:
114 raise Exception(f"\"zoom\" is not in the required range ({zoom:d})") from None
116 # Populate default values ...
117 if cookies is None:
118 cookies = {}
119 if headers is None:
120 headers = {}
122 # **************************************************************************
124 # Deduce tile names and URL ...
125 # NOTE: See https://www.thunderforest.com/docs/map-tiles-api/
126 if thunderforestKey is not None:
127 npy = None
128 png = os.path.expanduser(f"~/.local/share/thunderforest/tiles/map={thunderforestMap}/scale={scale:d}/zoom={zoom:d}/x={xtile:d}/y={ytile:d}.png")
129 if scale == 1:
130 url = f"https://tile.thunderforest.com/{thunderforestMap}/{zoom:d}/{xtile:d}/{ytile:d}.png?apikey={thunderforestKey}"
131 else:
132 url = f"https://tile.thunderforest.com/{thunderforestMap}/{zoom:d}/{xtile:d}/{ytile:d}@{scale:d}x.png?apikey={thunderforestKey}"
133 else:
134 npy = os.path.expanduser(f"~/.local/share/cartopy_cache/OSM/{xtile:d}_{ytile:d}_{zoom:d}.npy")
135 png = os.path.expanduser(f"~/.local/share/openstreetmap/tiles/{zoom:d}/{xtile:d}/{ytile:d}.png")
136 url = f"https://tile.openstreetmap.org/{zoom:d}/{xtile:d}/{ytile:d}.png"
138 # Make output folders if they are missing ...
139 os.makedirs(os.path.dirname(png), exist_ok = True)
140 if npy is not None:
141 os.makedirs(os.path.dirname(npy), exist_ok = True)
143 # **************************************************************************
145 # Check if the tile is missing ...
146 if not os.path.exists(png):
147 if debug:
148 print(f"INFO: Downloading \"{url}\" to \"{png}\" ...")
150 # Download tile ...
151 resp = download_file(
152 sess,
153 url,
154 png,
155 cookies = cookies,
156 debug = debug,
157 headers = headers,
158 setModificationTime = False,
159 timeout = timeout,
160 verify = verify,
161 )
163 # Check if the download failed ...
164 if resp is False:
165 # Return answer ...
166 print(f"WARNING: Failed to download the tile for x={xtile:d}, y={ytile:d}, scale={scale:d} and zoom={zoom:d}.")
167 return None
169 # Optimise tile ...
170 optimise_image(
171 png,
172 chunksize = chunksize,
173 debug = debug,
174 exiftoolPath = exiftoolPath,
175 gifsiclePath = gifsiclePath,
176 jpegtranPath = jpegtranPath,
177 optipngPath = optipngPath,
178 pool = None,
179 strip = True,
180 timeout = timeout,
181 )
183 # Sleep ...
184 time.sleep(1.0)
185 elif debug:
186 print(f"INFO: Already downloaded \"{url}\" to \"{png}\".")
188 # **************************************************************************
190 # Open image as RGB (even if it is paletted) ...
191 with PIL.Image.open(png) as imageObj:
192 image = imageObj.convert("RGB")
194 # **************************************************************************
196 # Check if the tile is missing ...
197 if npy is not None and not os.path.exists(npy):
198 if debug:
199 print(f"INFO: Making \"{npy}\" from \"{png}\" ...")
201 # Save tile ...
202 numpy.save(npy, image, allow_pickle = False)
204 # **************************************************************************
206 # Return answer ...
207 return image