I'm using cross filter, dc.js and leaflet to display data on an interactive dashboard.
This is my code for grouping and showing me the data table which is working fine.
.group(function (d) {
var format = d3.format('02d');
return d.Timestamp.getFullYear() + '/' + format((d.Timestamp.getMonth() + 1));
})
.columns([
"Timestamp",
"KMIST_TEMP_ID",
"ID POP",
"CUSTOMER NAME",
"district",
"ward",
]);
I want to return on a map with leaflet my geographic coordinates which are in the data table.
pharmaciesMarkers.clearLayers();
_.each(format.top(Infinity), function (d) {
var name = d.CUSTOMER NAME;
var marker = L.marker([loc.lat, loc.long]);
pharmaciesMarkers.addLayer(marker);
marker.bindPopup("<p>" + CUSTOMER NAME + " " + loc.KMIST_TEMP_ID + " " + loc.ward + "</p>");
});
map.addLayer(pharmaciesMarkers);
map.fitBounds(pharmaciesMarkers.getBounds())
What am I doing wrong here, why the markers do not display on the map?
Related
I'm building a Leaflet map of recent earthquakes that pulls from three APIs to create three layer groups on a map. I'm also building a sidebar with a list of earthquakes from just one of the three API links. When a user clicks on a map marker, a popup appears with more information about the quake. And when they click on a list item in the sidebar, I'd like for the popup for the corresponding quake to open, the same as if the user had clicked on the marker.
The popups work fine when the user clicks on the map markers. However, when I click on the list item, the popups with the correct information do open, but they all appear in the same place, and not at the quake's correct lat/lng.
I tried using FeatureGroup instead of LayerGroup, as suggestedhere, but the same thing happens. I also tried adding the popup to a specific layer with addTo(layers[key]);, but this changes nothing, either.
Here is the relevant code:
URLs = {
PAST_MONTH: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson",
PAST_WEEK: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_week.geojson",
PAST_DAY: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson",
};
Object.entries(URLs).forEach(([key, url]) => {
d3.json(url).then(function (response){
//Add features to the map.
response.features.forEach(feature => {
var calif = feature.properties.title.split(",");
if (calif[1] === " CA") {
console.log(feature.properties.title);
quake = L.geoJSON(feature, {
pointToLayer: function(feature, latlng) {
return L.circleMarker(latlng, {
radius: getRadius(feature.properties.mag),
fillColor: getColor(feature.properties.mag),
color: getColor(feature.properties.mag),
weight: 1,
opacity: 1,
fillOpacity: 0.8
});
},
});
//populate the sidebar with JUST the list of earthquakes in the past week
if (url === URLs.PAST_WEEK) {
var list = document.getElementsByClassName("quake-list");
var listItem = document.createElement("li");
listItem.innerHTML = feature.properties.title + " " + moment(feature.properties.time).startOf('day').fromNow();
list[0].appendChild(listItem);
listItem.addEventListener('click', function() {
quake.bindPopup("<h3>" + feature.properties.title + "</h3><hr>" +
"<strong>Magnitude: </strong>" + feature.properties.mag +
"<br><strong>Date and time: </strong>" + convertUnixTime(feature.properties.time))
quake.openPopup();
})
}
// Add popup with info, when user clicks on map markers
quake.bindPopup("<h3>" + feature.properties.title + "</h3><hr>" +
"<strong>Magnitude: </strong>" + feature.properties.mag +
"<br><strong>Date and time: </strong>" + convertUnixTime(feature.properties.time));
// Add to specific layer
quake.addTo(layers[key]);
};
});
});
});
And this is the specific part where I can't figure out how to bind the popups to the list items so that they appear at the correct lat/lng:
//populate the sidebar with JUST the list of earthquakes in the past week
if (url === URLs.PAST_WEEK) {
var list = document.getElementsByClassName("quake-list");
var listItem = document.createElement("li");
listItem.innerHTML = feature.properties.title + " " + moment(feature.properties.time).startOf('day').fromNow();
list[0].appendChild(listItem);
listItem.addEventListener('click', function() {
quake.bindPopup("<h3>" + feature.properties.title + "</h3><hr>" +
"<strong>Magnitude: </strong>" + feature.properties.mag +
"<br><strong>Date and time: </strong>" + convertUnixTime(feature.properties.time))
quake.openPopup();
})
}
I'm new to Leaflet and would appreciate any help. I'm sure I'm missing something obvious but I've been racking my brain trying to see what I'm missing. Thank you!
Instead adding the popup to the layergroup bind the popup to each layer.
if (url === URLs.PAST_WEEK) {
var list = document.getElementsByClassName("quake-list");
var listItem = document.createElement("li");
listItem.innerHTML = feature.properties.title + " " + moment(feature.properties.time).startOf('day').fromNow();
list[0].appendChild(listItem);
listItem.addEventListener('click', function() {
quake.eachLayer(function(layer){
layer.bindPopup("<h3>" + feature.properties.title + "</h3><hr>" +
"<strong>Magnitude: </strong>" + feature.properties.mag +
"<br><strong>Date and time: </strong>" + convertUnixTime(feature.properties.time),
{autoClose: false})
layer.openPopup();
});
})
}
The problem turns out to be an issue with scope for the "quake" variable. This code works:
var quake = L.geoJSON(feature, {
pointToLayer: function(feature, latlng) {
return L.circleMarker(latlng, {
radius: getRadius(feature.properties.mag),
fillColor: getColor(feature.properties.mag),
color: getColor(feature.properties.mag),
weight: 1,
opacity: 1,
fillOpacity: 0.8
});
},
});
I'm making a bubble map with Mapbox js.
The problem is: I'm trying to stop points appearing if they are below a certain value. Currently values of 0 appear.
The function that sets the points looks like this. I've commented out an if statement that was unsuccessful.
function layer(selecta){
geoJson = L.geoJson(geoJsonData, {
pointToLayer: function(feature, latlng) {
return L.circleMarker(latlng, {
color: '#c10000',
// if (parseInt(feature.properties[selecta]) < 50000){
radius: (Math.sqrt(feature.properties[selecta] / 3.141592) / 50)
// }
}).bindPopup('<h3>' + feature.properties.country + '</h3><p>' + 'Refugees, asylum seekers and IDPs in ' + currentYear + ': ' + numeral(feature.properties['yr' + currentYear]).format('0,0') + '</p>',{
closeButton: true,
minWidth: 320
});
}
}).addTo(map);
};
layer('yr2013');
The data is loaded from a geoJSON file, which is an array of objects.
Every object has properties for each year.
Eg.
{
"type":"Feature",
"geometry":{
"type":"Point",
"coordinates":[
64.585262,
41.377491
]
},
"properties":{
"country":"Uzbekistan",
"yr2000":"39598",
"yr2001":"40923",
"yr2002":"46014",
"yr2003":"45653",
"yr2004":"44932",
"yr2005":"44537",
"yr2006":"1422",
"yr2007":"1060",
"yr2008":"821",
"yr2009":"555",
"yr2010":"311",
"yr2011":"214",
//"yr2012":"176",
//"yr2013":"138"
}
},
If I get rid of the values that have a value below 200, like the above example, this returns an error in the console. The points still display, but it's a massive hack.
Can someone please tell me a cleaner way of doing this?
X EDIT X
I never did find a solution, but I ended up finding a work-around.
Leaflet has CircleMarker options opacity and fillOpacity.
Here's the docs
So I made a function that returns zero if the value is too small, so that the marker will not appear.
It is still technically there though.
Have you tried using Mapbox.js's .setFilter()? It should remove the features you don't want rather than just making them transparent.
https://www.mapbox.com/mapbox.js/example/v1.0.0/markers-with-multiple-filters/
How about this:
function layer(selecta){
geoJson = L.geoJson(geoJsonData, {
pointToLayer: function(feature, latlng) {
//I suppose this is an integer
var number_refugees = numeral(feature.properties['yr' + currentYear]);
if(number_refugees > 0){
var popup_content = '<h3>' + feature.properties.country + '</h3>'
+ '<p>' + 'Refugees, asylum seekers and IDPs in ' + currentYear + ': '
+ number_refugees.format('0,0') + '</p>';
return L.circleMarker(latlng, {
color: '#c10000',
radius: (Math.sqrt(feature.properties[selecta] / 3.141592) / 50)
}).bindPopup(popup_content),{
closeButton: true,
minWidth: 320
});
}
}
}).addTo(map);
};
layer('yr2013');
I can create a HeatMap using d3.js, dc.js and crossfilter, using data in a CSV file.
code:
var chart = dc.heatMap("#test");
d3.csv("morley.csv", function(error, experiments) {
var ndx = crossfilter(experiments),
runDim = ndx.dimension(function(d) { return [+d.Run, +d.Expt]; }),
runGroup = runDim.group().reduceSum(function(d) { return +d.Speed; });
chart
.width(45 * 20 + 80)
.height(45 * 5 + 40)
.dimension(runDim)
.group(runGroup)
.keyAccessor(function(d) { return +d.key[0]; })
.valueAccessor(function(d) { return +d.key[1]; })
.colorAccessor(function(d) { return +d.value; })
.title(function(d) {
return "Run: " + d.key[0] + "\n" +
"Expt: " + d.key[1] + "\n" +
"Speed: " + (299000 + d.value) + " km/s";})
.colors(["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"])
.calculateColorDomain();
chart.render();
});
but i want to do this same thing using JSON data, perhaps an array with some json data. This seems like a noob question but I was unable to find any example which uses JSON data for heatmap.
If you're using a JSON file then the code is going to be largely the same just replace d3.csv with d3.json.
If the data is already loaded in the browser then you can remove this function and just run:
var experiments = JSON.parse(json_object) //if json_object is still a string
var ndx = crossfilter(experiments)
and the rest of the code will be the same.
jsFiddle of the issue
I'm trying to filter over two dimensions in Crossfilter. I have an array of data that repeats a given object:
{
"bioguide_id" : "O000167",
"first_name" : "Barack",
"last_name" : "Obama",
"party" : "Democrat",
"religion" : "United Church of Christ",
"pac_id" : "C00404145",
"pac_name" : "Mendocino County Democratic Central Committee",
"contribution_count" : 4,
"contribution_sum" : 1264,
"congress" : 110
}
I have the following query to crossfilter working perfectly:
var contributions = data.dimension(function(c) { return c.contribution_sum; });
contributions.top(3).forEach(function(p, i) {
console.log(p.pac_name + " (" + p.congress + ")" + ": $" + p.contribution_sum);
});
However, how can I get the top X contribution_sum's from Y congress? I've attempted the following to no avail:
var congress = data.dimension(function(c) { return c.congress; });
congress.filter(111).group().order(function(c) { return c.contribution_sum; }).top(3).forEach(function(p, i) {
console.log(p.pac_name + " (" + p.congress + ")" + ": $" + p.contribution_sum);
});
Any thoughts on why?
I believe you will want to filter on your 'congress' dimension and then ask for the top members of your 'contribution_sum' dimension like:
var congress = data.dimension(function(c) { return +c.congress; });
var contributions = data.dimension(function(c) { return +c.contribution_sum; });
congress.filter(111);
contributions.top(3).forEach(function(p, i) {
console.log(p.pac_name + " (" + p.congress + ")" + ": $" + p.contribution_sum);
});
Complete JSFiddle: http://jsfiddle.net/eWr8Z/5/
Well I'm trying to combine 2 examples to search from my geojson file, and when selected it will zoom that property.
Search (search in GeoJSON one) http://labs.easyblog.it/maps/leaflet-search/examples/
Zoom part from here (without the dropdown) http://projects.bryanmcbride.com/leaflet/select_zoom.html
Basically the first one just with zoom from second. I have been able to get them working, but separately.
Codes (1.)
var featuresLayer = new L.GeoJSON(piirid, {
style: function(f) {
return {color: f.properties.color };
}
});
map.addLayer(featuresLayer);
var searchControl = new L.Control.Search({layer: featuresLayer, propertyName: 'L_AADRESS', circleLocation:false});
searchControl.on('search_locationfound', function(e) {
e.layer.setStyle({fillColor: '#3f0', color: '#0f0'});
}).on('search_collapsed', function(e) {
featuresLayer.eachLayer(function(layer) { //restore feature color
featuresLayer.resetStyle(layer);
});
});
map.addControl( searchControl ); //inizialize search control
Code 2.
// Loop through the geojson features and extract bbox and name, then add the options to the "zoom" select (no jQuery)
/*var select = document.getElementById("zoom");
select.options[select.options.length] = new Option('Select a State', 'Select a State');
for (each in geojson._layers) {
var bbox = geojson._layers[each].getBounds()._southWest.lat + "," + geojson._layers[each].getBounds()._southWest.lng + "," + geojson._layers[each].getBounds()._northEast.lat + "," + geojson._layers[each].getBounds()._northEast.lng;
select.options[select.options.length] = new Option(geojson._layers[each].feature.properties.name, bbox);
}*/
// Loop through the geojson features and extract bbox and name, then add the options to the "zoom" select and build autocomplete(using jquery)
var options = '<option value="Select a State">Select a State</option>';
var states = [];
for (each in geojson._layers) {
var L_AADRESS = geojson._layers[each].feature.properties.L_AADRESS;
var swLat = geojson._layers[each].getBounds()._southWest.lat;
var swLng = geojson._layers[each].getBounds()._southWest.lng;
var neLat = geojson._layers[each].getBounds()._northEast.lat;
var neLng = geojson._layers[each].getBounds()._northEast.lng;
var bbox = swLat + "," + swLng + "," + neLat + "," + neLng;
// Populate states array and build autocomplete
states.push({label: L_AADRESS, value: L_AADRESS, swlat: swLat, swlng: swLng, nelat: neLat, nelng: neLng});
$( "#search" ).autocomplete({
source: states,
minLength: 3,
select: function( event, ui ) {
map.fitBounds([[ui.item.swlat, ui.item.swlng],[ui.item.nelat, ui.item.nelng]]);
}
});
// Add states & bbox values to zoom select options
options += '<option value="' + bbox + '">' + geojson._layers[each].feature.properties.L_AADRESS + '</option>';
}
If I understand your question correctly you just have to add:
map.fitBounds(e.layer.getBounds());
to the search_locationfound event function.
This will set the maximum zoom level that you can still see the whole layer.
So the search_locationfound event function from your first example would be:
searchControl.on('search_locationfound', function(e) {
map.fitBounds(e.layer.getBounds());
e.layer.setStyle({fillColor: '#3f0', color: '#0f0'});
});
jsfiddle