24

I'm using QGIS 2.18. I need to create sector lights for navigational purposes on a map.

I have the light sector data as fields giving becon_id, start degree, end degree and color in a shapefile with over 500 buoys, beacons and lighthouses that need to be shown on the map. For each beacon, there can be many rows, each describing one light sector (for instance a white sector)

The end result should look about like below: The light sectors in the right colors, with the color marked as the character in the color field (R G W), and dotted lines from 100m to 1000m from the buoy/beacon/lighthouse.

This should most likely be created as a ruled-based symbol, but needs some python I guess?

enter image description here

Here below is an example of the shapefile data for a lighthouse (unfortunately not the above one) which has a green sector between 114 and 154 degrees, a white sector between 154 and 168 degrees, a red sector between 168 and 237 degrees, a green sector between 237 and 314 degrees, a white sector between 314 and 320 degrees, a red sector between 320 and 337 degrees (for some reason, 0 is not north but south):

shapefile table example

  • 1
    related gis.stackexchange.com/questions/170950/… – underdark Feb 26 '17 at 10:29
  • 2
    Please, could you upload a sample dataset and edit your question by elaborating what exactly is the result you expect? In the attached image I only see a universe of symbols and colors. – mgri Feb 26 '17 at 11:36
  • 1
    Example data would help here. Do you have one feature per sector light, or one feature per buoy? Wedge Buffer plugin might help here, but how easy this is will depend on how your data is set up. – Steven Kay Feb 26 '17 at 13:05
  • Hi @mgri and Steven , I added example data and tried to make the question clearer :), thanks! – Benjamin Donner Mar 1 '17 at 18:38
  • 1
    @mgri the lines are not a variable, but lines that should statically be shown as 900m long lines between the light sectors like in the image. Projected reference system. – Benjamin Donner Mar 1 '17 at 20:58
47

EDIT I edited the answer for managing particular situations (due to specific angle values) and for not displaying the dotted lines when a round angle is defined.


I propose a solution by only recurring to rule-based symbology and labeling.

Before starting, I want to underline that I will focus the attention on the explanation of the minimal things to do for reproducing the desired result: this means that some other minor parameters (like sizes, widths and so on) should be easily adjusted by you for better fitting your needs.

Furthermore, this solution only works if you assume that 0 degree is North instead of South (if 0 is South, instead, it would be enough summing a 180 value every time that appears a '90' in formulas which deal with angles, e.g. cos(radians(90)) would become cos(radians(180 + 90))). I preferred doing this only for the sake of giving a more general solution.


Styling

We will render the points with a Single symbol and by recurring to one Simple Marker and three Geometry generator symbol layers:

enter image description here

In the further explanation, I will follow the same order of the symbols in the image above.

1) Simple Marker

I chose a default symbol of a black star (this is the easier part of this tutorial), having a size of 3 mm and a width of 0.4 mm.

2) Geometry Generator No. 1

Add a new symbol layer and select the Geometry generator type:

enter image description here

Insert this expression in the Expression field:

CASE
WHEN abs( "ALKUKULMA" - "LOPPUKULMA") < 360
THEN
make_line(
 $geometry,
 make_point(
  $x + 1000*cos(radians(90 - "ALKUKULMA")),
  $y + 1000*sin(radians(90 - "ALKUKULMA"))
  )
)
END

We have just defined the first line which points towards the point where the light sector starts from. This line is 1000 m long and it's created only when the opening angle of the sector light is not a round angle (this happens for avoiding that the line would break a whole circle).

3) Geometry Generator No. 2

Same as above but, in this step, you need to use this expression:

CASE
WHEN abs( "ALKUKULMA" - "LOPPUKULMA") < 360
THEN
make_line(
 $geometry,
 make_point(
  $x + 1000*cos(radians(90 - "LOPPUKULMA")),
  $y + 1000*sin(radians(90 - "LOPPUKULMA"))
  )
)
END

We have just defined the first line which points towards the point where the light sector ends at. This line is 1000 m long and it's created only when the opening angle of the sector light is not a round angle (this happens for avoiding that the line would break a whole circle).

4) Geometry Generator No. 3

Insert this expression in the Expression field:

CASE

WHEN abs("ALKUKULMA" - "LOPPUKULMA") <= 180 AND "ALKUKULMA" >= "LOPPUKULMA"
THEN
difference(
 boundary(
  buffer(
   $geometry, 900)
   ),
  make_polygon(
   geom_from_wkt(
    geom_to_wkt(
     make_line(
      $geometry,
      make_point($x + 2000*cos(radians(90 - "ALKUKULMA" )), $y + 2000*sin(radians((90 - "ALKUKULMA" )))),
      make_point($x + 2000*cos(radians(90 - ("LOPPUKULMA" + "ALKUKULMA")/2 )), $y + 2000*sin(radians((90 - ("LOPPUKULMA" + "ALKUKULMA")/2 )))),
      make_point($x + 2000*cos(radians(90 - "LOPPUKULMA")), $y + 2000*sin(radians((90 - "LOPPUKULMA")))),
      $geometry)
   )  
  )
 )
)

WHEN abs("ALKUKULMA" - "LOPPUKULMA") <= 180 AND "ALKUKULMA" <= "LOPPUKULMA"
THEN
intersection(
 boundary(
  buffer(
   $geometry, 900)
   ),
  make_polygon(
   geom_from_wkt(
    geom_to_wkt(
     make_line(
      $geometry,
      make_point($x + 2000*cos(radians(90 - "ALKUKULMA" )), $y + 2000*sin(radians((90 - "ALKUKULMA" )))),
      make_point($x + 2000*cos(radians(90 - ("LOPPUKULMA" + "ALKUKULMA")/2 )), $y + 2000*sin(radians((90 - ("LOPPUKULMA" + "ALKUKULMA")/2 )))),
      make_point($x + 2000*cos(radians(90 - "LOPPUKULMA")), $y + 2000*sin(radians((90 - "LOPPUKULMA")))),
      $geometry)
   )  
  )
 )
)

WHEN abs("ALKUKULMA" - "LOPPUKULMA") > 180 AND "ALKUKULMA" >= "LOPPUKULMA"
THEN
intersection(
 boundary(
  buffer(
   $geometry, 900)
   ),
  make_polygon(
   geom_from_wkt(
    geom_to_wkt(
     make_line(
      $geometry,
      make_point($x + 2000*cos(radians(90 - "ALKUKULMA" )), $y + 2000*sin(radians((90 - "ALKUKULMA" )))),
      make_point($x - 2000*cos(radians(90 - ("LOPPUKULMA" + "ALKUKULMA")/2 )), $y - 2000*sin(radians((90 - ("LOPPUKULMA" + "ALKUKULMA")/2 )))),
      make_point($x + 2000*cos(radians(90 - "LOPPUKULMA")), $y + 2000*sin(radians((90 - "LOPPUKULMA")))),
      $geometry)
   )  
  )
 )
)

WHEN abs("ALKUKULMA" - "LOPPUKULMA") > 180 AND "ALKUKULMA" <= "LOPPUKULMA"
THEN
difference(
 boundary(
  buffer(
   $geometry, 900)
   ),
  make_polygon(
   geom_from_wkt(
    geom_to_wkt(
     make_line(
      $geometry,
      make_point($x + 2000*cos(radians(90 - "ALKUKULMA" )), $y + 2000*sin(radians((90 - "ALKUKULMA" )))),
      make_point($x - 2000*cos(radians(90 - ("LOPPUKULMA" + "ALKUKULMA")/2 )), $y - 2000*sin(radians((90 - ("LOPPUKULMA" + "ALKUKULMA")/2 )))),
      make_point($x + 2000*cos(radians(90 - "LOPPUKULMA")), $y + 2000*sin(radians((90 - "LOPPUKULMA")))),
      $geometry)
   )  
  )
 )
)


END

We have just defined the arc between the starting and ending points of the light sector (please note that 2000 is an arbitrary value because I'm trying to create a polygon to intersect with the boundary of the circle having a radius of 900 m).

Furthermore, we need to set the color which is stored in the "VARIS" field. For doing this, we need to specify it with a custom expression. Follow the arrow in the image below:

enter image description here

and then type this expression after having clicked on the Edit... button:

CASE
WHEN  "VARIS" = 'vi' THEN color_rgb(51,160,44)
WHEN "VARIS" = 'v' THEN color_rgb(255,255,255)
WHEN "VARIS" = 'p' THEN color_rgb(227,26,28)
END

Please note that, for this symbol layer, I created two lines: the upper line defines the color to use (in fact I set the custom expression for this one), while the lower one is useful for defining a black border (it will have a width which is bigger than the one of the upper line). Remember also to set Flat as Cap style for both lines for avoiding any color overlapping.


Labeling

1) Setting the labels

Go to Layer Properties > Labels and, as usual, follow the red arrows:

enter image description here

and then type this expression:

CASE
WHEN "VARIS" = 'vi' THEN 'G'
WHEN "VARIS" = 'v' THEN 'W'
WHEN "VARIS" = 'p' THEN 'R'
END

We have just defined the color rule using the value stored in the "VARIS" field.

2) Setting the placement for labels

Select the Placement option in the Labels Menu and select Offset from point.

Then, with reference to the image below:

enter image description here

follow the red arrow and type this expression:

CASE
WHEN "ALKUKULMA" > "LOPPUKULMA"
THEN
concat(
 -1000*cos(radians(90 - ("ALKUKULMA" + "LOPPUKULMA")/2)),
  ',',
  1000*sin(radians(90 - ("ALKUKULMA" + "LOPPUKULMA")/2))
)
WHEN "ALKUKULMA" <= "LOPPUKULMA"
THEN
concat(
 1000*cos(radians(90 - ("ALKUKULMA" + "LOPPUKULMA")/2)),
  ',',
  -1000*sin(radians(90 - ("ALKUKULMA" + "LOPPUKULMA")/2))
)
END

Then, follow the green arrow and type this expression:

CASE
WHEN "ALKUKULMA" >= "LOPPUKULMA"
THEN
180-(("ALKUKULMA" + "LOPPUKULMA")/2)
WHEN "ALKUKULMA" < "LOPPUKULMA"
THEN
- (("ALKUKULMA" + "LOPPUKULMA")/2)
END

Final result

If you correctly performed the previous tasks, you should be able to get this result:

enter image description here

Bonus

Since the minor parameters were too many for being completely covered within this answer, I have attached the style here: you may open this code with any text editor and save it as a QGIS Layer Style file (i.e. with a .qml extension).

The above style was created using QGIS 2.18.4 (it must have the same name of the shapefile you are using).

  • 3
    Looks great, really shows you the power of the geometry generator. Is it slow to render? – HeikkiVesanto Mar 7 '17 at 9:21
  • 2
    @Vesanto I tested it on one point having six features (i.e. six sector lights) and it rendered instantly. I think it should be fast also when dealing with hundreds of features because there's no call to providers or something similar, but only a couple of math operations and geometries as Well Know Text. – mgri Mar 7 '17 at 9:27
  • 2
    Questions/answers like these really show how versatile QGIS can be! – Joseph Mar 7 '17 at 10:24
  • 1
    @mgri you are The Master, a fantastically good solution and a great explanation involving a lot of work, THANK YOU!! – Benjamin Donner Mar 10 '17 at 9:02
  • 1
    @mgri a mountain in this region should be named after you, thank you!! I have tested a bit and no problems with your added solutions have been found :)! – Benjamin Donner Mar 26 '17 at 6:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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