henrythasler / Leaflet.Geodesic

Add-on to draw geodesic lines with leaflet
GNU General Public License v3.0
155 stars 27 forks source link

easy hack for coloring paths with a gradient #110

Closed vann closed 1 month ago

vann commented 2 months ago

This is just a suggestion of something to add to the documentation; not an issue.

I wanted to draw paths that were colored using a gradient indicating direction.

This solution I have is not perfect; using linear gradients in this way is a bit of a hack. The code is simple; it just looks at whether the start point of the geodesic is east or west of the end point. Then it uses that to set the color option of the geodesic. Instead of a color, it sets it to a url that points to an SVG linear gradient that you've defined in your html.


<script>
...
let bEastIsStart = pts[0].lng < pts[1].lng;
let color = bEastIsStart  ? "url(#etow-grad)" : "url(#wtoe-grad)";
let geoDesic = new L.Geodesic(pts, { color: color}).addTo(map);
</script>

<html> 
..
  <svg>
    <defs>
      <linearGradient id="etow-grad">
        <stop offset="0" stop-color="green"></stop>
        <stop offset="1" stop-color="red"></stop>
      </linearGradient>
      <linearGradient id="wtoe-grad">
        <stop offset="0" stop-color="red"></stop>
        <stop offset="1" stop-color="green"></stop>
      </linearGradient>
   </defs>
 </svg>

Here's an example where the start color is yellow, not green:

signal-2024-05-16-22-39-43-643

henrythasler commented 2 months ago

Great idea! I will add this as an example.

henrythasler commented 1 month ago

I refined your demo a bit and added it as an example: https://blog.cyclemap.link/Leaflet.Geodesic/flightpath-animated.html

vann commented 1 month ago

Animated; very nice!

Separate topic: but I figured out how to cluster geodesics so that they work with the leaflet.markercluster plugin.

I just built a reverse lookup table that takes zoom level & marker and returns the cluster marker. Then it draws geodesics using those cluster markers.

On the left are the clustered geodesics, on the right was the mess I had before:

[image: geodesic clustering.png]

let gClusterMarkerLookup = {};

const getClusteredArcKey = (lat, lng, zoom) => { let key = ${zoom},${lat},${lng}; return key; };

let buildClusterLookup = (clusterLayer) => { const cacheChildMarkers = (cluster) => { if (!cluster) return; let zoom = cluster._zoom; let children = cluster.getAllChildMarkers(); if (!children) return; children.forEach((childMarker) => { let lat = childMarker._latlng.lat; let lng = childMarker._latlng.lng; let key = getClusteredArcKey(lat, lng, zoom); if (!gClusterMarkerLookup[key]) gClusterMarkerLookup[key] = cluster.getLatLng(); }); for (let c of cluster._childClusters) cacheChildMarkers(c); };

clusterLayer.eachLayer((clusterMarker) => { let parent = clusterMarker.__parent; cacheChildMarkers(parent); }); };

// create and add cluster layer to map

clusterLayer = L.markerClusterGroup({ showCoverageOnHover: false, zoomToBoundsOnClick: true, spiderfyOnMaxZoom: true, disableClusteringAtZoom: disableClusteringAtZoomLevel, iconCreateFunction: ourClusterMarkerCreate, });

clusterLayer.addTo(map);

clusterLayer.addLayer(marker1); clusterLayer.addLayer(marker2); clusterLayer.addLayer(marker3);

// ... we've added some markers to the cluster layer. build the reverse lookup table: buildClusterLookup(clusterLayer);

// now, for each marker, lookup cluster markers to use for our geodesic's start and endPts let startKey = getClusteredArcKey(startPt.lat, startPt.lng, zoom); let startCluster = gClusterMarkerLookup[startKey]; let endKey = getClusteredArcKey(endPt.lat, endPt.lng, zoom); let endCluster = gClusterMarkerLookup[endKey];

// optional: check whether the geodesic either starts or ends in current map view: if (bounds && start && end) { let startVisible = bounds.contains(start); let endVisible = bounds.contains(end); if (!startVisible && !endVisible) return; }

let geoDesic = new L.Geodesic([startCluster, endCluster], geodesicOptions).addTo(map);

-V

On Sun, Jun 2, 2024 at 6:40 AM Henry Thasler @.***> wrote:

I refined your demo a bit and added it as an example: https://blog.cyclemap.link/Leaflet.Geodesic/flightpath-animated.html

— Reply to this email directly, view it on GitHub https://github.com/henrythasler/Leaflet.Geodesic/issues/110#issuecomment-2143814243, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAB4PL46AWU4DSBHJQX3BNLZFMADJAVCNFSM6AAAAABH3FEMS6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNBTHAYTIMRUGM . You are receiving this because you authored the thread.Message ID: @.***>

-- (415) 359-4069 - LinkedIn http://www.linkedin.com/in/vannm

henrythasler commented 1 month ago

I'm afraid the email reply system ate the images in your post...

vann commented 1 month ago

geodesic clustering

vann commented 1 month ago

Wow email really munged that email. Here's some better formatting for the code I sent.

This code doesn't work as is but I'm sure you can follow...


let gClusterMarkerLookup = {};

const getClusteredArcKey = (lat, lng, zoom) => {
    let key = `${zoom},${lat},${lng}`;
    return key;
  };

  let buildClusterLookup = (clusterLayer) => {
    const cacheChildMarkers = (cluster) => {
      if (!cluster) return;
      let zoom = cluster._zoom;
      let children = cluster.getAllChildMarkers();
      if (!children) return;
      children.forEach((childMarker) => {
        let lat = childMarker._latlng.lat;
        let lng = childMarker._latlng.lng;

        let key = getClusteredArcKey(lat, lng, zoom);
        if (!gClusterMarkerLookup[key]) 
          gClusterMarkerLookup[key] = cluster.getLatLng();
      });
      for (let c of cluster._childClusters) cacheChildMarkers(c);
    };

    clusterLayer.eachLayer((clusterMarker) => {
      let parent = clusterMarker.__parent;
      cacheChildMarkers(parent);
    });
  };

 // create and add cluster layer to map

 clusterLayer = L.markerClusterGroup({
      showCoverageOnHover: false,
      zoomToBoundsOnClick: true,
      spiderfyOnMaxZoom: true,
      disableClusteringAtZoom: disableClusteringAtZoomLevel, 
      iconCreateFunction: ourClusterMarkerCreate,
    });

    clusterLayer.addTo(map);

clusterLayer.addLayer(marker1);
clusterLayer.addLayer(marker2);
clusterLayer.addLayer(marker3);

 // ... we've added some markers to the cluster layer. build the reverse lookup table:
buildClusterLookup(clusterLayer);

// now, for each marker, lookup cluster markers to use for our geodesic's start and endPts
      let startKey = getClusteredArcKey(startPt.lat, startPt.lng, zoom); // zoom is map's current zoom level
      let startCluster = gClusterMarkerLookup[startKey];
      let endKey = getClusteredArcKey(endPt.lat, endPt.lng, zoom);
      let endCluster = gClusterMarkerLookup[endKey];

// optional: check whether the geodesic either starts or ends in current map view:
 if (bounds && startCluster && endCluster) {
        let startVisible =  bounds.contains(startCluster);
        let endVisible = bounds.contains(endCluster);
        if (!startVisible && !endVisible) 
          return;
}

 let geoDesic = new L.Geodesic([startCluster, endCluster], geodesicOptions).addTo(map);