2

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.

7
  • The title suggests that you read the basemap from a WMS Service, is that right? Does it matter that if you use a very detailed scale on the printout the texts will be very tiny if you are not lucky enough to deal with a WMS server that supports some tricks for handling that issue?
    – user30184
    Commented Jan 8, 2020 at 11:20
  • Yes, I now realize that is true, the text will probably be way too small to read. Any tips on how to make a large map with lots of detail in A0 size? I probably need to find a raster or vector layer with the amount of detail I want. Shame I can't just use a WMS layer.
    – UniStudent
    Commented Jan 8, 2020 at 11:39
  • 1
    did you increase the DPI in the map layout and does your WMS support DPI settings - GeoServer and MapServer do
    – Ian Turton
    Commented Jan 8, 2020 at 11:41
  • 1
    If you can get ahold of the WMS service as XYZ Tiles instead then you have a bit more flexibility as you can set the min and max zoom levels, however there are trade-offs with this as well. Commented Jan 8, 2020 at 11:42
  • I can get a hold of XYZ Tiles but don't see how that is different? I also tried the WFS server but that seems like building a map from pretty much scratch and I can't get the place names to show as anything but points on the map. I tried increasing the DPI but maybe I'll try different settings with that if I manage to lock the scale.
    – UniStudent
    Commented Jan 8, 2020 at 12:00

3 Answers 3

5

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:

/site-packages/mapproxy/grid.py

    ...

    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[0]*self.max_shrink_factor:
            raise NoTiles()

        return src_bbox, level
    ...

/site-packages/mapproxy/layer.py

    ...

    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[0])
    ...

/site-packages/mapproxy/service/wms.py

    ...

    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:

http://localhost:8080/mapproxy/service_name/service?minres=10

Please don't ask me about the right "minres" value ... I always have to try different values to achieve the best result. :-)

3

Good things come to those who wait!

Yesterday I found this nice tag called <TileLevel> in the GDAL WMS-XML description file, which helps us to freeze WMTS/TMS tile levels.

Take the XML description for the OpenStreetMap service as example (osm.xml):

<GDAL_WMS>
    <Service name="TMS">
        <ServerUrl>https://tile.openstreetmap.org/${z}/${x}/${y}.png</ServerUrl>
    </Service>
    <DataWindow>
        <UpperLeftX>-20037508.34</UpperLeftX>
        <UpperLeftY>20037508.34</UpperLeftY>
        <LowerRightX>20037508.34</LowerRightX>
        <LowerRightY>-20037508.34</LowerRightY>
        <TileLevel>14</TileLevel>
        <TileCountX>1</TileCountX>
        <TileCountY>1</TileCountY>
        <YOrigin>top</YOrigin>
    </DataWindow>
    <Projection>EPSG:3857</Projection>
    <BlockSizeX>256</BlockSizeX>
    <BlockSizeY>256</BlockSizeY>
    <BandsCount>3</BandsCount>
    <UserAgent>Mozilla/5.0 QGIS/31602</UserAgent>
    <Cache />
</GDAL_WMS>

Simply drag the file into QGIS map canvas and change the image resampling settings to Cubic.

If you create your map in a local projection (i.e. EPSG:31255), it's better to use GDALWARP with an additional resampling parameter (i.e. -r CubicSpline) to reproject the WMTS service into a virtual image (VRT) to improve the printout.

gdalwarp -r CubicSpline -t_srs EPSG:31255 -of VRT osm.xml osm_31255.vrt

If you're going to clip the WMTS to reduce the print size, don't forget to set the GDAL config option GDALWARP_DENSIFY_CUTLINE to NO. This setting will reduce the VRT size as well.

gdalwarp --config GDALWARP_DENSIFY_CUTLINE NO -r CubicSpline -dstalpha -cutline boundary.geojson -crop_to_cutline -t_srs EPSG:31255 -of VRT osm.xml osm_31255.vrt

And last but not least, you can do the same with WMTS-XML description files. Here you have to edit the tag <ZoomLevel> to get best results.

So there's really no need to install and customize a MapProxy server for this (see my first answer in this thread), but if you do, you won't regret it!

1
  • could it be possible to write this out for noobs to GDAL/PyQGIS? In only recently learned the PyConsole and hence only know how to add a WMSlayer using PyQGIS. I searched but didn't find clear tutorials on how to get your xml-description file and how to edit it. Any clarification would be great, thanks!
    – Soof
    Commented Jul 5, 2022 at 21:54
1

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.