I am making a A0 size poster map and need a very detailed basemap. Is there a way to lock the OpenStreetMap basemap to a certain scale/zoom so that it would be very detailed in the final print map? I can only get it to print in a big scale which simplifies the map a lot. The area I need to map is fairly big but I need it to still be detailed. I'm using QGIS but have access to ArcGis Pro as well.
I struggled around with the same problem: always getting unreadable text when printing WMS layers to poster format in QGIS.
Even though the reduction of the layout resolution (i.e. 150DPI) helps sometimes, I found a really nice solution by using the fantastic MapProxy web map proxy server.
Therefor I did some minor adjustments to enable an optional WMS-URL parameter called "minres", which locks the resolution to a specific value.
Here are my changes to 3 of the MapProxy Python files:
... def get_affected_tiles(self, bbox, size, req_srs=None, minres=0): # added minres src_bbox, level = self.get_affected_bbox_and_level(bbox, size, req_srs=req_srs, minres=minres) # added minres return self.get_affected_level_tiles(src_bbox, level) def get_affected_bbox_and_level(self, bbox, size, req_srs=None, minres=0): # added minres if req_srs and req_srs != self.srs: src_bbox = req_srs.transform_bbox_to(self.srs, bbox) else: src_bbox = bbox if not bbox_intersects(self.bbox, src_bbox): raise NoTiles() res = get_resolution(src_bbox, size) ## added code for minres if res < minres: res = minres ## level = self.closest_level(res) if res > self.resolutions*self.max_shrink_factor: raise NoTiles() return src_bbox, level ...
... def _image(self, query): ### added code for minres if not hasattr(query,'minres'): query.minres = 0 ### try: src_bbox, tile_grid, affected_tile_coords = \ self.grid.get_affected_tiles(query.bbox, query.size, req_srs=query.srs,minres=query.minres) # added "minres" except NoTiles: raise BlankImage() except GridError as ex: raise MapBBOXError(ex.args) ...
... def map(self, map_request): self.check_map_request(map_request) params = map_request.params query = MapQuery(params.bbox, params.size, SRS(params.srs), params.format) ### added code for minres query.minres = int(map_request.params.get('minres','0').replace('?','')) ### if map_request.params.get('tiled', 'false').lower() == 'true': query.tiled_only = True orig_query = query ... def capabilities(self, map_request): # TODO: debug layer # if '__debug__' in map_request.params: # layers = self.layers.values() # else: # layers = [layer for name, layer in iteritems(self.layers) # if name != '__debug__'] if map_request.params.get('tiled', 'false').lower() == 'true': tile_layers = self.tile_layers.values() else: tile_layers =  service = self._service_md(map_request) ### added code for minres if 'minres' in map_request.raw_params: service['url'] = service['url'] + '?minres=' + map_request.raw_params['minres'] ### root_layer = self.authorized_capability_layers(map_request.http.environ) ...
After all changes we can add "minres=xx" to the WMS URL to lock the layer resolution in QGIS:
Please don't ask me about the right "minres" value ... I always have to try different values to achieve the best result. :-)
The unfortunate truth is that WMS basemaps are designed for on-screen viewing, not for printing. To make a good printed map, you usually have to design the whole thing, with separate layers and custom styling.
One workaround is to get a basemap without any labels (there are several in the expanded provider pack of the quickmapservices plugin). Then you add manually any labels that you absolutely have to have, and live without the rest. Of course you still have the tradeoff between [high resolution + basemap features too small] vs. [low resolution/pixelated + basemap features the right size].
But, with that workaround, you can get labels by loading features (eg roads, towns, etc) from a WFS, turning off the symbology and only using them for labels. That way the labels are applied on top of the features baked into the basemap, and you don't have to set custom symbology.