0

I've been working through a problem with others on 1) grabbing id's from a GeoJSON, 2) placing those id's into a dropdown menu on a Leaflet map, 3) and enabling functionality to allow the user to choose an id, after which the map will filter and zoom to the chosen location.

We've got the control,

L.FeatureSelect = L.Control.extend({ 
  options: {
    position: 'topright',
    title: 'Name, Clear',
    lookupProperty: 'name',
    lookupInFeatureProperties: true
  },    
  initialize: function (features, options) {
    this.featureCollection = features;
    L.Util.setOptions(this, options);
  },
  onAdd: function(map) {
    this.div = L.DomUtil.create('div', 'leaflet-featureselect-container');
    this.select = L.DomUtil.create('select', 'leaflet-featureselect', this.div);
    var content = '';       
    if (this.options.title.length > 0 ) {
      content += '<option>&nbsp;' + this.options.title + '&nbsp;</option>';
    }
    if (this.options.lookupInFeatureProperties) {
      for (var i = 0; i < this.featureCollection.features.length; i++) {
        content += '<option>' + this.featureCollection.features[i].properties[this.options.lookupProperty] + '</option>';
      }
      }
    else {
      for (var i = 0; i < this.featureCollection.features.length; i++) {
        content += '<option>' + this.featureCollection.features[i][this.options.lookupProperty] + '</option>';
      }
    };
    this.select.innerHTML = content;
    this.select.onmousedown = L.DomEvent.stopPropagation;
    return this.div;
  },
  on: function(type,handler) {
    if (type == 'change'){
      this.onChange = handler;
      L.DomEvent.addListener(this.select, 'change', this._onChange, this);          
    } else {
      console.log('FeatureSelect - cannot handle ' + type + ' events.')
    }
  },
  _onChange: function(e) {
    var selectedItemKey = this.select.options[this.select.selectedIndex].value;
    if (this.options.lookupInFeatureProperties) {
      for (var i = 0; i < this.featureCollection.features.length; i++) {
        if (this.featureCollection.features[i].properties[this.options.lookupProperty] == selectedItemKey) {
          e.feature = this.featureCollection.features[i];
          break;
        }
      }
      }
    else {
      for (var i = 0; i < this.featureCollection.features.length; i++) {
        if (this.featureCollection.features[i][this.options.lookupProperty] == selectedItemKey) {
          e.feature = this.featureCollection.features[i];
          break;
        }
      }
    }
    this.onChange(e);
  }
});

L.featureSelect = function(features, options) {
  return new L.FeatureSelect(features, options);
};

And the map, which clears content when choosing the title (Name, Clear) using data from this location.

var baseLayer = L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',{attribution: 'Tiles &copy; CartoDB'});
var map = L.map("map",{layers: [baseLayer], center: [-23.88, -62.75], zoom: 4});

$.getJSON( "world.geo.json")
 .done(function(data) {
   var select = L.featureSelect(data);
   select.addTo(map);
   select.on('change', function(e) {
     if (e.feature === undefined) {
       return;
     }
     var feature = L.geoJson(e.feature);
     if (this.previousFeature != null) {
       map.removeLayer(this.previousFeature);
     }
     this.previousFeature = feature;  
     map.addLayer(feature);
     map.fitBounds(feature.getBounds());
   });
 });

The problem is that I'd like to feed the script a geojson with many non unique lookupProperty values. i.e. a polygon layer with many Brazil or many Canada values. Currently, the script will return all values under the "lookupProperty", meaning many duplicates end up in the dropdown menu.

How may I grab and place all non unique id's into dropdown menu array?

From my understanding, if the dropdown menu has all non unique id's, when a user selects the unique id, all features with that property value will be filtered and displayed on the map, which is what we want.

  • You should mention in your question that control is based on Leaflet.CountrySelect plugin, written by Anika S. Halota. – TomazicM Jul 2 at 15:12
  • Yes, this is derived from Anika S. Haolta, TomazicM, and Johan. On the backs of giants we stand. Thank you for being patient with someone so new to javascript! – bikingman Jul 2 at 15:21
