Leaflet map depicting NYC OpenData 311 Service Requests for Food Poisoning using Font Awesome icons, Leaflet Marker Clusters and the beautiful Stamen Toner base map via Leaflet Providers. Click on a marker cluster to zoom in for a greater level of detail.
Last active
July 11, 2017 16:48
-
-
Save gmculp/5897b1ac22eb4a0d122d to your computer and use it in GitHub Desktop.
Leaflet Map of Brooklyn 311 Food Poisoning Complaints
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Leaflet Example</title> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<!--<link rel="stylesheet" href="http://npmcdn.com/leaflet@0.7.7/dist/leaflet.css" /> --> | |
<link rel="stylesheet" href="http://npmcdn.com/leaflet@1.1.0/dist/leaflet.css" /> <!--necessary for using Leaflet--> | |
<link rel="stylesheet" href="http://leaflet.github.io/Leaflet.markercluster/dist/MarkerCluster.css" /> <!--necessary for marker cluster plugin--> | |
<link rel="stylesheet" href="marker_styles.css" /> <!--for markers--> | |
<!--<link href="http://netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.css" rel="stylesheet">--> | |
<!--<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">--> | |
<!--necessary for using FontAwesome--> | |
<link rel="stylesheet" href="https://unpkg.com/font-awesome-base64"/> | |
<style> | |
html, body { height: 100%; width: 100%; margin: 0; } | |
#map { | |
height: 100%; | |
width: 100%; | |
} | |
.info { | |
padding: 8px 18px; | |
background: white; | |
background: rgba(255,255,255,0.9); | |
box-shadow: 0 0 15px rgba(0,0,0,0.2); | |
border-radius: 5px; | |
} | |
.info h4, .info h3, .info h2 { | |
margin: 0px; | |
} | |
.lame { | |
margin-right: 8px; | |
vertical-align: middle; | |
display: inline-block; | |
} | |
.legend { | |
font: 14px/16px Arial, Helvetica, sans-serif; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="map"></div> | |
<!--<script src="https://npmcdn.com/leaflet@0.7.7/dist/leaflet.js"></script> --> | |
<script src="http://npmcdn.com/leaflet@1.1.0/dist/leaflet.js"></script> <!--necessary for using Leaflet--> | |
<!--<script src='http://rawgit.com/leaflet-extras/leaflet-providers/master/leaflet-providers.js' type='text/javascript'></script> --> | |
<!--<script src='https://leaflet-extras.github.io/leaflet-providers/leaflet-providers.js' type='text/javascript'></script> --> | |
<!--necessary for using Leaflet providers base maps--> | |
<!--<script src='https://rawgithub.com/leaflet-extras/leaflet-providers/gh-pages/leaflet-providers.js' type='text/javascript'></script> necessary for using Leaflet providers base maps--> | |
<script src="http://maps.stamen.com/js/tile.stamen.js?v1.2.1"></script> | |
<script src="http://code.jquery.com/jquery-1.9.1.js"></script> | |
<!--<script src="https://rawgit.com/Leaflet/Leaflet.markercluster/leaflet-0.7/dist/leaflet.markercluster.js"></script> --> | |
<script src="http://leaflet.github.io/Leaflet.markercluster/dist/leaflet.markercluster-src.js"></script> <!--necessary for marker cluster plugin--> | |
<script> | |
var zoom = 12; //set zoom level here | |
var center = [40.658528, -73.952551]; //set center coordinates here [latitude, longitude] | |
var map = L.map('map').setView(center, zoom); | |
//DECLARING BASE MAP... check out available base maps here: http://leaflet-extras.github.io/leaflet-providers/preview/ | |
//var provider_name = "Stamen.Toner"; //enter provider name here | |
//L.tileLayer.provider(provider_name).addTo(map); //apply title layer | |
var watercolor = new L.StamenTileLayer("toner").addTo(map); | |
map.attributionControl.setPrefix('311 Complaints via NYC OpenData'); //add data source to map's attribute text | |
var complaint_boro = "Brooklyn"; //enter target borough here | |
var complaint_type = "Food Poisoning"; //enter complaint type here | |
var cat_field = "location_type"; //category field... either descriptor or location_type | |
//set-up dates for complaints placed within past 6 days | |
var start_date = new Date(); | |
//start_date.setDate(start_date.getDate()-6); | |
start_date.setYear(start_date.getFullYear()-1); | |
var end_date = new Date(); | |
//var the_URL = "http://data.cityofnewyork.us/resource/erm2-nwe9.json"; //API Access Endpoint | |
var the_URL = "https://data.cityofnewyork.us/resource/fhrw-4uyv.json"; //new API Access Endpoint | |
the_URL += "?"; //a query parameter name is preceded by the question mark | |
the_URL += "$where="; //Filters the rows to be returned | |
the_URL += "(latitude IS NOT NULL)"; //only return records with coordinates | |
the_URL += " AND "; | |
the_URL += "(borough='" + complaint_boro.toUpperCase() + "')"; //borough of interest | |
the_URL += " AND "; | |
the_URL += "(complaint_type='" + complaint_type + "')"; //desired two complaint types nested within parentheses | |
the_URL += " AND "; | |
the_URL += "(created_date>='" + f_date(start_date, 'y_mm_dd') + "') AND (created_date<='" + f_date(end_date, 'y_mm_dd') + "')"; //date range | |
the_URL += "&$group=complaint_type,location_type,incident_address,borough,incident_zip,latitude,longitude"; //fields to group by | |
the_URL += "&$select=complaint_type,location_type,incident_address,borough,incident_zip,latitude,longitude,count(*)"; //fields to return | |
//the_URL += "&callback=?" | |
the_URL = encodeURI(the_URL); //encode special characters such as spaces and quotes | |
//var xhr = new XMLHttpRequest({mozSystem: true}); | |
console.log("test"); | |
$.getJSON(the_URL, function (json) { | |
// console.log("test2"); | |
//}); | |
//$.ajax({ | |
// url: the_URL, | |
// type: "GET", | |
//jsonp: "$jsonp", | |
// dataType: "jsonp" | |
//}).done(function(json) { | |
// console.log(json[0].toSource()); | |
//create distinct array of all location types | |
var marker_array = []; | |
var desc_array = $.distinct($.map( json, function( d, i ) { return d[cat_field]; })); | |
//create symbols object | |
//assign Font Awesome icon classes... see http://fortawesome.github.io/Font-Awesome/icons/ | |
//assign marker style via class... see markers.css | |
var symbols = {}; | |
symbols["Cafeteria - College/University"]={icon:"fa-university",color:"my_marker0"}; | |
symbols["Cafeteria - Private School"]={icon:"fa-bell-o",color:"my_marker0"}; | |
symbols["Cafeteria - Public School"]={icon:"fa-bell",color:"my_marker0"}; | |
symbols["Catering Service"]={icon:"fa-male",color:"my_marker0"}; | |
symbols["Correctional Facility - City"]={icon:"fa-building-o",color:"my_marker0"}; | |
symbols["Food Cart Vendor"]={icon:"fa-truck",color:"my_marker0"}; | |
symbols["Other (Explain Below)"]={icon:"fa-asterisk",color:"my_marker0"}; | |
symbols["Restaurant/Bar/Deli/Bakery"]={icon:"fa-cutlery",color:"my_marker0"}; | |
symbols["Soup Kitchen"]={icon:"fa-users",color:"my_marker0"}; | |
symbols["Senior Center"]={icon:"fa-wheelchair",color:"my_marker0"}; | |
symbols["Street Fair Vendor"]={icon:"fa-road",color:"my_marker0"}; | |
symbols["Summer Camp"]={icon:"fa-sun-o",color:"my_marker0"}; | |
//iterate through 311 Service Requests JSON URL | |
$.each(json, function(c, c_data){ | |
var this_icon, this_color; | |
//assign default symbology to unknown location types | |
if (symbols[c_data[cat_field]]){ | |
this_icon = symbols[c_data[cat_field]].icon; | |
this_color = symbols[c_data[cat_field]].color; | |
} | |
else { | |
this_icon = symbols[c_data["Other (Explain Below)"]].icon; | |
this_color = symbols[c_data["Other (Explain Below)"]].color; | |
} | |
//create symbol for each record | |
var my_symbol = L.divIcon({ | |
className: 'my_marker ' + this_color, //symbol color | |
iconSize: null, | |
html: '<i class="fa ' + this_icon + '"></i>' //symbol icon | |
}); | |
//create pop-up html content | |
var popup_html = c_data.complaint_type; | |
popup_html += "<br>"; | |
popup_html += c_data.location_type; | |
popup_html += "<br>"; | |
popup_html += c_data.count + " complaint(s) at this location"; | |
popup_html += "<br>"; | |
popup_html += c_data.incident_address; | |
popup_html += "<br>"; | |
popup_html += c_data.borough + ", NY " + c_data.incident_zip; | |
//create marker | |
var mark=L.marker([c_data.latitude, c_data.longitude], {icon: my_symbol}).bindPopup(popup_html); | |
//add marker to array | |
marker_array.push(mark); | |
}); | |
//add marker array to layer group... all markers will be treated as one layer | |
var marker = new L.layerGroup(marker_array); | |
//Custom radius and icon create function | |
var markers = L.markerClusterGroup({ | |
iconCreateFunction: function (cluster) { | |
var childCount = cluster.getChildCount(); | |
var c = ' marker-cluster-'; | |
var sz = 40; //use for symbolizing by size | |
if (childCount < 10) { | |
c += 'small'; | |
sz = 30; //comment out if using uniform size marker clusters | |
} else if (childCount < 100) { | |
c += 'medium'; | |
sz = 40; //comment out if using uniform size marker clusters | |
} else { | |
c += 'large'; | |
sz = 50; //comment out if using uniform size marker clusters | |
} | |
//create leaflet marker cluster symbol | |
return new L.DivIcon({ html: '<div><span>' + childCount + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(sz, sz) }); | |
} | |
}); | |
//add marker layer group to marker cluster group | |
markers.addLayer(marker); | |
//add marker cluster layer to map | |
map.addLayer(markers); | |
//zoom map to markers layer extent | |
map.fitBounds(markers.getBounds()); | |
//add scale bare | |
L.control.scale().addTo(map); | |
//build legend | |
var legend = L.control({position: 'topright'}); //possible positions: topleft, bottomleft, topright, bottomright | |
//legend function | |
legend.onAdd = function (map) { | |
//declare legend div html | |
var div = L.DomUtil.create('div', 'info'); | |
//add legend title | |
div.innerHTML = "<h2>" + complaint_type + "</h2>"; | |
div.innerHTML += "<h3>311 Service Requests</h3>"; | |
div.innerHTML += "<h4>" + f_date(start_date, '/') + " to " + f_date(end_date, '/') + "</h4>"; | |
//iterate through symbols object | |
$.each(symbols, function(key, value) { | |
div.innerHTML += '<div>'; | |
div.innerHTML += '<div class="lame"><div class="my_marker my_marker0"><i class="fa ' + value.icon + '"></i></div></div>'; | |
div.innerHTML += '<div class="lame legend">' + key + '</div>'; | |
div.innerHTML += '</div>'; | |
}); | |
return div; | |
}; | |
//add legend to map | |
legend.addTo(map); | |
//console.log("Request received: " + json[1][cat_field]); | |
}); | |
//function to return distinct function | |
$.extend({ | |
distinct : function(anArray) { | |
var result = []; | |
$.each(anArray, function(i,v){ if ($.inArray(v, result) == -1) result.push(v); }); | |
return result; | |
} | |
}); | |
function f_date(the_date, d_style){ | |
var dd = ('0' + the_date.getDate()).slice(-2); | |
var mm = ('0' + (the_date.getMonth() + 1)).slice(-2); | |
var y = the_date.getFullYear(); | |
return (d_style == "y_mm_dd")? y + '-' + mm + '-' + dd: mm + '/'+ dd + '/' + y; | |
} | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.my_marker div{ | |
text-align: center; | |
font-size: 14px; | |
font-weight: bold; | |
font-family: sans-serif; | |
} | |
.my_marker { | |
width: 20px; | |
height: 20px; | |
border-width: 2px; | |
border-radius:15px; | |
margin-top: 0px; | |
margin-left: -10px; | |
border-style: solid; | |
text-align: center; | |
} | |
.my_marker0 { | |
fill: #FFD700; | |
background: #FFD700; | |
stroke: #444; | |
border-color: #444; | |
} | |
/* MARKER CLUSTER STYLE */ | |
/*all marker cluster */ | |
.marker-cluster { | |
background-clip: padding-box; | |
background-color: rgba(255, 238, 140, 0.6); /* change back color here*/ | |
} | |
.marker-cluster div { | |
text-align: center; | |
font: bold 12px "Helvetica Neue", Arial, Helvetica, sans-serif; | |
background-color: rgba(255, 215, 0, 0.7); /* change fore color here*/ | |
} | |
/*small marker cluster*/ | |
.marker-cluster-small { | |
border-radius: 15px; | |
} | |
.marker-cluster-small div { | |
width: 20px; | |
height: 20px; | |
margin-left: 5px; | |
margin-top: 5px; | |
border-radius: 10px; | |
} | |
.marker-cluster-small span { | |
line-height: 20px; | |
} | |
/*medium marker cluster*/ | |
.marker-cluster-medium { | |
border-radius: 20px; | |
} | |
.marker-cluster-medium div { | |
width: 30px; | |
height: 30px; | |
margin-left: 5px; | |
margin-top: 5px; | |
border-radius: 15px; | |
} | |
.marker-cluster-medium span { | |
line-height: 30px; | |
} | |
/*large marker cluster*/ | |
.marker-cluster-large { | |
border-radius: 30px; | |
} | |
.marker-cluster-large div { | |
width: 40px; | |
height: 40px; | |
margin-left: 5px; | |
margin-top: 5px; | |
border-radius: 20px; | |
} | |
.marker-cluster-large span { | |
line-height: 40px; | |
} | |
/* IE 6-8 fallback colors */ | |
.leaflet-oldie .marker-cluster, .leaflet-oldie .marker-cluster div { | |
background-color: rgba(255, 215, 0, 1); | |
} | |
.leaflet-oldie .marker-cluster div { | |
background-color: rgba(255, 238, 140, 1); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment