2

How would I convert this function to store and utilize (feature.property.id) json values instead of keys in the dropdown menu within a leaflet map?

<html>
    <head>
        <meta charset="ISO-8859-1">
        <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css"/>
        <script src="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
        <script src="Leaflet.CountrySelect.js"></script>
    </head>

    <style>
        html, body, #map {
            padding:0px;
            margin:0px;
            height:100%;
            width:100%;
        }
    </style>

    <script>
        function init(){            
            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});
            var select = L.countrySelect({exclude:"French Southern and Antarctic Lands"});

            select.addTo(map);

            select.on('change', function(e){
                if (e.feature === undefined){ //Do nothing on title
                    return;
                }
                var country = L.geoJson(e.feature);
                if (this.previousCountry != null){
                    map.removeLayer(this.previousCountry);
                }
                this.previousCountry = country;

                map.addLayer(country);
                map.fitBounds(country.getBounds());

            });
        }
    </script>

    <body onload="init()">
        <div id="map"></div>
    </body>
</html>

with the corresponding JavaScript,

L.CountrySelect = {};
L.CountrySelect.countries = {"Afghanistan":{"type":"Feature","id":"AFG","properties":{"name":"Afghanistan"},"geometry":{"type":"Polygon","coordinates":[[[61.210817,35.650072],[62.230651,35.270664],[62.984662,35.404041],[63.193538,35.857166],[63.982896,36.007957],[64.546479,36.312073],[64.746105,37.111818],[65.588948,37.305217],[65.745631,37.661164],[66.217385,37.39379],[66.518607,37.362784],[67.075782,37.356144],[67.83,37.144994],[68.135562,37.023115],[68.859446,37.344336],[69.196273,37.151144],[69.518785,37.608997],[70.116578,37.588223],[70.270574,37.735165],[70.376304,38.138396],[70.806821,38.486282],[71.348131,38.258905],[71.239404,37.953265],[71.541918,37.905774],[71.448693,37.065645],[71.844638,36.738171],[72.193041,36.948288],[72.63689,37.047558],[73.260056,37.495257],[73.948696,37.421566],[74.980002,37.41999],[75.158028,37.133031],[74.575893,37.020841],[74.067552,36.836176],[72.920025,36.720007],[71.846292,36.509942],[71.262348,36.074388],[71.498768,35.650563],[71.613076,35.153203],[71.115019,34.733126],[71.156773,34.348911],[70.881803,33.988856],[69.930543,34.02012],[70.323594,33.358533],[69.687147,33.105499],[69.262522,32.501944],[69.317764,31.901412],[68.926677,31.620189],[68.556932,31.71331],[67.792689,31.58293],[67.683394,31.303154],[66.938891,31.304911],[66.381458,30.738899],[66.346473,29.887943],[65.046862,29.472181],[64.350419,29.560031],[64.148002,29.340819],[63.550261,29.468331],[62.549857,29.318572],[60.874248,29.829239],[61.781222,30.73585],[61.699314,31.379506],[60.941945,31.548075],[60.863655,32.18292],[60.536078,32.981269],[60.9637,33.528832],[60.52843,33.676446],[60.803193,34.404102],[61.210817,35.650072]]]}},"Angola":{"type":"Feature","id":"AGO","properties":{"name":"Angola"},"geometry":{"type":"MultiPolygon","coordinates":[[[[16.326528,-5.87747],[16.57318,-6.622645],[16.860191,-7.222298],[17.089996,-7.545689],[17.47297,-8.068551],[18.134222,-7.987678],[18.464176,-7.847014],[19.016752,-7.988246],[19.166613,-7.738184],[19.417502,-7.155429],[20.037723,-7.116361],[20.091622,-6.94309],[20.601823,-6.939318],[20.514748,-7.299606],[21.728111,-7.290872],[21.746456,-7.920085],[21.949131,-8.305901],[21.801801,-8.908707],[21.875182,-9.523708],[22.208753,-9.894796],[22.155268,-11.084801],[22.402798,-10.993075],[22.837345,-11.017622],[23.456791,-10.867863],[23.912215,-10.926826],[24.017894,-11.237298],[23.904154,-11.722282],[24.079905,-12.191297],[23.930922,-12.565848],[24.016137,-12.911046],[21.933886,-12.898437],[21.887843,-16.08031],[22.562478,-16.898451],[23.215048,-17.523116],[21.377176,-17.930636],[18.956187,-17.789095],[18.263309,-17.309951],[14.209707,-17.353101],[14.058501,-17.423381],[13.462362,-16.971212],[12.814081,-16.941343],[12.215461,-17.111668],[11.734199,-17.301889],[11.640096,-16.673142],[11.778537,-15.793816],[12.123581,-14.878316],[12.175619,-14.449144],[12.500095,-13.5477],[12.738479,-13.137906],[13.312914,-12.48363],[13.633721,-12.038645],[13.738728,-11.297863],[13.686379,-10.731076],[13.387328,-10.373578],[13.120988,-9.766897],[12.87537,-9.166934],[12.929061,-8.959091],[13.236433,-8.562629],[12.93304,-7.596539],[12.728298,-6.927122],[12.227347,-6.294448],[12.322432,-6.100092],[12.735171,-5.965682],[13.024869,-5.984389],[13.375597,-5.864241],[16.326528,-5.87747]]],[[[12.436688,-5.684304],[12.182337,-5.789931],[11.914963,-5.037987],[12.318608,-4.60623],[12.62076,-4.438023],[12.995517,-4.781103],[12.631612,-4.991271],[12.468004,-5.248362],[12.436688,-5.684304]]]]}},"Albania":{"type":"Feature","id":"ALB","properties":{"name":"Albania"},"geometry":{"type":"Polygon","coordinates":[[[20.590247,41.855404],[20.463175,41.515089],[20.605182,41.086226],[21.02004,40.842727],[20.99999,40.580004],[20.674997,40.435],[20.615,40.110007],[20.150016,39.624998],[19.98,39.694993],[19.960002,39.915006],[19.406082,40.250773],[19.319059,40.72723],[19.40355,41.409566],[19.540027,41.719986],[19.371769,41.877548],[19.304486,42.195745],[19.738051,42.688247],[19.801613,42.500093],[20.0707,42.58863],[20.283755,42.32026],[20.52295,42.21787],[20.590247,41.855404]]]}}

L.CountrySelect = L.Control.extend({
options: {
    position: 'topright',
    title: 'Country',
    exclude: [],
    include: [],
    countries: L.CountrySelect.countries,
},
onAdd: function(map) {
    this.div = L.DomUtil.create('div','leaflet-countryselect-container');
    this.select = L.DomUtil.create('select','leaflet-countryselect',this.div);
    var content = '';

    if (this.options.title.length > 0 ){
        content += '<option>'+this.options.title+'</option>';
    }

    var countries = (Array.isArray(this.options.include) && this.options.include.length > 0) ? this.options.include : this.options.countries;

    var countryKeys = Object.keys(countries).sort();
    for (i in countryKeys){
        if (this.options.exclude.indexOf(countryKeys[i]) == -1){
            content+='<option>'+countryKeys[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 if (type == 'click'){ //don't need this here probably, but for convenience?
        this.onClick = handler;
        L.DomEvent.addListener(this.select,'click',this.onClick,this);          
    } else {
        console.log('CountrySelect - cannot handle '+type+' events.')
    }
},
_onChange: function(e) {
    var selectedCountry = this.select.options[this.select.selectedIndex].value;
    e.feature = this.options.countries[selectedCountry];
    this.onChange(e);
    }
});

    L.countrySelect = function(id,options){
        return new L.CountrySelect(id,options);
    };

Mainly this function is pulling the super key county name, but this doesn't work well with a general JSON/GeoJSONs (please correct me if I'm wrong). I'd like to add feature.propoerty.id values to the dropdown list and have that filter/zoom to associated county based on that value instead of the super key.

Here's a demo: http://ahalota.github.io/Leaflet.CountrySelect/demo.html

I've been scratching my head for a couple hours on this and I'm pretty new to JavaScript.

More information on this function can be found here -> https://github.com/ahalota/Leaflet.CountrySelect

  • 1
    It's not quite clear what you want to achieve. Do you want to use regular GeoJSON (like the one from where data originally comes: github.com/johan/world.geo.json)? Or you want to use JSON country object as it is and search it by id (which doesn't make much sesnse)? – TomazicM Jun 29 at 10:36
  • Yes, I'd like to search by id. Any advice is welcome. Thank you – bikingman Jun 29 at 11:56
  • Fundamentally, what I'd like to do is to provide a dropdown menu on the UI that allows the user to pick an object ID from a list of unique id's within a json file, and have that option's geometry filtered and displayed on a map. This code seems nearly able to achieve what I'm looking for, and I believe the change needs to be made within the var countryKeys = Objects.Keys block, but I'm not sure. Thank you in advance for any help – bikingman Jun 29 at 12:15
1

First it has to be noted that solution below is modification of Leaflet.CountrySelect plugin, written by Anika S. Halota.

Requirement that control would work with any JSON file is to broad, since file must have defined structure that plugin can work with. Since file has to include geometries, the obvious candidate is then GeoJSON feature collection, where each feature has geometry that can be displayed and some id by which it can be identified.

The following changes were made to control:

  1. Control was renamed to L.featureSelect
  2. Input parameter features was added to control. This is input GeoJSON object.
  3. New option lookupProperty was added. It specifies feature property name to be used for lookup. Deafult value is 'id'.
  4. New option lookupInFeatureProperties was added. If set to true, feature property specified with lookupProperty will be searched for in properties feature property.
  5. Options exclude, include and countries were removed, sonce thay are not needed any more.
  6. Lookup of selected feature is done by looping through the features in feature collection until the feature with desired lookup value in specified property is found.

So here is how new control looks like:

L.FeatureSelect = L.Control.extend({ 
  options: {
    position: 'topright',
    title: 'Feature ID',
    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 = '';       
    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);
};

Below is example of using the new control with original countries file world.geo.json, available at https://github.com/johan/world.geo.json (jQuery is used to load GeoJSON file from local server):

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

In the above example feature property id would be used to select countries by id. Since each feature (country) has also country name stored properties property under name property, it could also be used to select countries by name by defining control like this:

var select = L.featureSelect(data, {
                lookupProperty: 'name',
                lookupInFeatureProperties: true,
                title: 'Country'
             });
  • This work exceptionally well. Thank you so much. Now it's a puzzle to 1. create a remove button to remove the selected feature upon click, and 2. add additional features into the id group to visualize with the selection without adding duplicate options into the search dropdown control. – bikingman Jul 2 at 0:31
  • I see by replacing return; under $.getJSON with map.removeLayer(this.previousFeature);, we can have the title option remove the feature from the map. $.getJSON( "json/groups_to_select.geojson") .done(function(data) { var select = L.featureSelect(data); select.addTo(map); select.on('change', function(e) { if (e.feature === undefined) { map.removeLayer(this.previousFeature); }... Any advice on how to extract unique ID's, and place then into the drop-down to visualize groups of features would would be very helpful. – bikingman Jul 2 at 0:44
  • 1
    One of the basic principles of this site is: one question per question, otherwise it could go on forever. If you have new question, post it. And one more thing: if answer helped you resolve your problem, mark it as accepted, not only upvote it. – TomazicM Jul 2 at 5:22
  • May I kindly ask again: why don't you deem this answer worthy of beeing accepted, when you are obviously using it (see gis.stackexchange.com/questions/327529/…). – TomazicM Jul 2 at 15:11
  • Very acceptable - thought I had noted – bikingman Jul 2 at 15:17

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.