Wednesday, 7 September 2016

How to place all unique values into a lookup array in JavaScript for a Leaflet map?


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 += '';

}
if (this.options.lookupInFeatureProperties) {
for (var i = 0; i < this.featureCollection.features.length; i++) {
content += '';
}
}
else {
for (var i = 0; i < this.featureCollection.features.length; i++) {
content += '';
}

};
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 © 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.



Answer



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 += '';

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

});

No comments:

Post a Comment

arcpy - Changing output name when exporting data driven pages to JPG?

Is there a way to save the output JPG, changing the output file name to the page name, instead of page number? I mean changing the script fo...