Många städer, och följaktligen etiketter för dessa, finns runt våra kuster. Därför kan det bli trångt där, men det finns ju ”leaders” i QGIS numera så det skall ju inte vara några problem. Leaders är däremot ”manuella”, och bör så vara, vilket dock kan ta mycket tid att justera och placera ut. Tänk om man kunde skapa utdragna etiketter längs kust automatiskt…
För detta använder jag ett enkelt polygonlager som bakgrund och ett enkelt textlager med ortsnamn. Namnen finns lite överallt, och det jag vill är att flytta ut så många etiketter som möjligt i havet och skapa en linje som pekar mot platsen de hör till. Jag vill även att detta skall göras automatiskt oberoende hur kusten är orienterad, och jag vill kunna justera en del parametrar i efterhand någorlunda enkelt.
Jag har skapat ett filtrerat lager med polygoner som består av ytor där jag föredrar att etiketter sparas. För att kunna återanvända stilen för textlagret som jag tänker skapa, så döper jag detta lager till ”try_label_here”.
Sååå…
Det jag tänker göra är:
- Skapa en buffert runt varje punkt med ett variabelt avstånd.
- Ta fram de delar av varje buffert som finns i den föredragna ytan.
- Placera etiketten vid den överlappande ytans tyngdpunkt.
- Om det inte finns en överlappande yta placera etiketter över punkten.
- Justera etiketten så att den ”fäster” på ”utsidan”.
Steg ett
Först och främst skapar jag en lagervariabel med namnet ”label_dist” så att jag kan använda samma värde lite överallt och ändra det smidigt vid behov.
Ett enkelt uttryck: buffer($geometry, @label_dist) skapar en buffert runt varje punkt.
Steg två
Lite problem först. Lantmäteriets geometrier för hav som jag använder var lite trasiga och topologiskt problematiska, så ”fix geometry” och ”dissolve” fick lösa det problemet.
Här skapas överlapp mellan buffertarna och havslagret.
intersection(
buffer($geometry, @label_dist),
aggregate('try_label_here','collect',$geometry))
Steg tre
Om det inte finns överlapp, så skall etiketten placeras runt ytans tyngdpunkt. Jag vill dessutom generera en linjegeometri från tyngdpunkten till originalpunkten. Jag börjar med geometrin.
make_line( pole_of_inaccessibility(
intersection( buffer($geometry, @label_dist),
aggregate('try_label_here','collect',$geometry))
,3), $geometry)
Genom att kombinera en linje och en markörlinje kan jag enkelt skapa en pil som pekar på den önskade platsen.
Nu är det dags att övergå till etiketternas placering, och då utgår jag från ”pole_of_inaccessibility()” funktionen från uttrycket ovan. Men jag behöver även ta hänsyn till etiketter utan pil, så jag kombinerar detta i nästa steg.
Steg fyra
Här används en if() sats som testar om lagren intersects() i stället för intersection(), annars är det två identiska uttryck. Om det inte ”intersect’ar” så placeras etiketten vid den ordinarie punktgeometrin.
Steg fem
För att fästa på lämpligt ställe så kommer jag att räkna ut riktningen på pilen och använda detta för att ”räkna om” till en kvadrant att fästa vid.
if(intersects(
buffer($geometry,@label_dist),
aggregate( 'try_label_here', 'collect',$geometry))
,
array_get( array(1,2,5,8,7,6,3,0),
to_int(azimuth( $geometry,
pole_of_inaccessibility(
intersection( buffer($geometry,@label_dist),
aggregate( 'try_label_here', 'collect',$geometry) )
,3)) / (pi()/4))),4)
Jag tror att ovanstående kan kräva lite förklaring…
Först och främst så finns här samma if() sats för att kolla om det är en ”piletikett” eller ej, och om det är det så skall etiketten placeras ovanpå punkten, vilket är värde ”4” för kvadrant (Quadrant) inställningarna. Detta är den sista siffran ”4” i uttrycket.
Sedan så har jag en array_get(array(….) ) del som använder ett uttryck, som jag återkommer till strax, för att välja ett kvadrantvärde från en lista. Ordningen är viktig då jag vill att dessa skall komma i medurs ordning med början i norr.
Uttrycket som används för att räkna ut vilket värde i listan som skall användas beräknar riktningen med azimuth() från ursprungspunkten till pilens startpunkt. Resultatet är en riktning i radianer, så om jag vill dela detta i åtta tårtbitar så delar jag med pi/4 och väljer heltalsdelen av detta. Då har jag ett värde som börjar med ”0” rakt norr ut och ett åttondels varv till höger, sedan kommer ”1” och så vidare. Jag hade kunnat göra det mer exakt genom att kompensera ytterligare pi/8 för att sektorerna skall vridas helt korrekt, men detta räcker.
Det är inte perfekt överallt och det går ganska långsamt, men det verkar fungera skapligt. Kan vara en användbar stil att plocka fram ibland. Glöm inte att det är en variabel (@label_dist) som styr generell längd på pilarna och att det måste finnas ett try_label_here lager (med korrekta geometrier) som beskriver där man föredrar att etiketter placeras.
Man behöver sedan inte skapa linjer och flytta etiketter, det går exempelvis att endast använda uttryck för kvadrant och se till att etiketterna placeras på ”sjösidan”.
Tycker man att roterade etiketter är ok, så kan man modifiera uttrycken så att etiketterna roteras ”ut mot havet” också.
Hejsan. Jag får det inte att fungera. Är inte säker på att jag skriver rätt kod på rätt plats. Jag ska påpeka att lagret jag vill ha etiketter till är ett ytlager, inte punkter som i ditt exempel.
Jag skriver in ”intersection(
buffer($geometry, @label_dist),
aggregate(’try_label_here’,’collect’,$geometry))” i geometrigeneratorn för lagrets symbologi. Den här biten verkar fungera som den ska.
Sedan tänker jag att jag ska skriva in övriga koden, den som är IF(), i lagrets etikettkontroll under geometrigeneratorn där. Jag får då responsen ”Uttryckets resultat är inte en geometri” från QGIS.
Ser du direkt vad jag gjort fel eller har du någon ledtråd jag kan försöka följa? Jag tror att det kan vara något enkelt fel jag gör eftersom jag inte är van att använda geometrigeneratorn.
Tack för att du delar med dig av dina fiffiga lösningar . /Rasmus Runhem
Jag har inget direkt tips som säkert löser ditt problem.
Försök hitta sätt att förenkla och avgränsa koden på olika sätt. Om du tror att det är if() som kan vara problemet, prova att ta bort allt utom ditt villkor och testa bara det. Du kan även prova att endast prova geometriuttrycket för sant och falskt var för sig. Om if(), sant och falskt fungerar var för sig, så bör allt tillsammans också fungera.