JavaScript nested loops make function execute too slowly - javascript

I have a function that includes some nested for loops and works as intended.
The issue however, is that it has to loop through a lot of data, meaning that it takes too long to execute and often freezes the browser while it executes.
Data got from server is in the order of 2000+ elements.
Can anyone suggest a more efficient way of writing this function that will reduce the number of iterations and speed up the process?
function markers(boxes) {
$.getJSON('empdata.json', function (data) {
var json = data;
for (var i = 0; i < boxes.length; i++) {
for(var v = 0; v < json.length; v++) {
//loop through parsed markers within bounds
var obj = json[v];
if (boxes[i].contains(new google.maps.LatLng(obj.latitude, obj.longitude))) {
var marker = new google.maps.Marker({
position: new google.maps.LatLng(obj.latitude, obj.longitude),
map: map,
});
} else {
console.log("out of box");
}
} //close bounds markers
} //close full loop parse
})
};
The first part of the function simply gets data about map locations from a static JSON file using the jQuery $.GetJSON function.
The second part loops through boundary boxes that have been created depending on the parameters given. Finally the third part loops through each item in the JSON file and creates a map pin if it is inside the given boundaries.
I think the problem is that there can many boxes to loop through and this multiplied by the 2000+ items in the JSON file means that function has to go through thousands of iterations each time.

