Another guest blog from Liam Mason, based on his lightning talk at the 8th Scottish QGIS user group. You can follow Liam on Twitter via @marinemaps
A2. Hit. You’ve sunk my battleship! 🙁
If you’ve used a street atlas or played the boardgame Battleship, you’ll be familar with grid systems using letters for the horizontal (x) coordinate and numbers for vertical (y) coordinate.
Whilst these grids don’t have the resolution of a coordinate system like latitude/longitude or eastings/northings, they allow readers to quickly identify where a street is located on an atlas, or a ship on a boardgame.
Creating these type of grids in QGIS takes advantage of the grid options in composer, the @gridnumber attribute, and a wee bit of maths.
Setting up the print composer
Create your map. In this example, I’ve used data from Natural Earth and projected on World Mercator / ESPG:3395 (because I want meridians and parallels of latitude & longitude to appear straight)
Create a new print composer using Project > New Print Composer (or Ctrl & P)
Use the Add new map button in Composer to add the map content.
(Leave space around the map for the graticules.)
Adding a latitude / longitude graticule with custom format
Click on the map (boxes will appear in corners to show it’s selected). Click the Item Properties tab, scroll down to Grids, and expand using the wee triangle.
Add a grid using the + button. Double-click on the name (Grid 1) and rename to something like ‘Grid lines’
I want my grid to use decimal degrees on WGS84 datum, so I select a CRS of EPSG:4326.
Using an X interval of 1 decimal degree and Y interval of 0.5 decimal degrees produces a grid that’s roughly rectangular.
Scroll down the item properties (past grid frame), and tick Draw coordinates. I only want the graticules down the right hand side and bottom, so I set Left and Top to Disabled.
I want a more truncated latitude & longitude and use a custom format.
Set Format to Custom, use the expression button (to right of format) and expression based annotation .
abs(@grid_number) || '°' || CASE WHEN @grid_axis = 'x' THEN IF (@grid_number > 0, 'E' , 'W') ELSE IF (@grid_number > 0, 'N' , 'S') END
This expression returns an absolute value of @grid_number. Since the CRS is EPSG:4326, @grid_number is decimal degrees, and would normally return a negative value for longitudes west of 0.
The rest of the expression concatenates (||) the degree symbol, and a conditional statement.
When the grid axis is X, and value is greater than zero, append E for east, otherwise append W for west. If grid axis isn’t X (i.e Y) and and value is greater than zero, append N for north, otherwise append S for south.
Now I have latitude and longitude down right and bottom sides in the desired format.
Adding a numeric value
Scroll back up and add an other grid using the + button. Rename this one to something like ‘numeric’. This grid will be used for the numeric grid reference.
This time I only want annotations, so I change the Grid type to Frame and annotations only. The CRS again will be EPSG:4326 and the Y interval will be 0.5 but I want the numeric annotation to be in the middle of the grid lines, so add an Offset of 0.25 (half of interval).
Scroll down the item properties to Draw coordinates. I only want the numeric graticules down the left so I set Right, Bottom, and Top to Disabled.
Set Format to Custom, use the expression button and use expression based annotation . Note in the example above, 49°N is most southern latitude.
((@grid_number-49)+0.25) / 0.5
The expression calls the grid number (decimal degrees including 0.25 offset), subtracts the lowest X value (49), adds the offset (0.25), and divides by the coordinate interval (0.5). (For values to start at zero, subtract the offset)
This creates a numeric grid which increases in a northerly direction. For a grid to increase in a southerly direction, use:
This similar to above, but substracts the largest X value and converts the number to an absolute value)
Adding a letter value
Scroll back up and add an other grid using the + button. Rename to something like ‘letters’.
Again I only want annotations, so similar to previous settings except the X interval will be 1 with an Offset of 0.5
Scroll down the item properties to Draw coordinates. I want the letters on the top so I set Left, Right, and Bottom to Disabled.
Set Format to Custom and use the expression button to select expression based annotation . Note in this example, 13°W was the furthest west (-13)
substr('ABCDEFGHIJKLMNOPQRST', ((@grid_number- -13) + 0.5) / 1 , 1)
The expression works like the numeric grid in that it calls the grid number (decimal degrees including offset), substracts the smallest Y number (-13), adds the offset (0.5), and divides by the coordinate interval (1).
This is nested within a substring query which calls a single character (defined by the length of 1) for the corresponding grid number . For example where the grid number is 3, the the third character, C, would be displayed.
And there you have it! A real world version of Battleship, (or a custom street atlas.)
The ICES statistical rectangle grid (a grid with more than one character)
My incentive for looking into these types of grids was to annotate fisheries maps with ICES statistical rectangles, which use alphanumeric codes to reference areas of the north east Atlantic.
I use the same X & Y internal and offset values as the examples above but different format expressions.
The numeric encoding is familiar enough. The differences from the examples above is that the smallest X is 36N and I’ve used left padding (lpad) to force leading zeroes to be displayed (eg 01, 02, 03…)
lpad(((@grid_number-36)+0.25) / 0.5, 2, 0)
The letter encoding is a wee bit different.
substr('A0A1A2A3B0B1B2B3B4B5B6B7B8B9C0C1C2C3C4C5C6C7C8C9D0D1D2D3D4D5D6D7D8D9E0E1E2E3E4E5E6E7E8E9F0F1F2F3F4F5F6F7F8F9G0G1G2G3G4G5G6G7G8G9H0H1H2H3H4H5H6H7H8H9J0J1J2J3J4J5J6J7J8J9K0K1K2K3K4K5K6K7K8K9L0L1L2L3L4L5L6L7L8L9M0M1M2M3M4M5M6M7M8', (@grid_number - -44) / 0.5 , 2)
The smallest Y is 44, but this time I need the grid value to increase by two at each step. I achieve this by dividing by 0.5 (half the interval of 1), I don’t correct for the offset (otherwise the first value would be 2 instead of 1), and the substring now has a length of 2.
The finished article, ICES rectangle annotations which work anywhere in NE Atlantic.