Leaflet – Applying Layer Control to Custom Markers on Leaflet Map

leaflet

I have a Leaflet map with custom markers.

Recently I added Layer Control so that I can make use of a legend to filter the markers.

I've managed to add the legend however it appears to recreate all the markers on top of my custom ones and filters those instead.

I know I'm doing something wrong in the code but can't figure out where.

How can I filter my custom markers using the legend/layer control ?

Here is my jsfiddle

var markersA = [];
var markersB = [];
var markersC = [];

//Loop through the initial array and add to two different arrays based on the specified variable
for (var i = 0; i < data.length; i++) {
  switch (data[i].Category) {
    case 1:
      markersA.push(L.marker([data[i].lat, data[i].lng]));
      break;
    case 2:
      markersB.push(L.marker([data[i].lat, data[i].lng]));
      break;
    case 3:
      markersC.push(L.marker([data[i].lat, data[i].lng]));
      break;
    default:
      break;
  }
}

//add the groups of markers to layerGroups
var groupA = L.layerGroup(markersA);
var groupB = L.layerGroup(markersB);
var groupC = L.layerGroup(markersC);

const myAPIKey = "**";

var tileLayer = {
  'Filter drop off Points:': L.tileLayer('http://{s}.tile.cloudmade.com/{key}/22677/256/{z}/{x}/{y}.png')
};

const layer = 'https://api.maptiler.com/maps/uk-openzoomstack-night/{z}/{x}/{y}.png?key=tbRG4BpdjpPfpJ5rjwZY'

const layer2 = 'https://api.maptiler.com/maps/voyager/{z}/{x}/{y}.png?key=tbRG4BpdjpPfpJ5rjwZY'

var osm = L.tileLayer(layer)

const map = L.map('map', {
  center: data[data.length - 12],
  zoomControl: true,
  zoom: 17,
  layers: [osm, groupA, groupB, groupC]
});

// 'translate(0,' + (-outerRadius + 7) + ')')

var overlayMaps = {
  "<img src= 'https://api.geoapify.com/v1/icon/?type=awesome&color=green&icon=tick&scaleFactor=2&apiKey=6dc7fb95a3b246cfa0f3bcef5ce9ed9a'  height=24>Delivered": groupA,
  "<img src= 'https://api.geoapify.com/v1/icon/?type=awesome&color=gold&icon=tick&scaleFactor=2&apiKey=6dc7fb95a3b246cfa0f3bcef5ce9ed9a'  height=24>Left": groupB,
  "<img src= 'https://api.geoapify.com/v1/icon/?type=awesome&color=red&icon=tick&scaleFactor=2&apiKey=6dc7fb95a3b246cfa0f3bcef5ce9ed9a'  height=24>Returned": groupC
};
L.control.layers(tileLayer, overlayMaps, {
  collapsed: false,
  position: 'topright'
}).addTo(map);




var mygeojson = {
  type: "FeatureCollection",
  features: [],
};
var waypoints = []
var all_points = []
for (i = 0; i < data.length; i++) {
  var lng = data[i].lng;
  var lat = data[i].lat;
  all_points.push([lng, lat]);
  if (data[i].Category != 0) {
    mygeojson.features.push({
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [data[i].lng, data[i].lat]
      },
      "properties": {
        "Icon": data[i].Icon,
        "Status": data[i].Status,
        "Customer": data[i].Customer,
        "Supplier": data[i].Supplier,
        "Item Size": data[i]["Item Size"],
        "Address": data[i].Address,
        "Customer Order": data[i]["Customer Order"],
        "Notes": data[i].Notes,
        "Instruction": data[i].Instruction

      }
    });
    waypoints.push({
      id: data[i].id,
      lat: data[i].lat,
      lng: data[i].lng
    });
  }
}
//  console.log(mygeojson);
//console.log(all_points);
var polyline = L.polyline(lngLatArrayToLatLng(all_points)).addTo(map);

function lngLatArrayToLatLng(lngLatArray) {
  return lngLatArray.map(lngLatToLatLng);
}

function lngLatToLatLng(lngLat) {
  return [lngLat[1], lngLat[0]];
}

function IconStyle(feature, latlng) {
  switch (feature.properties["Status"]) {
    case "Delivered":
      var deliveredIcon = new L.icon({
        iconUrl: `https://api.geoapify.com/v1/icon/?type=awesome&color=green&icon=tick&scaleFactor=2&apiKey=${myAPIKey}`,
        iconSize: [20, 32], // size of the icon
        iconAnchor: [0, 22], // point of the icon which will correspond to marker's location
        popupAnchor: [-3, -26] // point from which the popup should open relative to the iconAnchor    
      });
      return L.marker(latlng, {
        icon: deliveredIcon
      });
    case "Left":
      var leftIcon = new L.icon({
        iconUrl: `https://api.geoapify.com/v1/icon/?type=awesome&color=gold&icon=tick&scaleFactor=2&apiKey=${myAPIKey}`,

        iconSize: [20, 32], // size of the icon
        iconAnchor: [0, 22], // point of the icon which will correspond to marker's location
        popupAnchor: [-3, -26] // point from which the popup should open relative to the iconAnchor    
      });
      return L.marker(latlng, {
        icon: leftIcon
      });
    case "Returned":
      var returnedIcon = new L.icon({
        iconUrl: `https://api.geoapify.com/v1/icon/?type=awesome&color=red&icon=tick&scaleFactor=2&apiKey=${myAPIKey}`,
        iconSize: [20, 32], // size of the icon
        iconAnchor: [0, 22], // point of the icon which will correspond to marker's location
        popupAnchor: [-3, -26] // point from which the popup should open relative to the iconAnchor    
      });
      return L.marker(latlng, {
        icon: returnedIcon
      });
  }
}

