Leaflet:如何将 GeoJSON 数据集动态过滤到一系列图层复选框中?

Leaftlet: How to dynamically filter a GeoJSON dataset in to a series of Layer checkboxes?

我有一个网页,它从本地数据库获取一些标记图钉位置,还从远程 GeoJSON 源 (API) 获取一组特征。

目前,用户可以使用两个复选框来选择要查看的两个图层中的哪一个。这一切都很好:

<script>

    // Center the map
    var map = L.map('map').setView([54.233669, -4.406027], 6);


    // Attribution
    L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=REMOVED', {
        attribution: 'Map &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a>',
        id: 'mapbox.streets'
    }).addTo(map);



    // Create an empty layergroup for the Locations data
    var LayerLocations = L.layerGroup();

    // Format the popup markers for the Locations
    function forEachFeature(feature, layer) {

        // Image
        var ImageContent
        if (feature.properties.ImageURL) {
            ImageContent = "<img src='" + feature.properties.ImageURL + "' width='300' height='200' /><br /><br />"
        } else if (feature.properties.YouTubeID) {
                ImageContent = "<img src='https://img.youtube.com/vi/" + feature.properties.YouTubeID + "/hqdefault.jpg' width='300' height='200' /><br /><br />"

        } else {
            ImageContent = ""
        }

        // Build the popup content
        var popupContent = "<h4>" +
            feature.properties.Title +
            Author +
            "</h4>" +
            ImageContent +
            CommentsContent +
            "View and discuss this location.";
        layer.bindPopup(popupContent);
    }

    // Build layer: Locations
    fetch("JSONMapPoints.json")
      .then(function (response) { return response.json() })
      .then(function (data) {
          // Create a L.GeoJSON out of the data
          var locations = L.geoJson(data, {
              onEachFeature: forEachFeature,
              pointToLayer: function (feature, latlng) {
                  return L.marker(latlng, {
                      icon: L.icon({
                      iconUrl: "images/pins/" + feature.properties.CategoryID + ".png",
                      iconSize: [32, 37],
                      iconAnchor: [10, 32],
                      popupAnchor: [5, -30]
                      }),
                  })
              }
          });
          // Add the L.GeoJSON instance to the empty layergroup
          map.fitBounds(locations.getBounds());
          LayerLocations.addLayer(locations).addTo(map);
    });


    // Create an empty layergroup for the Guardian UTM data
    var LayerGuardianUTM = L.layerGroup();

    // Style the Guardian UTM features
    function setStyle(feature) {
        return {
            fillColor: feature.properties.fillColor,
            color: feature.properties.strokeColor,
            fillOpacity: feature.properties.fillOpacity,
            opacity: feature.properties.strokeOpacity
        };
    }

    // Build Layer: Guardian UTM
    function getGuardianUTMdata() {
        LayerGuardianUTM.clearLayers();
        fetch("https://example.com/v2/mapdata/geojson?n=" + map.getBounds().getNorth() + "&e=" + map.getBounds().getEast() + "&s=" + map.getBounds().getSouth() + "&w=" + map.getBounds().getWest(), { headers: { 'Authorization': 'REMOVED', 'X-AA-DeviceId': 'mySite' } })
          .then(function (responseGuardianUTM) { return responseGuardianUTM.json() })
          .then(function (dataGuardianUTM) {
              // Create a L.GeoJSON out of the data
              var featuresAA = L.geoJson(dataGuardianUTM, {
                  style: setStyle,
                  pointToLayer: function (feature, latlng) {
                      return L.marker(latlng, { icon: L.icon({ iconUrl: feature.properties.iconUrl }), })
                  },
                  onEachFeature: function (feature, layer) {
                      layer.bindPopup(feature.properties.name);
                  },
              });
              // Add the L.GeoJSON instance to the empty layergroup
              LayerGuardianUTM.addLayer(featuresAA).addTo(map);
          });
    }


    // Update the Guardian UTM layer if the map moves
    map.on('dragend', function () { getGuardianUTMdata(); });
    map.on('zoomend', function () { getGuardianUTMdata(); });


    // Layer controls
    var layerControl = new L.Control.Layers(null, {
        'Locations': LayerLocations,
        'Restrictions & Hazards': LayerGuardianUTM
    }).addTo(map);


</script>

我希望增加最终用户可用的功能。

我希望遍历从 API 返回的 GeoJSON 并在层下动态构建一系列复选框,而不是使用单个复选框来打开 on/off Restrictions & Hazards 层button/icon 基于 feature.properties.filters.name.

因此,当用户单击“图层”按钮图标时,他们应该会看到一系列复选框,允许他们挑选和选择他们希望查看的 GeoJSON 中的哪些功能。

