1

I have a map exported from QGIS (3.34 Prizren) as leaflet via QGIS2Web plugin. The map has 3 layers (identical data) but 2 are displayed as arrow SVG markers and 1 is displayed as points. Left arrows are blue and right are red. I created these 3 layers in QGIS and then exported.

enter image description here

In the leaflet code, I have applied rotation based on a the lhead field and am now trying to offset the arrows so that they appear to be coming out of the point. I need them to be offset while respecting the angle that they are rotated to (based on values lhead & rhead fields). In other words, I cannot simply offset everything a bit in the X and Y directions because the arrows must still be rotated to their respective angles, and these differ for each point. I have tried using the iconAnchor property in the arrow marker functions like so:

function style_left_2_0(feature) {
            return {
                pane: 'pane_left_2',
                rotationAngle: feature.properties['lhead'] || 0, 
                rotationOrigin: 'center center',
                icon: L.icon({
                    iconUrl: 'markers/left_2.svg',
                    iconSize: [32.68, 32.68],
                    iconAnchor: [32.34, 32.34]
                }),
                interactive: false,
            };
        }```

but this just shifts all of the blue left arrows up & to the left. I want them to be offset to the left while respecting their rotation. (ex. this left arrow’s angle is 279°, so I want it to be offset along this angle rather than along a regular horizontal plane).

enter image description here

This is what I want the end result to look like, just as I've configured in QGIS: arrows coming out of the point and offset while respecting their respective angles.

enter image description here

LINK TO FULL CODE: https://github.com/tabeh0udai/leaflet-arrows/blob/main/index.html


I have almost achieved my end result with help from @TomazicM:

//==============================================================
// Define custom icons for each symbol (1 point, 3 arrows)
//==============================================================
// Sensor marker point
const LR_Width = 64;
const LR_Height = 64;
const H_Height = 40;
const H_Width = 40;
const circleRadius = 6; //sensor marker
// Left arrow SVG (stored in Leaflet markers exported by QGIS2Web)
var L_arrow = L.icon({
    iconUrl: 'markers/test2.svg',
    iconSize: [LR_Width, LR_Height], 
    iconAnchor: [(LR_Width / 2), (LR_Height / 2)]
});
// Right arrow SVG
var R_arrow = L.icon({
    iconUrl: 'markers/test2.svg',
    iconSize: [LR_Width, LR_Height], 
    iconAnchor: [(LR_Width / 2), (LR_Height / 2)]
});
// Heading arrow SVG
var H_arrow = L.icon({
    iconUrl: 'markers/head.svg',
    iconSize: [H_Width, H_Height], 
    iconAnchor: [(H_Width / 2), (H_Height / 2)]
});


//===============================================
//Loop through each feature in the GeoJSON data
//===============================================
data.features.forEach(function(feature) {
    
    // ================================================================================================
    // Define variables to access fields (sensor coords, headings, rotation angles for directionality)
    // ================================================================================================
    var coords = feature.geometry.coordinates; // Sensor coords stored as long lat (XY)
    var lhead = feature.properties.lhead; // left arrow angles stored in data (sensors.js)
    var rhead = feature.properties.rhead; // right arrow angles
    var head = feature.properties.heading; // heading angles
    // ======================================================================
    // Add yellow sensor points to map based on coords in data (var coords)  
    // ======================================================================
    L.circleMarker([coords[1], coords[0]], { // Reverse the coords, leaflet takes lat long (YX)
        pane: 'tooltipPane',
        radius: circleRadius,
        color: '#FFFFFF',
        weight: 1,
        fill: true,
        fillColor: '#DBA044',
        fillOpacity: 1
    }).addTo(map);

    //===================================================================
    // Calculate & apply arrow offsets so arrow base diverges from point
    //===================================================================
    // ========== LEFT ARROW ============
    // calculate offset for left arrow (adjust as needed)
    var L_offsetX = Math.cos((lhead - 90) * Math.PI / 180) * 0.0003; 
    var L_offsetY = Math.sin((lhead - 90) * Math.PI / 180) * -0.00025; 
    // define offset of left arrow from sensor
    var L_offset = [coords[1] + L_offsetY, coords[0] + L_offsetX];
    // Add left arrow SVG marker with offset to map
    L.marker(L_offset, { 
        icon: L_arrow,
        rotationAngle: lhead, // Rotation angle based on left angle field value
    }).addTo(map);

    // ========== RIGHT ARROW ============   
    // calculate offset for right arrow from sensor
    var R_offsetX = Math.cos((rhead - 90) * Math.PI / 180) * 0.0003; 
    var R_offsetY = Math.sin((rhead - 90) * Math.PI / 180) * -0.00025; 
    // define offset of right arrow from sensor
    var R_offset = [coords[1] + R_offsetY, coords[0] + R_offsetX];
    // Add right arrow SVG marker with offset to map
    L.marker(R_offset, { 
        icon: R_arrow,
        rotationAngle: rhead, // field based
    }).addTo(map);

    // ========== HEADING ARROW ============   
    // calculate offset for heading from sensor
    var H_offsetX = Math.cos((head - 90) * Math.PI / 180) * 0.0001; 
    var H_offsetY = Math.sin((head - 90) * Math.PI / 180) * -0.00009; 
    // define offset of heading from sensor
    var H_offset = [coords[1] + H_offsetY, coords[0] + H_offsetX];
    // Add right arrow SVG marker with offset to map
    L.marker(H_offset, { 
        icon: H_arrow,
        rotationAngle: head, // field based
    }).addTo(map);
});

the only issue now is that I need to anchor the arrows in place so that they do not "shift" around while zooming. Here is what they look like when zooming in and out:

enter image description here enter image description here

Updated code: https://github.com/tabeh0udai/leaflet-arrows/blob/main/v2

New Post: Offset & anchor field-based arrow SVGs in Leaflet

2
  • 2
    why don't you create a unique svg icon, with both mirrored arrows and the dot in the center? you would then just need to define a rotation angle as you have already done to achieve the result, or am I wrong? Commented Mar 15 at 23:01
  • @andreaordonselli Thanks for your input, thats a nice simple solution that i'll keep in mind for the future. Although they appear similar in my images, the angles are different for each point so I'd have to create a bunch of SVGs Commented Mar 16 at 3:34

1 Answer 1

2

Since Leaflet has no built in rotation for markers, Leaflet.RotatedMarker plugin has to be used for marker rotation.

To get desired combined marker, right values have to be used for iconAnchor option when creating arrow icons. If both of original SVG icons point up (north) like this:
enter image description here enter image description here

then x value for iconAnchor option has to be half of icon width and y value has to be icon height plus radius of the circle between arrows. Rotation angle of the second arrow has to be 180 greater than for the first one.

Code for creating combined marker could then look something like this:

const iconWidth = 22;
const iconHeight = 30;
const circleRadius = 8;

var blueIcon = L.icon({
  iconUrl: 'img/arrow-up-blue.svg',
  iconSize: [iconWidth, iconHeight],
  iconAnchor: [(iconWidth / 2), (iconHeight + circleRadius)],
});
var redIcon = L.icon({
  iconUrl: 'img/arrow-up-red.svg',
  iconSize: [iconWidth, iconHeight],
  iconAnchor: [(iconWidth / 2), (iconHeight + circleRadius)],
});

function placeRotatedSymbol(coords, angle) {
  L.marker(coords, {
    icon: blueIcon,
    rotationAngle: angle,
  }).addTo(map);
  L.marker(coords, {
    icon: redIcon,
    rotationAngle: (angle + 180),
  }).addTo(map);
  L.circleMarker(coords, {
    pane: 'tooltipPane',
    radius: circleRadius,
    color: '#FFFFFF',
    weight: 1,
    fill: true,
    fillColor: '#DBA044',
    fillOpacity: 1
  }).addTo(map);
}

Call placeRotatedSymbol([48.8631169, 2.3708919], 72); would then give the following combined marker:

enter image description here

4
  • 1
    great answer. thank you for your support. Commented Mar 21 at 10:12
  • 1
    I'm not the creator of the question so I can't mark it as correct, in any case I voted for it because I think it is actually correct Commented Mar 21 at 20:21
  • 1
    @andreaordonselli Ups, sorry, I assumed it on the basis of comment content.
    – TomazicM
    Commented Mar 21 at 20:26
  • TomazicM, thank you so much for your thorough help with this, it has been very challenging to resolve over the past month. The iconAnchor formula worked perfectly and I've revised the rest of my code based on your suggestions! Commented Apr 12 at 23:02

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.