First, to get some context, I asked this a few weeks ago.
I now have Leaflet parsing a folder of gpx files, and drawing all of them in one map instance, using the Omnivore plugin.
Now what I need is for those files to change dynamically. I have a slider that represents a time interval. Each time I use the slider, I narrow the interval in which I want the tracks to be shown.
Ex: My slider goes from 15th of January to the 15th of May. So, the map shows all tracks from that time. If I change the slider to show from the 1st of April to the 15th of May, the map should react accordingly and re-draw the corresponding files, erasing from the map the tracks from before the 1st of April.
The problem is that I can't seem to get it doing this, since I keep getting a
TypeError: Cannot read property 'addLayer' of undefined
When I make a console.log of the layer, in the line where the problem happens, it prints at exactly the same time, the layer and then an undefined. I don't know why this happens and it is very hard do debug.
My code is as follows:
setTimeout(function() {
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
osmAttrib = '© OpenStreetMap contributors',
osm = L.tileLayer(osmUrl, {maxZoom: 18, attributionControl: false});
var ggl = new L.Google();
angular.element($elem[0]).append(angular.element('<div id="trackmap'+ trackmapCount +'" style="width: 100%; height: calc(100% - 25px); border: 1px solid #ccc"></div>'));
trackmaps[trackmapCount] = new L.Map('trackmap'+ trackmapCount +'', {center: new L.LatLng(center[0], center[1]), zoom: 10});
trackmaps[trackmapCount].addControl(new L.Control.Layers( {'Google':ggl,'OSM':osm}, {}));
console.log('map'+ trackmapCount +'');
var layer1 = osm.addTo(trackmaps[trackmapCount]);
createTracks(jsonRes);
$scope.$watch(function () {
return $elem[0].parentNode.clientWidth;
}, function ( w ) {
if ( !w ) { return; }
for(var i = 0; i < trackmapCount; i++) {
trackmaps[i].invalidateSize();
}
});
$scope.$watch(function () {
return $elem[0].parentNode.clientHeight;
}, function ( h ) {
if ( !h ) { return; }
for(var i = 0; i < trackmapCount; i++) {
trackmaps[i].invalidateSize();
}
});
$scope.$on('$destroy', function() {
rootScopeBroadcast();
});
var rootScopeBroadcast = $rootScope.$on('rootScope:broadcast-timeline_slider', function (event, data) {
for(var i = 0; i < trackmapCount; i++) {
trackmaps[i].removeLayer(runLayer);
}
var new_tracks = sliderProcessing(data.min_time, data.max_time, jsonRes)
createTracks(new_tracks); // THE PROBLEM IS HERE
});
function createTracks (track_list) {
myStyle = {
"color": "#0033ff",
"weight": 5,
"opacity": 0.65,
"clickable": true
};
customLayer = L.geoJson(null, {
style: myStyle,
});
for (var i = 0; i < track_list.length; i += 1) {
runLayer = omnivore.gpx(folderPath + track_list[i], null, customLayer)
.on('ready', function() {
//runLayer.showExtremities('arrowM');
})
.addTo(trackmaps[trackmapCount])
//a console.log of trackmaps[trackmapCount] here shows both the layer and an undefined at the same time
.on('click', function(d) {
console.log(d);
});
}
}
trackmapCount++;
delay = 0;
}, delay);
I'm probably missing some Leaflet re-draw function, or my control code is not correctly placed, but I've messed around with it and it stays the same.
Related
guys
I been trying to get my markers latlon when user double click on it but still don't get any results. Been trying other methods but i think this is the most accurate since i dont get any error when executing js
Any recommendation pls
var places = [
["LOCATION_1", 8.9856146341374, -79.51102268985925],
["LOCATION_2", 8.984640842221594, -79.51383510471848],
["LOCATION_3", 8.972080043026754, -79.5529245611453],
["LOCATION_4", 9.052896045979661, -79.4515923525883],
["LOCATION_5", 9.053366385577624, -79.50832832626823]
];
var map = L.map('map', {
center: [9.352867999999996, -79.689331],//[35.791188, -78.636755],
zoom: 9,
layers:L.tileLayer('http://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}',{
maxZoom: 20,
subdomains:['mt0','mt1','mt2','mt3']
})
});
for (var i = 0; i < places.length; i++) {
marker = new L.marker([places[i][1], places[i][2]])
.bindPopup(places[i][0])
.addTo(map);
}
function getdest(){
L.marker.on('dblclick',function(e){
var latlng_dest=e.latlng() });
console.log(latlng_dest)
return latlng_dest
}
navigator.geolocation.getCurrentPosition(function(location) {
var latlng_orig = new L.LatLng(location.coords.latitude, location.coords.longitude);
L.Routing.control({
waypoints: [
//L.latLng(9.10607301250145, -79.34754531445351),
L.latLng(latlng_orig)
//,L.latLng(latlng_dest)
//,L.latLng(9.100769244670843, -79.35099352767948)
,L.latLng(getdest())
]
}).addTo(map)
});
You have many common things wrong:
e.latlng() is not a function it is a property e.latlng
L.marker.on('dblclick',function(e){ this makes no sense. You creating a new instance of a Marker without coords and then adding a listener to it.
You can't return a value in a function from a listener. The listener is not called at the moment you return the value L.marker.on('dblclick',function(e){ var latlng_dest=e.latlng() }); return latlng_dest
Your code should look like that:
for (var i = 0; i < places.length; i++) {
marker = new L.marker([places[i][1], places[i][2]])
.bindPopup(places[i][0])
.addTo(map)
.on('dblclick', function(e) {
waypoints.push(e.latlng);
routeControl.setWaypoints(waypoints);
});
}
var routeControl = L.Routing.control({
waypoints: [],
}).addTo(map);
var waypoints = [];
navigator.geolocation.getCurrentPosition(function(location) {
var latlng_orig = new L.LatLng(location.coords.latitude, location.coords.longitude);
waypoints.push(latlng_orig);
});
I'd like to have my icon change based on the zoom, which I have working! BUT, I also have a setTimeout which runs my function every 7 seconds.
The problem is that every time the function is run, the icon gets set back to the bigBusIcon.
I've tried putting both the marker and the zoomend in functions, but I've had no luck... Any help is much appreciated!
const mapBuses = function () {
//other code is here
L.geoJSON(getGeoJson(routeFilter), {
onEachFeature: function (feature) {
let longitude = feature.coordinates[0];
let latitude = feature.coordinates[1];
let marker = L.marker([latitude, longitude], { icon: bigBusIcon, rotationAngle: feature.bearing })
.bindPopup(
`Bus Route: ${feature.routeNum}<br/>Speed: ${Math.round(feature.speed)} km/hr`
).addTo(busLayer);
// set icon size based on zoom (although it resets every 7 seconds)
map.on('zoomend', function () {
var currentZoom = map.getZoom();
if (currentZoom >= 14) {
marker.setIcon(bigBusIcon);
}
else {
marker.setIcon(smallBusIcon);
}
});
}
});
});
// refresh every 7 seconds
setTimeout(mapBuses, 7000);
};
You can check the current zoom while you are initializing your layer:
const mapBuses = function () {
//other code is here
L.geoJSON(getGeoJson(routeFilter), {
onEachFeature: function (feature) {
let longitude = feature.coordinates[0];
let latitude = feature.coordinates[1];
// get current zoom
let currentZoom = map.getZoom();
// add condition to chose icon
let myIcon = currentZoom > 14 ? bigBusIcon : smallBusIcon;
// use myIcon variable in marker creation
let marker = L.marker([latitude, longitude], { icon: myIcon, rotationAngle: feature.bearing })
.bindPopup(
`Bus Route: ${feature.routeNum}<br/>Speed: ${Math.round(feature.speed)} km/hr`
).addTo(busLayer);
// set icon size based on zoom (although it resets every 7 seconds)
map.on('zoomend', function () {
var currentZoom = map.getZoom();
if (currentZoom >= 14) {
marker.setIcon(bigBusIcon);
}
else {
marker.setIcon(smallBusIcon);
}
});
}
});
});
// refresh every 7 seconds
setTimeout(mapBuses, 7000);
};
I'm trying to display markers on a map using Leaflet. On clicking each marker, I'm trying to display a variable inside a popup but I am not able to do so. However, if the same thing is done inside the alert method, the variable gets displayed. What am I doing wrong?
var map = L.map('map').setView([51.505, -0.09], 12);
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18
}).addTo(map);
var latlngs = [[51.49,-0.11],[51.51,-0.13],[51.505,-0.09],[51.507,-0.08], [51.509,-0.07]];
var speed = [1,2,3,4,5]
var time = [12,14,15,16]
var test = 1
customMarker = L.CircleMarker.extend({
options: {
}
});
for (var i = 0, len = latlngs.length; i < len; i++) {
var m = new customMarker(latlngs[i], {
speed: speed[i],
time: time[i]
});
m.on('mouseover', function() {
//alert('Speed at this point' + this.options.speed)
this.bindPopup(this.options.speed).openPopup()
})
m.addTo(map);
}
var polyline = L.polyline(latlngs,{});
polyline.addTo(map);
bindPopup accepts multiple types for its content argument :
bindPopup(<String|HTMLElement|Function|Popup>content, <Popup options> options?)
You're passing an integer, not a string and that confuses Leaflet into looking for a DOM node named 1. Cast your value to a string and your problem disappears :
this.bindPopup(this.options.speed+"").openPopup()
And a demo
var map = L.map('map').setView([51.505, -0.09], 12);
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18
}).addTo(map);
var latlngs = [[51.49,-0.11],[51.51,-0.13],[51.505,-0.09],[51.507,-0.08], [51.509,-0.07]];
var speed = [1,2,3,4,5]
var time = [12,14,15,16]
var test = 1
customMarker = L.CircleMarker.extend({
options: {
}
});
for (var i = 0, len = latlngs.length; i < len; i++) {
var m = new customMarker(latlngs[i], {
speed: speed[i],
time: time[i]
});
m.on('mouseover', function() {
this.bindPopup(this.options.speed+"").openPopup()
})
m.addTo(map);
}
var polyline = L.polyline(latlngs,{});
polyline.addTo(map);
html, body {
height: 100%;
margin: 0;
}
#map {
width: 100%;
height: 100%;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.2.0/dist/leaflet.css" integrity="sha512-M2wvCLH6DSRazYeZRIm1JnYyh22purTM+FDB5CsyxtQJYeKq83arPe5wgbNmcFXGqiSH2XR8dT/fJISVA1r/zQ==" crossorigin=""/>
<script src="https://unpkg.com/leaflet#1.2.0/dist/leaflet.js" integrity="sha512-lInM/apFSqyy1o6s89K4iQUKg6ppXEgsVxT35HbzUupEVRh2Eu9Wdl4tHj7dZO0s1uvplcYGmt3498TtHq+log==" crossorigin=""></script>
<div id='map'></div>
Coming again with another question :)
This time I had a requirement to show some progress while Child rows are being loaded. Since there is an Api call which relatively takes little time to return data, I do want to show the some progress unless the user who clicks the parent row is totally unaware whether there is a call done to see its child rows.
What I have done:
I wrote a style sheet class which has a
loader-small.gif
image as this:
tr.loading td.details-control {
background: url('/Images/loader-small.gif') no-repeat center center;
}
and applied like this:
$('#accountManagerEarningsDataTable tbody').on('click', 'td.details-control', function () {
var tr = $(this).closest('tr');
var row = table.row(tr);
try {
if (row.child.isShown()) {
// This row is already open - close it
row.child.hide();
tr.removeClass('shown');
}
else {
//Calling the loading Class ------>
tr.addClass('loading');
// Open this row
var arrForTable1 = [];
var arrForTable2 = [];
totalBrokerage = 0;
totalRetailBrokerage = 0;
totalSelfServiceBrokerage = 0;
console.log('You selected: ' + row.data().AccountManagerID);
var settings = {
"columnDefs": [
{ targets: 1, align: "right", decimals: 0 },
{ targets: 2, align: "right", decimals: 0 },
{ targets: 3, align: "right", decimals: 0 },
{ targets: 4, align: "right", decimals: 2 },
{ targets: 5, align: "right", decimals: 2 }
]
};
//problems with asynchoronus call back
var response = organization_GetAccountManagerDetailEarningsAccountData(row.data(), purl2, pcontext);
if (response.success === "true") {
for (var i = 0; i < response.value.length; i++) {
for (var j = 0; j < response.value[i].Securities.length; j++) {
var itemRow2 = {};
itemRow2["Security ID"] = response.value[i].Securities[j].SecurityId;
itemRow2["Trades"] = response.value[i].Securities[j].Trades;
itemRow2["Buy Qty"] = response.value[i].Securities[j].BuyQuantity;
itemRow2["Sell Qty"] = response.value[i].Securities[j].SellQuantity;
itemRow2["Total Brkg"] = response.value[i].Securities[j].Effective_Brokerage;
itemRow2["Online Brkg"] = response.value[i].Securities[j].Online_Brokerage;
arrForTable2.push(itemRow2);
totalBrokerage = totalBrokerage + parseFloat(response.value[i].Securities[j].Effective_Brokerage);
totalSelfServiceBrokerage = totalSelfServiceBrokerage + parseFloat(response.value[i].Securities[j].Online_Brokerage);
}
totalBrokerage = Math.round(totalBrokerage * 100) / 100;
totalSelfServiceBrokerage = Math.round(totalSelfServiceBrokerage * 100) / 100;
totalRetailBrokerage = Math.round(totalRetailBrokerage * 100) / 100;
var itemRow1 = {};
itemRow1["Account ID"] = response.value[i].AccountId;
itemRow1["Account Name"] = response.value[i].AccountName;
itemRow1["..."] = '<div class="alert alert-info" role="alert">' + buildHtmlTable(arrForTable2, 'table2x' + j, settings) + '<p>Total Brokerage ' + numberWithCommas(totalBrokerage) + '</p></div>';
arrForTable1.push(itemRow1);
arrForTable2 = [];
totalBrokerage = 0;
totalRetailBrokerage = 0;
totalSelfServiceBrokerage = 0;
}
tr.removeClass('loading');
htmlTable1 = buildHtmlTable(arrForTable1, 'table1x' + i);
row.child(htmlTable1).show();
tr.addClass('shown');
}
else {
row.child('<table><tr><td>' + response.value[0].AccountId + '</td></tr></table>').show();
tr.addClass('shown');
};
}
} catch (e) {
console.log(e.message);
}
});
The Problem:
Firefox nicely shows the Progress image after the user clicks it, but Edge and Chrome does not show. Both browsers crossed this piece of code when I was debugging from developer tools of the respective browser.
Its browser compatible problem? Is there a solution for it? Help me please.
In case of chrome there is such an issue while showing the loading bar while making a server call. Please make the following changes where you are making the service call. First add the class loading to the table
tr.addClass('loading');
After that make the service call by giving a timeout function
setTimeout(function(){
var response = organization_GetAccountManagerDetailEarningsAccountData(row.data(), purl2, pcontext);
......
//Your service calls and response call backs
},1);
On providing a timeout (say 1ms), Chrome will get the time to bind the loading bar to DOM, In other case the DOM Object is not available to show the spinner.
I've got a map with a few MarkerWithLabel objects on it (http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerwithlabel/1.1.9/). The labels, in this case, are integers.
I also have a MarkerClustererPlus (http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.1.2/) which also works fine.
However, I want to change the text on Clusters to show the sum of those integers on labels for every MarkerWithLabel inside a Cluster.
I did that by binding this function to the end of clustering:
function calculateClusterLabels() {
$.each(markerCluster.clusters_, function(i, cluster){
var sum = 0;
var cluster_markers = cluster.getMarkers();
$.each(cluster_markers, function(j, marker) {
sum += marker.labelContent;
});
cluster.clusterIcon_.sums_['text'] = sum;
cluster.updateIcon(); // also tried cluster.repaint();
});
}
And that works - at least for the Cluster text. But now we are getting to the real problem: it freezes the whole Map. Raven.js catches this: Uncaught TypeError: undefined is not a function. But nothing clearer than this.
Any ideas?
EDIT:
Some more code. Data is fetched with ajax and then the markers are set in a loop:
$.each(us_data, function(k, v) {
var markerPosition = new google.maps.LatLng(us_data[k]['lat'], us_data[k]['lon']);
var marker = new MarkerWithLabel({
position: markerPosition,
draggable: false,
map: map,
labelContent: us_data[k]['count'],
labelAnchor: anchor,
labelClass: "marker-with-label"
});
markers.push(marker);
});
And then I make the Clusters and bind the event:
markerCluster = new MarkerClusterer(map, markers, {imagePath: 'https://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/images/m'});
google.maps.event.addListener(markerCluster, 'clusteringend', function() {
calculateClusterLabels();
});
This all happens inside the .done() of ajax, but markerCluster and markers are visible outside.
To answer myself: well, it was fairly simple after all:
function calculateClusterLabels() {
$.each(markerCluster.clusters_, function(i, cluster){
var sum = 0;
var cluster_markers = cluster.getMarkers();
$.each(cluster_markers, function(j, marker) {
sum += marker.labelContent;
});
if (cluster.clusterIcon_.sums_ != null) {
cluster.clusterIcon_.sums_['text'] = sum;
}
});
}
What I did - I added a simple if statement to check whether the .sums_ object is not null (because it wasn't null only on the visible clusters that had markers inside) and I omitted the .updateIcon call and everything works perfectly, no errors.
An even better solution, for my case, was to simply change the markerclusterer.js source file:
MarkerClusterer.CALCULATOR = function (markers, numStyles) {
var index = 0;
var title = "";
var count = 0;
if (typeof markers[0].labelContent != 'undefined') {
var sum = 0;
var i;
for (i = 0; i < markers.length; ++i) {
if (!isNaN(markers[i].labelContent) {
sum += markers[i].labelContent;
} else {
// whatever we need, perhaps we want to calculate it differently
}
}
count = sum.toString();
} else {
count = markers.length.toString();
}
var dv = count;
while (dv !== 0) {
dv = parseInt(dv / 10, 10);
index++;
}
index = Math.min(index, numStyles);
return {
text: count,
index: index,
title: title
};
};
This was even better because it updated Cluster styles, as well and it works both with regular Marker and MarkerWithLabel objects.