I assume you have either an ocean polygon layer or a coastline line layer. If not, you can create one by making a large polygon that covers your entire layer of interest, and use the
clip tool to cut holes in it where the land is. Substitute the name of this layer in the expressions where they say
Coastline (thin blue line)
For the coastline, simply apply a thin blue line style to your coastline layer, or a thin blue boundary line to your ocean polygon layer.
Add three rule-based styles to the administrative areas layer. The first two rules will be the boundaries. The third rule will be the polygon fill.
High-level administrative land-border (thick grey line)
Filter the first rule to the high-level administrative areas. Apply a line geometry generator style using this expression:
difference( boundary( $geometry), buffer(collect('coastline'),42))
Substitute a reasonable, small buffer distance where my expression says 42. Buffer distance is in the same units as the layer's CRS. "Reasonable" depends on the scale of your map and how closely your coastline layer lines up with your administrative boundaries layer. The goal is to clip the entire section of the administrative boundary along the coast, without leaving a noticeable gap.
Apply a thick grey line style.
Low-level administrative land-border (thin grey line)
Filter the second rule to the low-level administrative areas. Use the same geometry generator settings and expression. Apply a thin grey line style.
For the third rule, don't apply a filter. Apply a simple fill style, and set the outline pen type to "no pen."
Explanation of the expression
difference( boundary( $geometry), buffer('coastline',42))
boundary($geometry) converts the current polygon boundary into a line
difference(geometry_a,geometry_b) removes the part of geometry_a that intersects with geometry_b
collect('coastline') merges the 'coastline' layer into a single feature. This step is needed because the
difference() formula requires a single feature input.
buffer('coastline', distance) creates a buffer around the coastline layer, just in case the coastline doesn't exactly match up with the admin boundary.
- If the 'coastline' layer does match the admin boundary exactly, you could omit the buffer function, and just use
difference( boundary($geometry), 'coastline')
difference( boundary , buffer('coastline') ) subtracts the buffered coastline from the administrative boundary, leaving only the part of the boundary that isn't on the coast
Using a complicated expression like this in the geometry generator can cause slow rendering, because QGIS has to run all the steps of the expression "on the fly". If you have a problem with slow rendering, you can shave some time off the rendering process by performing some of the steps outside the expression. For example, dissolve the coastline layer into a single feature, clip it to your area of interest, and buffer the result. Omit the buffer function from the expression, and just use this layer. Use it in the
coalesce('newlayer') function or by extracting the single feature using
geometry(get_feature('newlayer','anyattribute',attributevalue). I'm not sure which method will be faster.