Prevent plotting scaled point with zero value, Mapbox, leaflet - javascript

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');

Related

Leaflet: .bindPopup with layer groups opens popup in the same spot for all features

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

leaflet popup width on markers loaded with JSON

I have a leaflet map with markers loaded dynamically with geoJSON data:
mapLayers.markets = L.geoJSON(geoData, {
pointToLayer: function (feature, latlng) {
switch (feature.properties.status) {
case "stress":
return L.marker(latlng, { icon: iconA });
case "alert":
return L.marker(latlng, { icon: iconB });
case "crisis":
return L.marker(latlng, { icon: iconC });
}
},
onEachFeature: onEachFeature,
}).addTo(map);
I bind the popup inside the onEachFeature function:
function onEachMarketFeature(feature, layer) {
var tableCommon = '<table>' +
'<thead><tr><th>Agency</th><th>Personnel</th></tr></thead>' +
'<tbody>' +
'<tr><td>Alfa</td><td>12</td></tr>' +
'<tr><td>Bravo</td><td>123</td></tr>' +
'</tbody></table>';
var tableSingle='<table>' +
'<thead><tr><th>Agency</th><th>Personnel</th></tr></thead>' +
'<tbody>' +
'<tr><td>Juliet</td><td>62</td></tr>' +
'<tr><td>Lima</td><td>41</td></tr>' +
'</tbody></table>';
var container = '<div><div class="row"><div class="col-xl-6">' + tableCommon + '</div><div class="col-xl-6">' + tableSingle + '</div></div></div>';
layer.bindPopup(feature.properties.type +
'<br/> Market Code=' + feature.properties.code + '<br/>' + feature.properties.name + '</a><br/>' + container);
}
The problem is that the content inside the popup, defined with row-col classes of bootstrap4, is greater than the popup and this one is not resized to fit it, and results of a smaller width.
I've tried to set in the bindPopup function the {maxWidth: "none"} but it's even worst and the popup is extra small.
This is what I get:
and this is what I'm trying to have:

Proper formatting and distribution on multiple events for a jquery button click?

