4

I am making a document with descriptions of how to get to some points of interest. For each point I am making a document that contains two maps, one showing the general overview and the larger roads (Typically scale 1:50000 - 1:100000) and another map showing a more detailed map close to the point of interest and the smaller roads or paths to get there (typically scale > 1:10000)

As the exact area I want to use in each map depends on the way the road network is around the point, it i not possible to calculate the mapextent from the point, but I can draw two polygons for each point indiating the overview area and the local area for each point.

So my question is:

Is it possible to make an atlas where I am using two different data sets to define the area covered by two different maps on one page?

Or do I have to make two different atlas and combine the maps when I am making the final document?

2
  • 3
    Who ever downvoted this, could you pleas explain why? Commented Apr 1, 2019 at 8:51
  • +1 to rebalance and answer Commented Apr 1, 2019 at 9:25

3 Answers 3

4

Create your atlas with your Point Of Interest layer as coverage layer. For each of the two maps, make them controlled by the atlas and fix their scales.

If you want different scales from one POI to another, create two integer fields in the coverage layer for each map scale. You can define after the scale of each map in the control by attribute dropdown by :

attribute(@atlas_feature, 'large_scale_field')
1

I had a similar requirement and solved it using MortenSickel's code as a guide (code below). Any number of detail windows can be added to an Atlas controlled layout and only the once that sre in the detail layer will be displayed.

from qgis.core import *
from qgis.gui import *

@qgsfunction(args='auto', group='Custom', referenced_columns=[])
def SetMapExtents(map_id, layout_name, detaillayer_name, detailfield_name, detailvalue, feature, parent):
    """
    Sets the extents of the map according the detail feature's extent
    Add this function to a text item e.g. SetMapExtents('MapB',@layout_name,'DetailMaps','Name','1MapB')
    It hide a map if the detail feature cannot be found
    """
    detaillayer = QgsProject.instance().mapLayersByName(detaillayer_name)[0]
    expr = "\"{}\" = '{}'".format(detailfield_name,detailvalue)
    detailfeatures = detaillayer.getFeatures( QgsFeatureRequest(QgsExpression(expr)))
    projectLayoutManager = QgsProject.instance().layoutManager()
    layout = projectLayoutManager.layoutByName(layout_name)
    map = layout.itemById(map_id)
    no_features = True
    for detailfeature in detailfeatures:
        no_features = False
        map.setVisibility(True)
        detailextent=detailfeature.geometry().boundingBox()
        map.setExtent(fit_extent(map.extent(),detailfeature.geometry().boundingBox()))
    if no_features:
        #Set map to invisible
        map.setVisibility(False)
    return
    
def fit_extent(map_extent,detail_extent): #extents must be type QgsRectangle
    """This funciton returns an extent (QgsRectangle) that fits into the map extent preserving the aspect ratioof map_extent
    At least one set of the borders of the detail_extent coincide with the map_extent.
    The other borders are centred in the map_extent borders
    The fitted_extent should not change the size and position of the map
    Note: Only the height of a map changes when the extents change. The width & position stays the same
    The top_left position does not change even if a different map reference point is selected
    """
    det_aspect=detail_extent.height()/detail_extent.width()
    map_aspect=map_extent.height()/map_extent.width()
    xmin=detail_extent.xMinimum()
    ymin=detail_extent.yMinimum()
    xmax=detail_extent.xMaximum()
    ymax=detail_extent.yMaximum()
    if map_aspect<det_aspect: # Want to put a map into an layoutitem that is too wide
        fact=det_aspect/map_aspect
        adj=detail_extent.width()*(fact-1)/2
        xmin -= adj
        xmax += adj
    else: # Want to put a map into an layoutitem that is too high
        fact=map_aspect/det_aspect
        adj=detail_extent.height()*(fact-1)/2
        ymin -= adj
        ymax += adj
    fitted_extent = QgsRectangle(xmin,ymin,xmax,ymax)
    return fitted_extent #QgsRectangle
0

I needed a bit more customisation than what Monticolo's answer would allow, so this is what I ended up doing. For each page in the atlas, defined by the detail map, find the corresponding polygon in the overview data set, set the extent of the overview map using the overview polygon. (And in case the overview polygon does not exist, use the same extent on both maps)

I do not want the shape of the overview map to change, therefore I am adjusting the bounding box to fit into the size of the defined overview map.

@qgsfunction(args='auto', group='Custom')
def getextent(feature,parent):
    overviewlayername='punkt_oversikt'
    layoutname='detaljkart-atlas'
    searchfield='navn'
    overviewmap='Hovedkart'
    detailmap='Detaljkart'
    projectInstance= QgsProject.instance()
    projectLayoutManager = projectInstance.layoutManager()
    layout=projectLayoutManager.layoutByName(layoutname)
    atl=layout.atlas()
    pg=atl.nameForPage(atl.currentFeatureNumber()) # The same as returned by @atlas_pagename
    ovLayer = QgsProject.instance().mapLayersByName(overviewlayername)[0] # The one and only
    xpr='"{}" = \'{}\''.format(searchfield,pg) # Search string - same format as used in filter
    expr = QgsExpression(xpr)
    it = ovLayer.getFeatures( QgsFeatureRequest( expr ) ) 
    n=0
    for item in it: # Only one item is to be expected
        n+=1
        bb=item.geometry().boundingBox()
    if n==0: 
        # In case no items are found, use the same extent for both maps
        detmap=layout.itemById(detailmap)
        bb=detmap.extent()
    xmax=bb.xMaximum()
    ymax=bb.yMaximum()
    xmin=bb.xMinimum()
    ymin=bb.yMinimum()
    aspect=bb.width()/bb.height()
    ovmap=layout.itemById(overviewmap)
    ovbb=ovmap.extent()
    ovaspect=ovbb.width()/ovbb.height()
    if ovaspect>aspect: # Want to put a map into an layoutitem that is too wide
        fact=ovaspect/aspect
        adj=bb.width()*(fact-1)/2
        xmin=xmin-adj
        xmax=xmax+adj
    else: # Want to put a map into an layoutitem that is too high
        fact=aspect/ovaspect
        adj=bb.height()*(fact-1)/2
        ymin=ymin-adj
        ymax=ymax+adj
    bbox=QgsRectangle(xmin,ymin,xmax,ymax)
    ovmap.setExtent(bbox)
    return "" # This is called from a textbox -

The function is being called by an invisible textbox. I have to look into a more elegant solution, but that is another question.

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.