Skip to content

Instantly share code, notes, and snippets.

@answerquest
Last active June 2, 2021 06:45
Show Gist options
  • Save answerquest/aa3c700f716c61c5751a to your computer and use it in GitHub Desktop.
Save answerquest/aa3c700f716c61c5751a to your computer and use it in GitHub Desktop.
Leaflet combine geojson map data with CSV data (working example)

See http://nikhilvj.techydudes.net/files/pune-info.html for working version.

The main code part is:

Papa.parse(DATAPATH + 'pune-wards-info.csv', {
	download: true,
	header: true,
	complete: function(results) {
		var PuneInfoLayer = L.geoJson(null, {
			style: {weight: 2, opacity: 1, color: 'black', dashArray: '4', fillOpacity: 0.2, fillColor: "orange" },
			onEachFeature: function(feature, layer) { //below code will be executed for each item in the layer
				;var popupText = "";
				var labelText = "";
				var detailsText = "";
				if (feature.properties && feature.properties.name && turf.area(feature)) {
					//Here it comes! The part where we look up the CSV data for a row having the same name as the map item's name
					labelText = feature.properties.name.toString();
					var filtered = results.data.filter(function(data){return data.name == this;}, labelText);
					detailsText = "<h4>" + labelText + ' : ' + filtered[0]['title'] + "</h4>";
					labelText += ' : ' + filtered[0]['title'];
					//console.log(JSON.stringify(filtered[0]));
					detailsText += 'Admin ward: <b>' + filtered[0]['adminward'] + '</b><br><br>';
					detailsText += '<b>Voters:</b> ' + filtered[0]['voters'] + ', ' + filtered[0]['female_voters'] + ' female, ' + filtered[0]['male_voters'] + ' male<br><br>';
					detailsText += '<b>Elected Representatives:</b><br><table cellspacing="20">';
					detailsText+= '<tr><td><img src="'+ PICSPATH + filtered[0]['wardnum'] +'A.jpg" height="100px"><br>A: ' + filtered[0]['ER_A'] + '</td><td><img src="'+ PICSPATH + filtered[0]['wardnum'] +'B.jpg" height="100px"><br>B: ' + filtered[0]['ER_B'] + '</td></tr></table>';
					detailsText += 'Municipal Polling Stations: <b>' + filtered[0]['total_polling_buildings'] + '</b> buildings, <b>' + filtered[0]['total_polling_stations'] + '</b> stations.<br><br>';
					detailsText += '<b>Area:</b> ' + Math.round(turf.area(feature)*(1e-6)*100)/100 + ' sq.km <br>';
					detailsText += '<small><b>Areas / Landmarks : </b>' + filtered[0]['areas'] + '</small>';
				}
				layer.bindLabel(labelText); //removing .bindPopup(detailsText);
				layer.on({
					click: function() {
						document.getElementById('sidepanel').innerHTML = detailsText;
					}
				});
			} //end onEachFeature
		}); //end layer defining. We still have to load the data into it!!
		
		omnivore.geojson(DATAPATH+'pune-electoral-wards.geojson', null, PuneInfoLayer).addTo(wards);
	}	//Papa.parse's complete: section over
});	// Papa.parse function over.
<!DOCTYPE html>
<html>
<head>
<title>Pune Info</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="lib/leaflet.css" /> <!-- original: http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css -->
<link rel="stylesheet" href="lib/leaflet-search.css" />
<link rel="stylesheet" href="lib/leaflet-measure.css">
<link rel="stylesheet" href="lib/leaflet.label.css">
<link rel="stylesheet" href="lib/Control.FullScreen.css" />
<link rel="stylesheet" href="lib/MarkerCluster.Default.css" />
<link rel="stylesheet" href="lib/MarkerCluster.css" />
</head>
<style>
html, body, #container, #map {
padding: 0;
margin: 0;
}
html, body, #map, #container {
height: 100%;
}
#sidepanel {
height: 90%;
}
.info {
padding: 6px 8px;
font: 14px/16px Calibri, Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
width: 400px;
}
.info h4 {
margin: 0 0 5px;
color: #777;
}
.leaflet-google-layer {
z-index: 0 !important;
}
//.leaflet-map-pane {
// z-index: 100;
/* This is for google maps */
//}
#container {
// position: relative;
}
#map {
display:inline-block;
width: 100%;
}
#sidepanel {
padding-top: 35px;
padding-bottom: 25px;
padding-left: 15px;
width: 25%;
min-width: 200px;
position: absolute;
z-index: 10;
font: 14px Calibri, Arial, Helvetica, sans-serif;
overflow: auto;
//background: -webkit-linear-gradient(left, rgba(153,189,188,0), rgba(153,189,188,1)); /* For Safari 5.1 to 6.0 */
//background: -o-linear-gradient(right, rgba(153,189,188,0), rgba(153,189,188,1)); /* For Opera 11.1 to 12.0 */
//background: -moz-linear-gradient(right, rgba(153,189,188,0), rgba(153,189,188,1)); /* For Firefox 3.6 to 15 */
//background: linear-gradient(to left, rgba(153,189,188,0), rgba(153,189,188,1)); /* Standard syntax (must be last) */
//background: linear-gradient(to left, rgba(248, 227, 190,0), rgba(248, 227, 190,1)); /* Standard syntax (must be last) */
// from http://www.colorzilla.com/gradient-editor/
background: -moz-linear-gradient(left, rgba(255,255,255,1) 0%, rgba(255,255,255,0.8) 60%, rgba(255,255,255,0) 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, right top, color-stop(0%,rgba(255,255,255,1)), color-stop(60%,rgba(255,255,255,0.8)), color-stop(100%,rgba(255,255,255,0))); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0.8) 60%,rgba(255,255,255,0) 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0.8) 60%,rgba(255,255,255,0) 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0.8) 60%,rgba(255,255,255,0) 100%); /* IE10+ */
background: linear-gradient(to right, rgba(255,255,255,1) 0%,rgba(255,255,255,0.8) 60%,rgba(255,255,255,0) 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#00ffffff',GradientType=1 ); /* IE6-9 */
}
#sidepanel h4 {
margin: 40px 0 5px;
padding-left: 35px;
font: 20px Calibri, Arial, Helvetica, sans-serif;
}
</style>
<body>
<div id="container">
<div id="sidepanel">
<h4>Pune Info</h4>Click on an item to see its details.
</div>
<div id="map"></div>
</div>
<script src="lib/leaflet.js"></script> <!-- Orginal: http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js -->
<script src="lib/leaflet-search.js"></script> <!-- from http://labs.easyblog.it/maps/leaflet-search/examples/nominatim.html -->
<script src="lib/leaflet-measure.js"></script> <!-- from https://github.com/ljagis/leaflet-measure -->
<script src='lib/leaflet-omnivore.min.js'></script> <!--Original: https://api.tiles.mapbox.com/mapbox.js/plugins/leaflet-omnivore/v0.2.0/leaflet-omnivore.min.js -->
<script src="lib/leaflet.label.js"></script> <!-- from https://github.com/Leaflet/Leaflet.label -->
<script src="lib/papaparse.min.js"></script> <!-- from http://papaparse.com -->
<!-- Scripts for Google maps layer,taken from http://bl.ocks.org/crofty/2197701 -->
<!-- <script src="http://maps.google.com/maps/api/js?v=3.2&sensor=false"></script> Don't want to depend on google right now..-->
<script src="lib/leaflet-google.js"></script> <!--Original: http://matchingnotes.com/javascripts/leaflet-google.js -->
<script src="lib/Control.FullScreen.js"></script> <!-- From https://github.com/brunob/leaflet.fullscreen -->
<script src="lib/turf.min.js"> </script> <!-- from http://api.tiles.mapbox.com/mapbox.js/plugins/turf/v2.0.0/turf.min.js -->
<script src="lib/leaflet.markercluster-src.js"> </script> <!-- from http://api.tiles.mapbox.com/mapbox.js/plugins/turf/v2.0.0/turf.min.js -->
<script>
var DATAPATH = "data/"
var PICSPATH = DATAPATH + 'ER_pics/';
var SidePanelDefault = "<h4>Pune Info</h4>Click on an item to see its details.";
var osmLink = '<a href="http://openstreetmap.org">OpenStreetMap</a>';
var mapboxUrl = 'https://{s}.tiles.mapbox.com/v3/{id}/{z}/{x}/{y}.png';
var MBAttrib = '&copy; ' + osmLink + ' Contributors & <a href="https://www.mapbox.com/about/maps/">Mapbox</a>';
var MBstreets = L.tileLayer(mapboxUrl, {id: 'nikhilsheth.m0mlpl2d', attribution: MBAttrib}),
MBsatlabel = L.tileLayer(mapboxUrl, {id: 'nikhilsheth.m0mmaa87', attribution: MBAttrib}),
MBsat = L.tileLayer(mapboxUrl, {id: 'nikhilsheth.m0mni8e7', attribution: MBAttrib}),
MBemerald = L.tileLayer(mapboxUrl, {id: 'nikhilsheth.m0mmebk6', attribution: MBAttrib}),
MBrun = L.tileLayer(mapboxUrl, {id: 'nikhilsheth.m0mmicn2', attribution: MBAttrib}),
MBlight = L.tileLayer(mapboxUrl, {id: 'nikhilsheth.m0mmobne', attribution: MBAttrib}),
MBbw = L.tileLayer(mapboxUrl, {id: 'nikhilsheth.m0mn13df', attribution: MBAttrib}),
MBdark = L.tileLayer(mapboxUrl, {id: 'nikhilsheth.jme9hi44', attribution: MBAttrib}),
MBpencil = L.tileLayer(mapboxUrl, {id: 'nikhilsheth.m0mn5lf5', attribution: MBAttrib}),
MBpirates = L.tileLayer(mapboxUrl, {id: 'nikhilsheth.m0mn8b72', attribution: MBAttrib}),
MBwheatpaste = L.tileLayer(mapboxUrl, {id: 'nikhilsheth.m0mnld61', attribution: MBAttrib}),
MBcomic = L.tileLayer(mapboxUrl, {id: 'nikhilsheth.m0mo16hg', attribution: MBAttrib}),
OsmIndia = L.tileLayer(mapboxUrl, {id: 'openstreetmap.1b68f018', attribution: MBAttrib});
var baseLayers = {
"OpenStreetMap.IN": OsmIndia,
// "OpenStreetMap": osmMap,
// "OSM Landscape": landMap,
// "Stamen Toner": stamentoner,
// "Stamen Toner-Lite": stamenlite,
// "Stamen Toner-Hybrid": stamenhybid,
// "Stamen Toner-Labels": stamenlabels,
// "Stamen Toner-Lines": stamenlines,
// "Watercolor": stamenwater,
"Mapbox Streets": MBstreets,
"Mapbox Satellite w/labels": MBsatlabel ,
// "Mapbox Satellite": MBsat ,
// "Mapbox Emerald": MBemerald ,
// "Mapbox Run,Cycle": MBrun ,
"Mapbox Light": MBlight ,
// "Mapbox Black and White": MBbw ,
"Mapbox Dark": MBdark ,
"Mapbox Pencil": MBpencil ,
// "Mapbox Pirates": MBpirates ,
// "Mapbox Wheatpaste": MBwheatpaste ,
// "Mapbox Comic": MBcomic ,
// "Google Maps": googleLayer
};
var map = L.map('map', {
layers: [OsmIndia], //only add one! put the rest in the baselayers group
fullscreenControl: true,
fullscreenControlOptions: {
position: 'topleft'
}
})
.setView([18.53,73.85], 12)
.on('overlayremove', function(e) { // to reset the side panel
document.getElementById('sidepanel').innerHTML = SidePanelDefault;
});
var wards = defaultArea().addTo(map); // Initializing and Pre-loading initial/default overlay
var overlays = {
"Electoral Wards": wards
};
var layerControl = L.control.layers(baseLayers, overlays, {collapsed: false}).addTo(map);
//making a variable of the layer control, so we can dynamically add new layers later on. From http://stackoverflow.com/a/25385141/4355695
//#################################################################
// ELECTORAL WARDS ... SYNC'D WITH INFO CSV
//omnivore.geojson(DATAPATH+'pune-electoral-wards.geojson', null, wards);
//so basically we added this layer to the main map as an empty (but initialized) one and then later loaded it up with data, and that's OK!
Papa.parse(DATAPATH + 'pune-wards-info.csv', {
download: true,
header: true,
complete: function(results) {
var PuneInfoLayer = L.geoJson(null, {
style: {weight: 2, opacity: 1, color: 'black', dashArray: '4', fillOpacity: 0.2, fillColor: "orange" },
onEachFeature: function(feature, layer) { //below code will be executed for each item in the layer
;var popupText = "";
var labelText = "";
var detailsText = "";
if (feature.properties && feature.properties.name && turf.area(feature)) {
//Here it comes! The part where we look up the CSV data for a row having the same name as the map item's name
labelText = feature.properties.name.toString();
var filtered = results.data.filter(function(data){return data.name == this;}, labelText);
detailsText = "<h4>" + labelText + ' : ' + filtered[0]['title'] + "</h4>";
labelText += ' : ' + filtered[0]['title'];
//console.log(JSON.stringify(filtered[0]));
detailsText += 'Admin ward: <b>' + filtered[0]['adminward'] + '</b><br><br>';
detailsText += '<b>Voters:</b> ' + filtered[0]['voters'] + ', ' + filtered[0]['female_voters'] + ' female, ' + filtered[0]['male_voters'] + ' male<br><br>';
detailsText += '<b>Elected Representatives:</b><br><table cellspacing="20">';
detailsText+= '<tr><td><img src="'+ PICSPATH + filtered[0]['wardnum'] +'A.jpg" height="100px"><br>A: ' + filtered[0]['ER_A'] + '</td><td><img src="'+ PICSPATH + filtered[0]['wardnum'] +'B.jpg" height="100px"><br>B: ' + filtered[0]['ER_B'] + '</td></tr></table>';
detailsText += 'Municipal Polling Stations: <b>' + filtered[0]['total_polling_buildings'] + '</b> buildings, <b>' + filtered[0]['total_polling_stations'] + '</b> stations.<br><br>';
detailsText += '<b>Area:</b> ' + Math.round(turf.area(feature)*(1e-6)*100)/100 + ' sq.km <br>';
detailsText += '<small><b>Areas / Landmarks : </b>' + filtered[0]['areas'] + '</small>';
}
layer.bindLabel(labelText); //removing .bindPopup(detailsText);
layer.on({
click: function() {
document.getElementById('sidepanel').innerHTML = detailsText;
}
});
} //end onEachFeature
}); //end layer defining. We still have to load the data into it!!
omnivore.geojson(DATAPATH+'pune-electoral-wards.geojson', null, PuneInfoLayer).addTo(wards);
} //Papa.parse's complete: section over
}); // Papa.parse function over.
//#### NOW ADD MORE LAYERS #######
//GARDENS
var gardenslayer = circleMarker("green","Gardens in Pune");
omnivore.csv('http://cors.io/?u=https://docs.google.com/spreadsheets/d/1Ec3Yegt1fIOTpyUv8DcLBsVFlu1P1ibGtQfTV4rU13w/export?format=csv', null, gardenslayer)
.on('error', function() {
// fired if the layer can't be loaded over AJAX
// or can't be parsed
console.log("HEY! ERROR WHILE LOADING GARDENS!");
});
//backup CSV path: DATAPATH+'pune_gardens.csv'
layerControl.addOverlay(gardenslayer , "Gardens");
//SCHOOLS
var schoolslayer = circleMarker("blue","Government Schools",["name","School_No"]);
omnivore.csv(DATAPATH+'pune-school-locations.csv', null, schoolslayer);
layerControl.addOverlay(schoolslayer , "Schools");
//HOSPITALS
var hospitalslayer = circleMarker("red","Government Medical Infrastructure");
omnivore.csv(DATAPATH+'pune-medical.csv', null, hospitalslayer);
layerControl.addOverlay(hospitalslayer , "Medical");
//ADMIN WARDS
var adminlayer = defaultArea("magenta","Administrative Wards");
omnivore.geojson(DATAPATH+'pune-admin-wards.geojson', null, adminlayer);
omnivore.csv(DATAPATH+'pune-admin-ward-offices.csv', null, adminlayer);
layerControl.addOverlay(adminlayer , "Admin Wards");
//ASSEMBLY CONSTITUENCIES
var assemblylayer = defaultArea("blue","Assembly Constituencies");
omnivore.geojson(DATAPATH+'pune-assembly-constituences.geojson', null, assemblylayer);
layerControl.addOverlay(assemblylayer , "Assembly Constituencies");
//PMC ELECTION POLLING BOOTHS
//CLUSTERING this layer.. we have to load it in the .on('ready') function. Else it doesn't load.
var pmcpollingboothslayer = L.geoJson(null, { onEachFeature: defaultOnEachFeature });
omnivore.csv(DATAPATH+'pmc_polling_booths.csv', null, pmcpollingboothslayer)
.on('error', function() { console.log("HEY! ERROR WHILE LOADING PMC POLLING BOOTHS!"); })
.on('ready', function(e) {
var clustermarkers = new L.markerClusterGroup( { disableClusteringAtZoom: 16 } );
clustermarkers.addLayer(pmcpollingboothslayer);
layerControl.addOverlay(clustermarkers , "PMC polling boths");
});
//#########################################################################################
//######### FUNCTIONS #########
//#### DEFAULT EMPTY GEOJSON LAYERS FOR DEFINING CLICKING ETC
function circleMarker(color, title, fields) {
if(!color) color="orange";
return L.geoJson(null, {
onEachFeature: function (feature, layer) { defaultOnEachFeature(feature, layer, title, fields); },
// style: {weight: 2, opacity: 1, color: 'black', dashArray: '4', fillOpacity: 0.2, fillColor: color },
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
radius: 6,
fillColor: color,
color: "#000",
weight: 1,
opacity: 1,
fillOpacity: 0.8
});
}
});
}
function defaultArea(color, title, fields) {
if(!color) color="orange";
return L.geoJson(null, {
onEachFeature: function (feature, layer) { defaultOnEachFeature(feature, layer, title, fields); },
style: {weight: 2, opacity: 1, color: 'black', dashArray: '4', fillOpacity: 0.2,
fillColor: color }
});
}
function defaultOnEachFeature(feature, layer, title, fields) {
var popupText = "";
var labelText = "";
var detailsText = "<h4>Pune Info";
if (title) detailsText += ': ' + title + '</h4>';
else detailsText += '</h4>';
if (feature.properties && feature.properties.name) {
popupText += "<b>" + feature.properties.name.toString() + "</b>";
labelText = feature.properties.name.toString();
}
/*
if (feature.properties && feature.properties.description) {
popupText += "<br>" + feature.properties.description.toString();
}*/
if (fields) {
for (var prop in fields) {
detailsText += "<b>" + fields[prop] + ":</b> " + feature.properties[fields[prop]] + "<br>";
}
} else {
for (var prop in feature.properties) {
//if ( ( feature.properties[prop].toLowerCase().indexOf('.jpg') != -1 ) && ( feature.properties[prop].toLowerCase().indexOf('<img') == -1 ) )
//detailsText += '<img src="'+DATAPATH;
//else
detailsText += "<b>" + prop + ":</b> " + feature.properties[prop] + "<br>";
}
}
layer.bindPopup(popupText).bindLabel(labelText);
layer.on({
click: function() {
document.getElementById('sidepanel').innerHTML = detailsText;
}
});
}
function areastyle(feature) {
return {
weight: 2,
opacity: 1,
color: 'black',
dashArray: '4',
fillOpacity: 0.2,
fillColor: "orange"
//fillColor: 'magenta'
};
}
function easynumber(N) {
F = parseFloat(N);
if(F == 0) return "No expenditure";
else if (!F) return N;
else if(F >= 10000000 ) return "Rs." + F/10000000 + " crore";
else if(F >= 100000 ) return "Rs." + F/100000 + " lakh";
else if(F >= 1000 ) return "Rs." + F/1000 + " thousand";
else if(F >= 1) return "Rs." + F;
}
function commanumber(N) {
F = parseInt(N);
if( F >= 1000 ) return F;
return F;
}
//for getting lat and long
var popup = L.popup();
function onMapClick(e) {
popup
.setLatLng(e.latlng)
.setContent("" + e.latlng.toString())
.openOn(map);
}
map.on('click', onMapClick);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment