0

I have a leaflet web map displaying 13 points and 3 arrows diverging from each point (4 layers total). The map was exported from QGIS via QGIS2Web.

  • all layers (including points) have exactly the same data (same set of coordinates to display points & three arrow layers)
  • each arrow is rotated based on a field value (left arrow based on lhead, right arrow based on rhead, heading based on head field)
  • arrows are custom SVGs. Left & right use the same SVG while heading uses another style

Problem

With help from @TomazicM I have gotten this far. The last thing is to anchor the arrows in place because they currently overlap when zoomed out (1), and “separate” from the points when zoomed in (2). I want the arrow bases to always be touching the points and stop shifting around when zooming:

enter image description here

This is what I want them to look like, regardless of scale:

enter image description here

Code Sample

//==============================================================
// Define icon dimensions for custom icons
// Define custom icons for each symbol (1 point, 3 arrows)
//==============================================================
// LR arrow icon dimensions
const LR_Width = 64;
const LR_Height = 64;
// Heading arrow icon dimensions
const H_Height = 40;
const H_Width = 40;
// sensor marker dimensions
const circleRadius = 6; 

// 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);
});

Full Code Here

Link to full code

2
  • 1
    Why do you calculate position of arrow icons? You give them different geodesic position than circle icon, so it's natural that when you zoom in they move apart from each other and when you zoom out they move closer to each other. Since circle radius is constant, why don't you use solution from this answer: gis.stackexchange.com/questions/478580/…
    – TomazicM
    Commented Apr 13 at 12:38
  • Thank you so much @TomazicM, i've finally gotten the code from your previous post to work with my code and data. Sorry, I'm pretty new to this so I really appreciate your help and patience with my problem! Commented Apr 18 at 18:30

1 Answer 1

0

Update

The arrows are very close to working and only a minor adjustment needs to be made to the heading (yellow arrow) offset. This is considered solved. Thank you for your great help TomazicM who provided a solution in my previous post. I have adapted that code for my project. Here is the code:

//==============================================================
// Define custom icons for each symbol (1 point, 3 arrows)
//==============================================================
// Sensor marker dimensions
const circleRadius = 6; 
// LR arrow icon dimensions
const LR_Width = 64;
const LR_Height = 64;
// Heading arrow icon dimensions
const H_Height = 40;
const H_Width = 40;

// 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 + circleRadius] 
});
// Right arrow SVG
var R_arrow = L.icon({
    iconUrl: 'markers/test2.svg',
    iconSize: [LR_Width, LR_Height],
    iconAnchor: [LR_Width / 2, LR_Height + circleRadius] 
});

// Heading arrow SVG
var H_arrow = L.icon({
    iconUrl: 'markers/head.svg',
    iconSize: [H_Width, H_Height],
    iconAnchor: [H_Width / 2, H_Height + circleRadius] 
});

//===============================================
// Function to place rotated symbols 
//===============================================
function placeRotatedSymbol(coords, lhead, rhead, head) {
    //==================================
    // Calculate offsets for left arrow
    //==================================
    var L_offsetX = Math.cos((lhead - 90) * Math.PI / 180) * 0.00000000000009;
    var L_offsetY = Math.sin((lhead - 90) * Math.PI / 180) * -0.00000000000009;
    var L_offset = [coords[1] + L_offsetY, coords[0] + L_offsetX];
    // Add left arrow marker
    var leftMarker = L.marker(L_offset, {
        icon: L_arrow,
        rotationAngle: lhead
    });
    //==================================
    // Calculate offsets for right arrow
    //==================================
    var R_offsetX = Math.cos((rhead - 90) * Math.PI / 180) * 0.00000000000009;
    var R_offsetY = Math.sin((rhead - 90) * Math.PI / 180) * -0.00000000000009;
    var R_offset = [coords[1] + R_offsetY, coords[0] + R_offsetX];
    // Add right arrow marker
    var rightMarker = L.marker(R_offset, {
        icon: R_arrow,
        rotationAngle: rhead
    });
    //==================================
    // Calculate offsets for heading arrow
    //==================================
    var H_offsetX = Math.cos((head - 90) * Math.PI / 180) * -0.00000000000009;
    var H_offsetY = Math.sin((head - 90) * Math.PI / 180) * -0.00000000000009;
    var H_offset = [coords[1] + H_offsetY, coords[0] + H_offsetX];
    // Add heading arrow marker
    var headingMarker = L.marker(H_offset, {
        icon: H_arrow,
        rotationAngle: head
    });

    return [leftMarker, rightMarker, headingMarker];
}

//======================================================
// Loop through each feature in the data & place markers
//======================================================
data.features.forEach(function(feature) {   
    // Define variables to access fields (sensor coords, rotation angles for arrows)
    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 arrow angle

    // Call the function to place the rotated symbols on the map for each feature
    var markers = placeRotatedSymbol(coords, lhead, rhead, head); 

    // Add circle marker for each point
    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);
3
  • Why do you place each arrow marker at different coordinate? There is absolutely no need for that. Just place each marker at the same coordinate.
    – TomazicM
    Commented Apr 18 at 19:17
  • I thought I had to place each arrow at a different coordinate because there are 13 points and they all have their own arrows. Commented Apr 19 at 19:03
  • Just try putting all arrows at the same coordinate. You'll se it works. Then update your new questions with modified code.
    – TomazicM
    Commented Apr 19 at 19:56

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.