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

1#!/usr/bin/env python3 

2 

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 

26 

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. 

31 

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 

73 

74 Returns 

75 ------- 

76 image : PIL.Image 

77 the OpenStreetMap tile 

78 

79 Notes 

80 ----- 

81 Copyright 2017 Thomas Guymer [1]_ 

82 

83 References 

84 ---------- 

85 .. [1] PyGuymer3, https://github.com/Guymer/PyGuymer3 

86 """ 

87 

88 # Import standard modules ... 

89 import os 

90 import time 

91 

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 

103 

104 # Import sub-functions ... 

105 from ..download_file import download_file 

106 from ..image import optimise_image 

107 

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 

115 

116 # Populate default values ... 

117 if cookies is None: 

118 cookies = {} 

119 if headers is None: 

120 headers = {} 

121 

122 # ************************************************************************** 

123 

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" 

137 

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) 

142 

143 # ************************************************************************** 

144 

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}\" ...") 

149 

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 ) 

162 

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 

168 

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 ) 

182 

183 # Sleep ... 

184 time.sleep(1.0) 

185 elif debug: 

186 print(f"INFO: Already downloaded \"{url}\" to \"{png}\".") 

187 

188 # ************************************************************************** 

189 

190 # Open image as RGB (even if it is paletted) ... 

191 with PIL.Image.open(png) as imageObj: 

192 image = imageObj.convert("RGB") 

193 

194 # ************************************************************************** 

195 

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}\" ...") 

200 

201 # Save tile ... 

202 numpy.save(npy, image, allow_pickle = False) 

203 

204 # ************************************************************************** 

205 

206 # Return answer ... 

207 return image