The bottleneck in your code is not the nested loops itself, but rather it's the google marker print that is very very slow.
However you can do some things like:
avoid the REcreation of temporary variables like var obj = json[v]; because it creates a lot of overhead. It is better to reassign that variable. (This principle is also at the base of fast js gaming libraries like Phaser.io. Same situation for:
for (var i = 0 ...
for(var v = 0 ...
var marker = new google.maps.Marker({ ...
avoid recall of new google.maps.LatLng(obj.latitude, obj.longitude) the creation of new objects is more expensive than assignation.
you can replace the creation of markers with fusion tables
make markers as thinner as possible
Use DOM DocumentFragments (if you see here the print is almost instantaneous)
do not console log because it is REALLY slow! Better attach text to DOM div acting as on-page log.
go async! try delegate the print of a single marker to an async callback in order to avoid the time spent waiting the end of the marker creation.
what is the complexity of boxes[i].contains()? Look that it is in the inner loop, so it is run N * M times, where N is the length of boxes and M the length of json. In computation theory, the more is optimized this function, the faster the whole snipped runs.
EDIT:
Found an interesting quick script that benchmarks sync 2K marker generation vs async one. On my pc elapsed time dropped of about 22 times when going async.
The core function is:
var createmarker=function(coordinates,index) {
setTimeout(function() {
var neighborMarker = new google.maps.Marker({
position: coordinates,
map: map,
title:'Marker '+index,
icon: 'https://cloud.githubusercontent.com/assets/238439/4837489/46de6daa-5fd7-11e4-9622-0e1cc674f6b2.png'
});
markersArray.push(neighborMarker );
},10);
};
clearMarkers('Asynchronous',function(coordsArray,timeini) {
for(var index=0;index<coordsArray.length;index++) {
createmarker(coordsArray[index], index);
}
});

hi im not sure if this will be of any help but how im understanding it your function has to go trough a big data base and because of that its slow.
so would it be a good solution to divide your database so add a general parameter that can divide your gigantic database into 4-8 smaller ones and make sure or that it will go trough them one by one or just immediately to the one it has to go to
like i said dunno if its gonna be of any help

Related

Leaflet array of markers that need to move not recreate

Firstly - I am a novice who has picked away at getting something functional for me over the past few years. My code below basically works - it's just complex and I believe there must be a better approach. Looking for a kick in different direction.
I am using javascript and leaflet to display an array of moving markers (players). The data on the markers comes to me in a table - listing an ID for each marker, a location and other information not needed for this purpose. The table of data will have a changing number of markers (ie: just one (MYID), or that plus one other, two or even more 'others'), and it will have multiple entries for the each marker (same ID and location, just the other data is different).
One of the markers ids is the same as "MYID" which I then treat differently, but for the others (if any) I have a function that puts them on my map. And each time the function is called it should move them to their new location data, but I currently don't know how to do that elegantly. So I am presently looking to delete all and recreate them each time the function is called (triggered by each time anything changes). Not so good coding.
function updatemap(displaytable,MYID) { // Update other player locations on map
OPN=[]; //reset OPN array
OPX=[]; //reset OPX array
OPY=[]; //reset OPY array
for (var r=0;r<OPLoc.length;r++){
// Need to remove all OPloc off map first and then re-create them?
};
OPLoc=[];
var q=0;
for (var p=0; p<displaytable.length; p++){ // for every line of the displaytable
if ((!OPN.includes(displaytable[p].id)) && (displaytable[p].id != MYID)){ // ... create a unique other player entry, once only
OPN.push(displaytable[p].id);
OPX.push(displaytable[p].lat);
OPY.push(displaytable[p].lon);
OPLoc[q] = new L.marker([displaytable[p].lat,displaytable[p].lon], {icon: oplayericon})
.addTo(mymap)
.bindPopup(displaytable[p].id + "<br>" + displaytable[p].lat + "," + displaytable[p].lon);
q++;
};
};
//...other code not relevant
};
None of the variable/arrays in the function are needed anywhere else for anything - I've just created all this mess to do this one thing. And the arrays are created globally so that they are available next time it's called.
I assume there are functions/capabilities of the array commands that I am just not aware of. Can anyone suggest a better way? please?
well, still not sure it's elegant - but it's functional. Not sure if there are edge cases that I'm not covering - but this appears to work and do what I need. I only use two arrays now (one for the markers and one for the ids so I can keep track of what's active). I tried making a 2D array but that made me sad, so it's two arrays now.
for (var s=0;s<displaytable.length;s++) { // display all other players
if (OPid.includes(displaytable[s].id)) { //if the player is already on the map, just update location
OPLoc[OPid.indexOf(displaytable[s].id)].setLatLng([displaytable[s].lat,displaytable[s].lon]);
} else { // if the player is not MYID and not already on the map, create it
if (displaytable[s].id != MYID) {
OPid.push(displaytable[s].id);
OPLoc[OPid.indexOf(displaytable[s].id)] = new L.marker([displaytable[s].lat,displaytable[s].lon], {icon: oplayericon})
.addTo(mymap)
.bindPopup(displaytable[s].id);
};
};
};
for (var r=0;r<OPid.length;r++) { //check if the OPlayer is still active
var found = false;
for(var q=0;q<displaytable.length;q++) {
if (displaytable[q].id == OPid[r]) {
found = true;
};
};
if (!found) { //if the OPlayer id is no longer in the displaytable
mymap.removeLayer(OPLoc[r]); //remove it from the map
OPLoc.splice(r,1); //remove it from the OPLoc array
OPid.splice(r,1); //remove it form the OPid array
};
};
```

In Bing Maps v8 can I get a pushpin object from a layer in a loop?

In Bing Maps v7 I was able to add pushpins to an entityCollection and then loop through that collection later in the code to set options or whatever. Now, I am having trouble getting pins from the v8 layers.
Here is what I used to do in v7 after I had already added the pin to the entityCollection:
for (var i = 0; i < entityCollection.getLength() ; i++) {
var pin = entityCollection.get(i);
pin.setOptions({ visible: true });
}
I have changed the object entityCollection to a layer for v8 and I am also looping through the layer while i < entityCollection.data.length
Now, in Bing Maps v8, I'm having trouble getting the pin object from the layer that I have already added it to. The code above throws an error on the setOptions line and I have also tried getting the pin with:
entityCollection.data[i]
instead of
entityCollection.get(i)
But that doesn't work either. I'm afraid my question is too generic, because I can't find anything that actually answers my question. I have a work around, but that causes failures later when I want to hide all the pins with certain attributes. Thanks in advance!
Bing Maps v8 has done away with the entityCollection - though they say it's still sort of supported, you obviously don't want to be using deprecated things any more.
Anywhere you have an entityCollection, replace it with a Layer (Microsoft.Maps.Layer). Layers expose the getPrimitives() method which will provide you with an array of the contents.
var map = new Microsoft.Maps.Map(..., ...);
var layer = new Microsoft.Maps.Layer();
// Add layer data...
layer.add(new Microsoft.Maps.Pushpin(...));
// Add layer to map
map.layers.insert(layer);
// Then you can iterate
var layerItems = layer.getPrimitives();
var len = layerItems.length;
for(var i = 0; i < len; i++){
var pin = layerItems[i];
// Do something with your pin
pin.setOptions({visible: false});
}
Note that if you're doing mass updates to the entire contents of the layer, such as showing or hiding every pin in the layer, you can do that directly on the layer. This will save you (the browser) a chunk of work setting each pin individually.
layer.setVisible(true);
Yes, in V8 layers have a getPrimitives function which returns an array containing all the shapes. You can then loop through these shapes like you would any other array.

Best way to share out many points on a few features?

I have 5000+ LatLng points, and for each of them I would like to find out which feature (region) they belong to. The features come from a kmz layer by Philippe Ivaldi, converted to GeoJSON.
Currently, I am doing this with turfjs in a double for loop. As expected, the calculation freezes the browser for ten minutes, which ain't very convenient.
Here's my code :
function countCeaByLayer(geoJsonLayer){
jQuery.getJSON('http://localhost/server/retrieveData.php', function(data){
var turfPoints = [];
for(var i = 0; i < data.length; i++){
turfPoints.push(turf.point([data[i].longitudeWGS84, data[i].latitudeWGS84]));
}
var features = geoJsonLayer.toGeoJSON().features;
for(var i = 0; i < features.length; i++){
var turfPointsNew = [];
for(var j = 0; j < turfPoints.length; j++){
var isInside = turf.inside(turfPoints[j], features[i]);
if(!isInside) turfPointsNew.push(turfPoints[j]);
}
turfPoints = turfPointsNew;
}
console.log("done");
});
}
What can I do to avoid freezing the browser?
Make it async?
Do the calculation with node and turfjs on a server?
Or deploy leafletjs on a server with node and leaflet-headless?
...or should I just deal with it?
Thanks!
To optimize your code, you should do something like this.
Loop over the points.
For each point, when you iterate over polygons to know if the point is inside one of them, first get the polygon Bounds and see if the point is within the bounds.
If not, you can skip going further and go to the next polygons.
If it's within the bounds, go for a plain check if it is inside the polygon itself.
If it's the case, break the loop iterating over polygons and switch to the next point.
For example, it could be:
points.forEach(function(point) {
polygons.some(function(polygon) {
if (polygon.getBounds().contains(point)) { // or other method if you are not playing with Leaflet features
if (turf.isInside(polygon, point) { // for example, not sure this method actually exists but you get the concept
// point is within the polygon, do tuff
return true; // break the some loop
}
}
});
});
I've myself developped something that exactly does the same thing also based on turf, I run it on the client side (and my loops are made with .some, not classical for loop, so it could even go further in terms of performance) and I never experienced freeze.
From my point of view, 5000 points is peanut for browser to handle, but if your polygons are really complex (dozen of hundreds of thousands of vertices), this can slow down the process of course.
Br,
Vincent
If Stranded Kid's answer is overkill for you,
geoJsonLayer.eachLayer(function(layer){
var within = turf.within(turf.featureCollection(turfPoints),turf.featureCollection([layer.toGeoJSON()]));
console.dir(within);
});
And make sure your coordinates are floats and not strings, because that's what caused the slowdown for me.

need to wait for MarkerWithLabel to finish populating the map

I'm using MarkerWithLabel (excellent little tool by the way) to mark several dozen locations on a map - in this case ~300.
So I loop through the elements of my data and on each iteration of the loop, create a label like so
int i = 1;
for(b in data){
var figureLabel = document.createElement("FIGURE");
var pictureLabel = document.createElement("img");
pictureLabel.src = "Assets/my_icon.png";
var caption = document.createElement("FIGCAPTION");
var text = document.createTextNode(i);
caption.appendChild(text);
figureLabel.appendChild(pictureLabel);
figureLabel.appendChild(caption);
markers[i] = new MarkerWithLabel({
position : new google.maps.LatLng(data[b].latitude, data[b].longitude),
map : map,
labelContent : figureLabel,
labelClass : "labels",
labelAnchor : new google.maps.Point(33.5, 56)
});
i++;
}
console.log('Map is populated!');
I'm here writing a question though, so let's not get too excited, there's a problem. Once all of these guys are loaded onto the map, I need to bind a click event to each of these markers. Let's say it will look like this:
$('figure').click(function(){
console.log($(this).find('figcaption').text());
});
It should be as simple as allowing the for:in loop to complete, but there's a pretty obvious lag between the completion of the loop and the placement of the labels. The log 'Map is populated!` happens several hundred milliseconds before the markers appear, and indeed if I bind my events right after the loop is complete, it will fail to find anything to bind to, and I don't get the desired functionality.
So what I've tried is to attach a listener to my map object like so:
google.maps.event.addListenerOnce(map, 'tilesloaded', function() {
//Bind some click events
});
And this actually works sometimes but is completely unreliable. I've also tried other listeners like boundschanged and idle but these are also unreliable.
So I need some way to identify the point in my control flow where the markers and their respective DOM elements are actually appended to the map, not simply when the loop which contains their instantiation completes.
You should try attaching the listener directly to the marker object with google.maps.event.addListener(). See here for a direct example (and more) on both adding and removing listeners to markers.
To not rely on links alone, the relevant example from there including both addition and (one method for the) removal of listeners is
var listener1 = google.maps.event.addListener(marker, 'click', aFunction);
google.maps.event.removeListener(listener1);
Of course, you were already using this function in your example code, just not applied to the markers themselves.
The MarkerWithLabel docs also contain lots of examples, including ones dealing with events, that you may have missed.

Javascript animation with recursion, strange behavior

Im trying to do code in javascript a ruzzle solver. For now it just dig through the maze and find every possible path ( in the future I will match them against a dictionary to find the real valid words in it)
You can see it here : http://178.239.177.105/ruzzle/
I wanted to do it with an animation that show how the algorithm works on it, but im issuing a problem.
If you load it, the page just dont show anything, and my browser crash after a while.
BUT...
if you set an alert("") function, somewhere in the middle of the recursion function, you would be able to go through any step in the algorithm.
Especially if you set the browser to prevent to show any further alert messages, you'll finally see the animation working on the maze.
I was actually trying to do this via setInterval(), but is not working.
So I have two questions:
- Why do the script cause the page to crash, or not if there's an alert?
- How can I properly show the animation using some kind on wait() mechanism?
Thanks
You can see all the code by going on the page and look at the source code, however for the sake of clarity I'll paste the relevant code here:
You can also play with the code here : http://jsfiddle.net/Gcw2U/
(you will have to uncomment the last line in the to make it run)
//this matrix of chars rapresent the 4x4 puzzle
var ruzle_model = [["w","a","l","k"],["m","o","o","n"],["h","a","t","e"],["r","o","p","e"]];
// ""offsets" rapresent the four motion vector(up,down,left,right)
// used to visit the matrix
var offsets = [[1,0],[0,1],[-1,0],[0,-1]];
//recursive function to dig the maze
function path(m,i,j,paths,checkeds){
alert("SET BROWSER TO AVOID NEXT ALERTS MSGs!");
//base case, if not hitting a wall or already checked cell
if ( ! (i<=3 && i>=0 && j>=0 && j<=3) || isChecked(checkeds,i,j)){
terminal.innerHTML = terminal.innerHTML + "-"+ paths;
uncheckAllCells();
return paths;
}
//call path for every direction (up,down,left,right) stored in offsets
var tmp = [];
for (var c=0; c<offsets.length;++c){
var offset = offsets[c];
checkCells(i,j);
checkeds.push(new Array(i,j));
tmp.push(path(m,i+offset[0],j+offset[1],paths + m[i][j],copy(checkeds)));
}
return tmp;
}
//call path on every cell in the maze
function ruzzle(r){
var sol = []
for(var i=0; i<4; ++i){
for(var j=0; j<4; ++j){
var checkeds = new Array();
sol.push(path(r,i,j,'',checkeds));
}
}
terminal.innerHTML = sol;
return sol;
}
Javascript loops and recursions inhibit rendering of the page, so any changes made will stay invisible until the script stops executing, like when you spawn an alert. When a user sets "do not show alert messages", the alert still yields execution time to the underlying eventloop, which will update the page.
For as-fast-as-possible (high fps) animations, use requestAnimationFrame().
In your case, setTimeout() is the best way to go. Set a timeout on the recursive call to path.
function recursive(args) {
// do stuff to args
setTimeout(function () {
recursive(args);
}, 5);
}
Example

Categories

Resources