For now, on a button click I have it so that it takes in data from two textboxes, and uses it to
1) append tweets to a panel, and
2) drop pins on a map.
My next step is to have it so that on the button click, it geodecodes a location, and does the same thing. I feel like my jquery.click function is getting really big, and wanted to know if there was a standard way to "separate" it out to make it look prettier and more readable. Can you typically have javascript functions within a jquery file that are called upon, or what is the way to go?
Here is my current jquery file. As you can see it's very big but what happens is straight forward: searchbutton on click takes some values, and sets up a new map in that location, then I access my web server's information, append it to a panel, and also drop pins on a map.
$(function () {
$("#search-button").click(function() {
// variables for google maps
var LatValue = parseFloat($("#searchLat").val());
var LonValue = parseFloat($("#searchLon").val());
var myLatLng = {lat: LatValue, lng: LonValue};
var map = new google.maps.Map(document.getElementById('map-canvas'), {
zoom: 12,
center: myLatLng
});
$.getJSON(
"http://localhost:3000/tw",
{
geoSearchWord: $("#searchme").val(),
geoSearchWordLat: $("#searchLat").val(),
geoSearchWordLon: $("#searchLon").val(),
geoSearchWordRad: $("#searchRadius").val()
}
).done(function (result) {
$("#fromTweets").empty();
console.log(result);
for (i = 0; i < result.statuses.length; i++) {
//Print out username and status
$("#fromTweets").append('<b>' + "Username: " + '</b>' + result.statuses[i].user.screen_name + '<br/>');
$("#fromTweets").append('<b>' + "Tweet: " + '</b>' + result.statuses[i].text + '<br/>');
$("#fromTweets").append('<b>' + "Created at: " + '</b>' + result.statuses[i].created_at + '<br/>');
if (result.statuses[i].geo !== null) {
//Print out the geolocation
$("#fromTweets").append('<b>' + "GeoLocation: " + '</b>' + "Lat: " + result.statuses[i].geo.coordinates[0] + " Lon: " + result.statuses[i].geo.coordinates[1] + '<br/>'+ '<br/>');
//dropping a new marker on the map for each tweet that has lat/lon values
//Multiplying by i * 0.0005 to space them out in case they are from the same gelocation while still holding
//the integrity of their location.
LatValue = parseFloat(result.statuses[i].geo.coordinates[0] + i*0.0005);
LonValue = parseFloat(result.statuses[i].geo.coordinates[1] + i*0.0005);
myLatLng = {lat: LatValue, lng: LonValue};
var newMarker = new google.maps.Marker({
position: myLatLng,
map: map,
animation: google.maps.Animation.DROP,
});
} else {
$("#fromTweets").append('<b>' + "GeoLocation: " + '</b>' + "Cannot be identified" + '<br/>' + '<br/>')
}
}
});
});
The most simple and obvious thing you can do it so split your code by extracting independent logical blocks to functions:
Just something like this:
var map;
function combineTweetsAjaxRequestData()
{
return {
geoSearchWord: $("#searchme").val(),
geoSearchWordLat: $("#searchLat").val(),
geoSearchWordLon: $("#searchLon").val(),
geoSearchWordRad: $("#searchRadius").val()
};
}
function createGMap()
{
return new google.maps.Map(document.getElementById('map-canvas'), {
zoom: 12,
center: {
lat: parseFloat($("#searchLat").val()),
lng: parseFloat($("#searchLon").val())
}
});
}
function createGMarker(coords)
{
var coordsFixed = {
lat: parseFloat(coords[0] + i * 0.0005),
lng: parseFloat(coords[1] + i * 0.0005)
};
return new google.maps.Marker({
position: coordsFixed,
map: map,
animation: google.maps.Animation.DROP,
});
}
function clearInfo() {
$("#fromTweets").empty();
}
function appendInfo(title, text)
{
$("#fromTweets").append('<b>' + title + ':</b> ' + text + '<br/>');
}
function processTweet(tw)
{
appendInfo('Username', tw.user.screen_name);
appendInfo('Tweet', tw.text);
appendInfo('Created at', tw.created_at);
if (tw.geo !== null) {
var twCoords = tw.geo.coordinates;
appendInfo('GeoLocation', "Lat: " + twCoords[0] + " Lon: " + twCoords[1]);
createGMarker(twCoords);
} else {
appendInfo('GeoLocation', "Cannot be identified")
}
}
function loadTweets() {
$.getJSON(
"http://localhost:3000/tw",
combineTweetsAjaxRequestData()
).done(function (result) {
clearInfo();
console.log(result);
result.statuses.forEach(processTweet);
});
}
$(document).ready(function () {
map = createGMap();
$("#search-button").click(function() {
loadTweets();
});
});
Now, it can be easily read as a text. Your code should be readable and understandable from the first glance. Even better, if a non-developer can read it and understand some basic concepts.
What happens when the page is loaded? We create a Google map control and load tweets
How do we load tweets? We make a AJAX request by combining request data from inputs
What happens when it is loaded? We clear out current information and process every tweet
How do we process a single tweet? We output some basic information. Then, we output geolocation if it is available. Otherwise, we output an error.
Now, if you need to add information to another source, you won't extend or modify your loadTweets method - you will extend or modify appendInfo method, because the logics of information output is encapsulated here.

Google Maps API v3: Looping through and adding polylines to a map

I've gone through the forums trying to search for solutions, and I've adapted my code for several problems I've come across, but this one eludes me. I'm trying to plot multiple points on a map (which works) and then draw lines between the points. Each marker contains information, such as latitude and longitude, as well as it's neighbor (the point that I want to draw the line to). I also give it the color I want the line, as different 'routes' can be in different colors. The only I can see being a problem is that I draw a line, set it on the map, and run the loop again for the next marker.
My code places the markers, places the information window for each marker, but doesn't draw the lines between the markers. Can anyone see where I've screwed up, or even if this is possible to do it in the way I'm doing it?
The reason I'm keeping the drawMap function separate is in the event users want to filter out visible data, I need to re-draw the map dynamically. So, I'm keeping the marker list globally so I can reference it even after the function has finished.
My code:
http://www.nku.edu/~sweikatam1/nkuPhysicalMapTest.html
The spot in particular with the issue is the function drawMap, which gets passed an array of objects, markerList, containing the latitude, longitude, color (for the polyline), and other bits of data.
The line placement:
for(var j = 0; j < markerList.length; j++){
if(markerList[j].name == markerList[i].neighbor){
var targetDestination = new google.maps.LatLng(markerList[j].latitude,markerList[j].longitude);
//alert(markerList[i].latitude + " " + markerList[i].longitude + " \r" + markerList[j].latitude + " " + markerList[j].longitude);
}
}
//set the pathway for the two points
var flightPlanCoordinates = [
new google.maps.LatLng(markerList[i].latitude, markerList[i].longitude),
new google.maps.LatLng(targetDestination)
];
//alert("Color: " + markerList[i].routeColor);
//set the line and color
var flightPath = new google.maps.Polyline({
path:flightPlanCoordinates,
strokeColor:markerList[i].routeColor,
strokeOpacity:2,
strokeWeight:5
});
//apply lines
flightPath.setMap(map);
The function as a whole:
function drawMap(markerList){
//alert("Starting drawMap");
//create the map
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 15,
center: new google.maps.LatLng(39.032253, -84.465015),
mapTypeId: google.maps.MapTypeId.HYBRID
});
var marker;
for (var i = 0; i <markerList.length; i++){
/*alert("markerList # i: " + markerList[i].name);
alert("Marker Data: " + "Name: " + markerList[i].name + "\r "
+ "Latitude: " + markerList[i].latitude + "\r "
+ "Longitude: " + markerList[i].longitude + "\r "
+ "Content Type: " + markerList[i].contentType + "\r "
+ "Image: " + markerList[i].image
+ "Color: " + markerList[i].routeColor);*/
var contentData = '<b>'+markerList[i].name +'</b>: '
+ markerList[i].latitude + ' x '
+ markerList[i].longitude + '<br/><p>'
+ markerList[i].contentType
+'</p><img src="' + markerList[i].image + " height=150 width=100>";
//create the marker
marker = new google.maps.Marker({
position: new google.maps.LatLng(markerList[i].latitude,markerList[i].longitude),
draggable: false,
icon: markerList[i].icon,
map:map,
content: contentData,
title:markerList[i].name
});
//find the neighbor for the current marker and set the destination coordinates
for(var j = 0; j < markerList.length; j++){
if(markerList[j].name == markerList[i].neighbor){
var targetDestination = new google.maps.LatLng(markerList[j].latitude,markerList[j].longitude);
//alert(markerList[i].latitude + " " + markerList[i].longitude + " \r" + markerList[j].latitude + " " + markerList[j].longitude);
}
}
//set the pathway for the two points
var flightPlanCoordinates = [
new google.maps.LatLng(markerList[i].latitude, markerList[i].longitude),
new google.maps.LatLng(targetDestination)
];
//alert("Color: " + markerList[i].routeColor);
//set the line and color
var flightPath = new google.maps.Polyline({
path:flightPlanCoordinates,
strokeColor:markerList[i].routeColor,
strokeOpacity:2,
strokeWeight:5
});
//apply lines
flightPath.setMap(map);
//handle open information windows
google.maps.event.addListener(marker,'click', function(){
closeInfos();
var info = new google.maps.InfoWindow({content: this.content});
info.open(map,this);
infos[0]=info;
});
//alert("Marker " + markerList[i].name + " placed.");
}
//alert("drawMap complete. Drawing lines");
<!-- DRAWING LINES COMPLETE -->
}
If anything here seems odd, confusing, or needs clarification, please let me know. Criticisms and ways of doing things better are always welcome. Thanks guys!
In defining, flightPlanCoordinates, the second LatLng was being created out of another LatLng (targetDestination). So just remove the new google.maps.LatLng portion and use the targetDestination directly. With this change, I saw a blue zigzag and a red one being drawn on the map.
var flightPlanCoordinates = [
new google.maps.LatLng(markerList[i].latitude, markerList[i].longitude),
targetDestination
];