从 API 返回的 GeoJSON 是动态的,其内容会根据用户的位置和缩放级别而变化。

GeoJSON 的一个例子是:

{
    "isCompleteData": true,
    "excludedData": [],
    "countriesInViewport": [],
    "nationalFlightRestrictions": [],
    "features": [
        {
            "geometry": {
                "coordinates": [
                    [
                        -2.6300508975982666,
                        53.536331176757812
                    ],
                    [
                        -2.6293964385986328,
                        53.533683776855469
                    ],
                    [
                        -2.6288816928863525,
                        53.531524658203125
                    ],
                    [
                        -2.6228303909301758,
                        53.529739379882813
                    ],
                    [
                        -2.6218380928039551,
                        53.528053283691406
                    ],
                    [
                        -2.6206841468811035,
                        53.526073455810547
                    ]
                ],
                "type": "LineString"
            },
            "id": "A05B59534A594F20583A3B8EB479F211E507F265",
            "properties": {
                "hazardFactor": "40",
                "hazardFactorName": "Warning",
                "fillColor": "#ffbb00",
                "strokeColor": "#b88702",
                "fillOpacity": "0.35",
                "strokeWidth": "1",
                "strokeOpacity": "0.8",
                "detailedCategory": "power:line",
                "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=power_line.png",
                "name": "Power Line",
                "category": "groundHazard",
                "filters": [
                    {
                        "name": "Ground Hazards",
                        "property": "show",
                        "active": true
                    }
                ],
                "display": {
                    "category": "Ground Hazard",
                    "detailedCategory": "Power Line",
                    "title": "Ground Hazard",
                    "sections": [
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=power_line.png",
                            "title": "Power Hazard",
                            "text": "The highlighted area is believed to contain power infrastructure. Power infrastructure presents heightened risk of damage to your equipment and critical National infrastructure."
                        },
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
                            "title": "Summary",
                            "text": "Yellow zones indicate regions where operation of your drone may raise security, privacy or safety concerns."
                        }
                    ],
                    "actions": []
                }
            },
            "type": "Feature"
        },
        {
            "geometry": {
                "coordinates": [
                    -2.6228303909301758,
                    53.529739379882813
                ],
                "type": "Point"
            },
            "id": "6EB24E66D75083A4A135296C12BE004D79629818",
            "properties": {
                "hazardFactor": "40",
                "hazardFactorName": "Warning",
                "fillColor": "#ffbb00",
                "strokeColor": "#b88702",
                "fillOpacity": "0.35",
                "strokeWidth": "1",
                "strokeOpacity": "0.8",
                "detailedCategory": "power:tower",
                "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=power_tower.png",
                "name": "Power Pylon",
                "category": "groundHazard",
                "filters": [
                    {
                        "name": "Ground Hazards",
                        "property": "show",
                        "active": true
                    }
                ],
                "display": {
                    "category": "Ground Hazard",
                    "detailedCategory": "Power Pylon",
                    "title": "Ground Hazard",
                    "sections": [
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=power_tower.png",
                            "title": "Power Hazard",
                            "text": "The highlighted area is believed to contain power infrastructure. Power infrastructure presents heightened risk of damage to your equipment and critical National infrastructure."
                        },
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
                            "title": "Summary",
                            "text": "Yellow zones indicate regions where operation may raise security, privacy or safety concerns."
                        }
                    ],
                    "actions": []
                }
            },
            "type": "Feature"
        },
        {
            "geometry": {
                "coordinates": [
                    [
                        [
                            -2.6234986782073975,
                            53.533077239990234
                        ],
                        [
                            -2.6215133666992187,
                            53.528900146484375
                        ],
                        [
                            -2.6183879375457764,
                            53.529270172119141
                        ],
                        [
                            -2.6178712844848633,
                            53.529655456542969
                        ]
                    ]
                ],
                "type": "Polygon"
            },
            "id": "557952B3668AC5DF5C583BE8E8C1840D97B5ABD4",
            "properties": {
                "hazardFactor": "40",
                "hazardFactorName": "Warning",
                "fillColor": "#ffbb00",
                "strokeColor": "#b88702",
                "fillOpacity": "0.35",
                "strokeWidth": "1",
                "strokeOpacity": "0.8",
                "detailedCategory": "landuse:cemetery",
                "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=landuse_cemetery.png",
                "name": "Wigan Borough Cemetery",
                "category": "groundHazard",
                "filters": [
                    {
                        "name": "Ground Hazards",
                        "property": "show",
                        "active": true
                    }
                ],
                "display": {
                    "category": "Ground Hazard",
                    "detailedCategory": "Cemetery",
                    "title": "Wigan Borough Cemetery",
                    "sections": [
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
                            "title": "Summary",
                            "text": "Yellow zones indicate regions where operation of your drone may raise security, privacy or safety concerns."
                        }
                    ],
                    "actions": []
                }
            },
            "type": "Feature"
        },
        {
            "geometry": {
                "coordinates": [
                    [
                        [
                            -3.235,
                            53.53694
                        ],
                        [
                            -3.05278,
                            53.45944
                        ],
                        [
                            -3.20139,
                            53.38583
                        ],
                        [
                            -3.02778,
                            53.24083
                        ],
                        [
                            -2.73028,
                            53.10722
                        ]
                    ]
                ],
                "type": "Polygon"
            },
            "id": "616CB45B9DA924146E9A5483843B588B36F0AD31",
            "properties": {
                "hazardFactor": "60",
                "hazardFactorName": "Danger",
                "fillColor": "#ffffff",
                "strokeColor": "#ffffff",
                "fillOpacity": "0.2",
                "strokeWidth": "1",
                "strokeOpacity": "0.8",
                "detailedCategory": "type:tma",
                "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=class_type_tma.png",
                "airac": {
                    "to": "2019-08-15",
                    "from": "2019-07-18"
                },
                "altitudeFloor": {
                    "datum": "Msl",
                    "meters": 1066.7999983784639,
                    "feet": 3499.9999946799994
                },
                "altitudeCeiling": {
                    "datum": "Sps",
                    "meters": 7467.5999886492482,
                    "feet": 24499.99996276
                },
                "name": "MANCHESTER TMA 1",
                "listOrderHint": "1000",
                "category": "airspace",
                "designator": "EGCC1",
                "airspaceType": "TMA",
                "filters": [
                    {
                        "name": "Upper Airspace",
                        "property": "show",
                        "active": false
                    },
                    {
                        "name": "Type TMA",
                        "property": "show",
                        "active": true
                    }
                ],
                "display": {
                    "category": "airspace",
                    "detailedCategory": "Type TMA",
                    "title": "MANCHESTER TMA 1",
                    "sections": [
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
                            "title": "Altitude",
                            "text": "This piece of airspace is in effect above 1067m / 3500ft MSL"
                        },
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
                            "title": "Terminal control area",
                            "text": "Control area normally established at the confluence of ATS routes in the vicinity of one or more major aerodromes."
                        },
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
                            "title": "Regulated Airspace",
                            "text": "This airspace has a specific classification."
                        },
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
                            "title": "Summary",
                            "text": "Red zones are regulated high-risk areas."
                        }
                    ],
                    "actions": []
                }
            },
            "type": "Feature"
        },
        {
            "geometry": {
                "coordinates": [
                    [
                        [
                            -3.05278,
                            53.45944
                        ],
                        [
                            -2.06667,
                            53.575
                        ],
                        [
                            -2.83333,
                            53.53333
                        ],
                        [
                            -3.05278,
                            53.45944
                        ]
                    ]
                ],
                "type": "Polygon"
            },
            "id": "BC69E04789D9A790DB5B29B0EE2804D42E4FA12A",
            "properties": {
                "hazardFactor": "60",
                "hazardFactorName": "Danger",
                "fillColor": "#ffffff",
                "strokeColor": "#ffffff",
                "fillOpacity": "0.2",
                "strokeWidth": "1",
                "strokeOpacity": "0.8",
                "detailedCategory": "class:d",
                "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=class_d.png",
                "airac": {
                    "to": "2019-08-15",
                    "from": "2019-07-18"
                },
                "altitudeFloor": {
                    "datum": "Msl",
                    "meters": 761.99999884176,
                    "feet": 2499.9999961999997
                },
                "altitudeCeiling": {
                    "datum": "Msl",
                    "meters": 1066.7999983784639,
                    "feet": 3499.9999946799994
                },
                "name": "MANCHESTER CTA 1",
                "listOrderHint": "600",
                "category": "airspace",
                "designator": "EGCC1",
                "airspaceClass": "D",
                "airspaceType": "CTA",
                "filters": [
                    {
                        "name": "Upper Airspace",
                        "property": "show",
                        "active": false
                    },
                    {
                        "name": "Class  D",
                        "property": "show",
                        "active": true
                    }
                ],
                "display": {
                    "category": "airspace",
                    "detailedCategory": "Class  D",
                    "title": "MANCHESTER CTA 1",
                    "sections": [
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
                            "title": "Altitude",
                            "text": "This piece of airspace is in effect above 762m / 2500ft MSL"
                        },
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
                            "title": "Regulated Airspace",
                            "text": "This airspace has a specific classification."
                        },
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
                            "title": "Summary",
                            "text": "Red zones are regulated high-risk areas."
                        }
                    ],
                    "actions": []
                }
            },
            "type": "Feature"
        },
        {
            "geometry": {
                "coordinates": [
                    [
                        [
                            -10,
                            54.56667
                        ],
                        [
                            -9,
                            54.75
                        ],
                        [
                            -8.25,
                            55.33333
                        ]
                    ]
                ],
                "type": "Polygon"
            },
            "id": "11DD2D3CBA8992F29E49A277FC322D19FCD67066",
            "properties": {
                "hazardFactor": "60",
                "hazardFactorName": "Danger",
                "fillColor": "#ffffff",
                "strokeColor": "#ffffff",
                "fillOpacity": "0.2",
                "strokeWidth": "1",
                "strokeOpacity": "0.8",
                "detailedCategory": "type:cta",
                "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=class_type_cta.png",
                "airac": {
                    "to": "2019-08-15",
                    "from": "2019-07-18"
                },
                "altitudeFloor": {
                    "datum": "Sps",
                    "meters": 7467.5999886492482,
                    "feet": 24499.99996276
                },
                "altitudeCeiling": {
                    "datum": "Sps",
                    "meters": 20116.799969422464,
                    "feet": 65999.99989968
                },
                "name": "UPPER AIRSPACE CTA",
                "listOrderHint": "1000",
                "category": "airspace",
                "designator": "EGUP",
                "airspaceType": "CTA",
                "filters": [
                    {
                        "name": "Upper Airspace",
                        "property": "show",
                        "active": false
                    },
                    {
                        "name": "Type CTA",
                        "property": "show",
                        "active": true
                    }
                ],
                "display": {
                    "category": "airspace",
                    "detailedCategory": "Type CTA",
                    "title": "UPPER AIRSPACE CTA",
                    "sections": [
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
                            "title": "Altitude",
                            "text": "This piece of airspace is in effect above FL244.9999996276"
                        },
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
                            "title": "Control area",
                            "text": "A controlled airspace extending upwards from a specified limit above the earth."
                        },
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
                            "title": "Regulated Airspace",
                            "text": "This airspace has a specific classification."
                        },
                        {
                            "iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
                            "title": "Summary",
                            "text": "Red zones are regulated high-risk areas."
                        }
                    ],
                    "actions": []
                }
            },
            "type": "Feature"
        }
    ],
    "bbox": [
        -2.6261,
        53.5288,
        -2.6201,
        53.5308
    ],
    "type": "FeatureCollection"
}

基于示例 GeoJSON,图层按钮应包含以下复选框:

这可能吗?!

创建多个 L.GeoJSON 实例,并利用它们的 filter option:

A Function that will be used to decide whether to include a feature or not. The default is to include all features:

function (geoJsonFeature) {
    return true;
}

Note: dynamically changing the filter option will have effect only on newly added data. It will not re-evaluate already included features.

例如:

fetch("JSONMapPoints.json")
  .then(function (response) { return response.json() })
  .then(function (data) {
      
      var locations = L.geoJson(data, {
          filter: function(feat) { return feat.properties.filters.name === 'Location'},
          /* etc */
      });
      var hazards = L.geoJson(data, {
          filter: function(feat) { return feat.properties.filters.name === 'Hazard'},
          /* etc */
      });
      var airspace = L.geoJson(data, {
          filter: function(feat) { return feat.properties.filters.name === 'Air Space'},
          /* etc */
      });
});

对于那些不同的 L.GeoJSON 实例,将它们添加到图层控件只是调用 addOverlay() 的问题,例如

layersControl.addOverlay(locations, "Locations");      
layersControl.addOverlay(hazards, "Hazards);    
layersControl.addOverlay(airspace, "Air Space");

不过要注意 scope。 JS 新手的一个错误是 假设 变量会神奇地存在于需要它的范围内。在这种特殊情况下,我会确保在创建 GeoJSON 图层时已经实例化了图层控件,并将它们添加到所述图层控件中。


一种更复杂的方法是自动检测类别或过滤器,遍历 GeoJSON 结构以获取它们,例如:

fetch("JSONMapPoints.json")
  .then(function (response) { return response.json() })
  .then(function (data) {
     
    // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
    var categories = new Set();

    for (var i in data) {
      var feature = data[i];
      categories.add(feature.properties.filters.name);
    }

然后遍历类别以编程方式创建 L.GeoJSON 个实例,注意适当的闭包:

    categories.forEach(function(category) {
      var layer = L.geoJSON(data, {
        filter: function(f){ f.properties.filters.name === category }
        /* etc */ });
      layersControl.addOverlay(layer, category);
    });

// And we're done here.
});

对于需要三个过滤器的数据集来说,这有点过度设计,但当数量更多时会很好地工作。