Best Answer

Since you are adding your markers to groupA, groupB and groupC layers and also to GeoJSON layer that uses the same data from mygeojson GeoJSON object and all these layers are added to the map, it's normal you will get your markers twice.

Solution to select desired markers from GeoJSON layer with layer control would be to break your GeoJSON layer to three different layers, using filter option to select only layers of desired category for each of those layers.

Fro this you would have to add Category property when creating your GeoJSON obkect:

var mygeojson = {
  type: "FeatureCollection",
  features: [],
};
var waypoints = []
var all_points = []
for (i = 0; i < data.length; i++) {
  var lng = data[i].lng;
  var lat = data[i].lat;
  all_points.push([lng, lat]);
  if (data[i].Category != 0) {
    mygeojson.features.push({
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [data[i].lng, data[i].lat]
      },
      "properties": {
        "Category": data[i].Category,
        "Icon": data[i].Icon,
        "Status": data[i].Status,
        "Customer": data[i].Customer,
        "Supplier": data[i].Supplier,
        "Item Size": data[i]["Item Size"],
        "Address": data[i].Address,
        "Customer Order": data[i]["Customer Order"],
        "Notes": data[i].Notes,
        "Instruction": data[i].Instruction

      }
    });
    waypoints.push({
      id: data[i].id,
      lat: data[i].lat,
      lng: data[i].lng
    });
  }
}

Than you can wrap you GeoJSON layer creation into a function, where one of the parameters is category to filter data only for desired category:

function filteredGeojsonLayer(geoJsonData, category) {
  var PoI = L.geoJson(mygeojson, {
    filter: function(feature) {
      return(feature.properties.Category == category);
    },
    pointToLayer: IconStyle,
    onEachFeature: function(feature, layer) {
      if (feature.properties) {
        var div = document.createElement("div");
        div.className = 'popupCustom';
        var content =
          `<div class="info-window-container info-window-grid">
              <div class="grid-item">
                <h4 class="info-window-title">Customer Name:</h4>
                <div  class="info-window-text">${feature.properties.Customer}</div>
              </div>
              <div class="grid-item">
                <h4 class="info-window-title info-window-img-container">Status:
                   <span class="info-window-status-${feature.properties.Status}">${feature.properties.Status} 
                   </span>
                   <img src="${feature.properties.Icon}" class="info-window-marker" alt="${feature.properties.Address}"/>
                </h4>
              </div>
              <div class="grid-item">
                <h4 class="info-window-title">Supplier:</h4>
                <div  class="info-window-text">${feature.properties.Supplier}</div>
              </div>
              <div class="grid-item">
                <h4 class="info-window-title">Item Size:</h4>
                <div  class="info-window-text">${feature.properties["Item Size"]}</div>
              </div>
              <div class="grid-item">
                <h4 class="info-window-title">Address:</h4>
                <div  class="info-window-text">${feature.properties.Address[0]["Line 1"]}</div>
                <div  class="info-window-text">${feature.properties.Address[0]["Line 2"]}</div>
                <div  class="info-window-text">${feature.properties.Address[0]["Town"]}</div>
                <div  class="info-window-text">${feature.properties.Address[0]["Post Code"]}</div>
              </div>
              <div class="grid-item">
                <h4 class="info-window-title">Instruction:</h4>
                <div  class="info-window-text">${feature.properties.Instruction || "N/A"}</div>
              </div>
               <div class="grid-item">
                <h4 class="info-window-title">Customer Order:</h4>
                <div  class="info-window-text">${feature.properties["Customer Order"]}</div>
               </div>
              <div class="grid-item">
                <h4 class="info-window-title">Delivery Notes:</h4>
                <div  class="info-window-text">${feature.properties.Notes || "N/A"}</div>
              </div>

            </div>`;
        layer.bindPopup(content);
      }
    }
  }).addTo(map);
  return(PoI);
}

Then you can create three different GeoJSON layers, corresponding to the three categories, and use those in layer control:

var overlay1 = filteredGeojsonLayer(mygeojson, 1);
var overlay2 = filteredGeojsonLayer(mygeojson, 2);
var overlay3 = filteredGeojsonLayer(mygeojson, 3);

var overlayMaps = {
  "<img src= 'https://api.geoapify.com/v1/icon/?type=awesome&color=green&icon=tick&scaleFactor=2&apiKey=6dc7fb95a3b246cfa0f3bcef5ce9ed9a'  height=24>Delivered": overlay1,
  "<img src= 'https://api.geoapify.com/v1/icon/?type=awesome&color=gold&icon=tick&scaleFactor=2&apiKey=6dc7fb95a3b246cfa0f3bcef5ce9ed9a'  height=24>Left": overlay2,
  "<img src= 'https://api.geoapify.com/v1/icon/?type=awesome&color=red&icon=tick&scaleFactor=2&apiKey=6dc7fb95a3b246cfa0f3bcef5ce9ed9a'  height=24>Returned": overlay3
};

L.control.layers(tileLayer, overlayMaps, {
  collapsed: false,
  position: 'topright'
}).addTo(map);

See working JSFiddle: https://jsfiddle.net/TomazicM/ct9pj8n1/