Problem with printing Javascript

I'm not the best with Javascript and I seem to have got stuck.
I have a map in place and I need the Lat/Long for a location (that's fine) but the output comes in an alert box. I just need the values themselves.
E.g document.write(latt); document.write(longg);
At the moment this is the code that outputs the box:
function showPointLatLng(point)
{
alert("Latitude: " + point.lat() + "\nLongitude: " + point.lng());
}
Any help would be great, Thanks!
P.S
I think this is the controller:
function usePointFromPostcode(postcode, callbackFunction) {
localSearch.setSearchCompleteCallback(null,
function() {
if (localSearch.results[0])
{
var resultLat = localSearch.results[0].lat;
var resultLng = localSearch.results[0].lng;
var point = new GLatLng(resultLat,resultLng);
callbackFunction(point);
}else{
alert("Postcode not found!");
}
});
localSearch.execute(postcode + ", UK");
}
Looks like you can swap the showPointLatLng call with:
document.write( point.lat() + "," + point.lng() );
As lat/long come from calls to methods of the existing point object.
If you are asking how to make that work...
function showPointLatLng(point) {
document.write("Latitude: " + point.lat() + "\nLongitude: " + point.lng());
}
// Eg, ...
showPointLatLng({
lat : function(){ return 98; }
lng : function(){ return 42; /*the meaning of life!*/ }
});
Instead of document.write you could do something like this:
var info = document.createElement('div');
info.innerHTML = point.lat() + "," + point.lng();
document.body.appendChild(info);

Categories