0

First it has to be noted that control mentioned in the question is modification of Leaflet.CountrySelect plugin, written by Anika S. Halota.

To achieve unique lookup keys in menu, the following changes were made to control:

  1. At the load time (onAdd method) all lookup keys are stored into internal array where they are sorted and made unigue.
  2. Internal method _sortUnique was added to get sorted and unique lookup keys.
  3. When features are selected to be displayed (_onChange method), loop goes through all the features to get possible multiple features that correspond to selected lookup value.
L.FeatureSelect = L.Control.extend({ 
  options: {
    position: 'topright',
    title: 'Name, Clear',
    lookupProperty: 'id',
    lookupInFeatureProperties: false
  },    
  initialize: function (features, options) {
    this.featureCollection = features;
    L.Util.setOptions(this, options);
  },
  onAdd: function(map) {
    this.div = L.DomUtil.create('div', 'leaflet-featureselect-container');
    this.select = L.DomUtil.create('select', 'leaflet-featureselect', this.div);
    var content = '';
    this.lookupArray = [];
    if (this.options.title.length > 0 ) {
      content += '<option>&nbsp;' + this.options.title + '&nbsp;</option>';
    }
    if (this.options.lookupInFeatureProperties) {
      for (var i = 0; i < this.featureCollection.features.length; i++) {
        this.lookupArray.push(this.featureCollection.features[i].properties[this.options.lookupProperty]);
      }
      }
    else {
      for (var i = 0; i < this.featureCollection.features.length; i++) {
        this.lookupArray.push(this.featureCollection.features[i][this.options.lookupProperty]);
      }
    };
    this.lookupArray = this._sortUnique(this.lookupArray);
    for (var i = 0; i < this.lookupArray.length; i++) {
      content += '<option>' + this.lookupArray[i] + '</option>';
    }
    this.select.innerHTML = content;
    this.select.onmousedown = L.DomEvent.stopPropagation;
    return this.div;
  },
  on: function(type, handler) {
    if (type == 'change'){
      this.onChange = handler;
      L.DomEvent.addListener(this.select, 'change', this._onChange, this);          
    } else {
      console.log('FeatureSelect - cannot handle ' + type + ' events.')
    }
  },
  _onChange: function(e) {
    e.features = [];
    var selectedItemKey = this.select.options[this.select.selectedIndex].value;
    if (this.options.lookupInFeatureProperties) {
      for (var i = 0; i < this.featureCollection.features.length; i++) {
        if (this.featureCollection.features[i].properties[this.options.lookupProperty] == selectedItemKey) {
          e.features.push(this.featureCollection.features[i]);
        }
      }
      }
    else {
      for (var i = 0; i < this.featureCollection.features.length; i++) {
        if (this.featureCollection.features[i][this.options.lookupProperty] == selectedItemKey) {
          e.features.push(this.featureCollection.features[i]);
        }
      }
    }
    this.onChange(e);
  },
  _sortUnique: function(array) {
    array.sort();
    var last_i;
    for (var i = 0; i < array.length; i++)
      if ((last_i = array.lastIndexOf(array[i])) !== i) {
        array.splice(i + 1, last_i - i);
      }
    return array;
  }     
});

L.featureSelect = function(features, options) {
  return new L.FeatureSelect(features, options);
};

Since multiple selected features are now possible, adding and removing is done through L.featureGroup:

$.getJSON( "world.geo.json")
 .done(function(data) {
   var select = L.featureSelect(data);
   select.addTo(map);
   select.on('change', function(e) {
     if (this.previousFeatures != null) {
       map.removeLayer(this.previousFeatures);
     }
     if (e.features === undefined) {
       return;
     }
     this.previousFeatures = L.featureGroup();
     for (var i = 0; i < e.features.length; i++) {
       this.previousFeatures.addLayer(L.geoJson(e.features[i]));
     }
     map.addLayer(this.previousFeatures);
     map.fitBounds(this.previousFeatures.getBounds());
   });
 });
  • TomazicM you're amazing! This helps tremendously! Thank you! – bikingman Jul 5 at 13:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.