Leaflet array of markers that need to move not recreate - javascript

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
};
};
```

Related

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.

JavaScript nested loops make function execute too slowly

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

Arrays Inside Objects: Conditional Updating Of One Object's Array From Another Object's Array

DISCLAIMER
I have absolutely no idea how to succinctly describe the nature of the problem I am trying to solve without going deep into context. It took me forever to even think of an appropriate title. For this reason I've found it nearly impossible to find an answer both on here and the web at large that will assist me. It's possible my question can be distilled down into something simple which does already have an answer on here. If this is the case I apologise for the elaborate duplicate
TL;DR
I have two arrays: a main array members and a destination array neighbours (technically many destination arrays but this is the tl;dr). The main array is a property of my custom group object which is auto-populated with custom ball objects. The destination array is a property of my custom ball object. I need to scan each element inside of the members array and calculate distance between that element and every other element in the members group. If there exist other elements within a set distance of the current element then these other elements need to be copied into the current element's destination array. This detection needs to happen in realtime. When two elements become close enough to be neighbours they are added to their respective neighbours array. The moment they become too far apart to be considered neighbours they need to be removed from their respective neighbours array.
CONTEXT
My question is primarily regarding array iteration, comparison and manipulation but to understand my exact dilemma I need to provide some context. My contextual code snippets have been made as brief as possible. I am using the Phaser library for my project, but my question is not Phaser-dependent.
I have made my own object called Ball. The object code is:
Ball = function Ball(x, y, r, id) {
this.position = new Vector(x, y); //pseudocode Phaser replacement
this.size = r;
this.id = id;
this.PERCEPTION = 100;
this.neighbours = []; //the destination array this question is about
}
All of my Ball objects (so far) reside in a group. I have created a BallGroup object to place them in. The relevant BallGroup code is:
BallGroup = function BallGroup(n) { //create n amount of Balls
this.members = []; //the main array I need to iterate over
/*fill the array with n amount of balls upon group creation*/
for (i = 0; i < n; i++) {
/*code for x, y, r, id generation not included for brevity*/
this.members.push(new Ball(_x, _y, _r, _i)
}
}
I can create a group of 4 Ball objects with the following:
group = new BallGroup(4);
This works well and with the Phaser code I haven't included I can click/drag/move each Ball. I also have some Phaser.utils.debug.text(...) code which displays the distance between each Ball in an easy to read 4x4 table (with duplicates of course as distance Ball0->Ball3 is the same as distance Ball3->Ball0). For the text overlay I calculate the distance with a nested for loop:
for (a = 0; a < group.members.length; a++) {
for (b = 0; b < group.members.length; b++) {
distance = Math.floor(Math.sqrt(Math.pow(Math.abs(group.members[a].x - group.members[b].x), 2) + Math.pow(Math.abs(group.members[a].y - group.members[b].y), 2)));
//Phaser text code
}
}
Now to the core of my problem. Each Ball has a range of detection PERCEPTION = 100. I need to iterate over every group.members element and calculate the distance between that element (group.members[a]) and every other element within the group.members array (this calculation I can do). The problem I have is I cannot then copy those elements whose distance to group.members[a] is < PERCEPTION into the group.members[a].neighbours array.
The reason I have my main array (BallGroup.members) inside one object and my destination array inside a different object (Ball.neighbours) is because I need each Ball within a BallGroup to be aware of it's own neighbours without caring for what the neighbours are for every other Ball within the BallGroup. However, I believe that the fact these two arrays (main and destination) are within different objects is why I am having so much difficulty.
But there is a catch. This detection needs to happen in realtime and when two Balls are no longer within the PERCEPTION range they must then be removed from their respective neighbours array.
EXAMPLE
group.members[0] -> no neighbours
group.members[1] -> in range of [2] and [3]
group.members[2] -> in range of [1] only
group.members[3] -> in range of [1] only
//I would then expect group.members[1].neighbours to be an array with two entries,
//and both group.members[2].neighbours and group.members[3].neighbours to each
//have the one entry. group.members[0].neighbours would be empty
I drag group.members[2] and group.members[3] away to a corner by themselves
group.members[0] -> no neighbours
group.members[1] -> no neighbours
group.members[2] -> in range of [3] only
group.members[3] -> in range of [2] only
//I would then expect group.members[2].neighbours and group.members[3].neighbours
//to be arrays with one entry. group.members[1] would change to have zero entries
WHAT I'VE TRIED
I've tried enough things to confuse any person, which is why I'm coming here for help. I first tried complex nested for loops and if/else statements. This resulted in neighbours being infinitely added and started to become too complex for me to keep track of.
I looked into Array.forEach and Array.filter. I couldn't figure out if forEach could be used for what I needed and I got very excited learning about what filter does (return an array of elements that match a condition). When using Array.filter it either gives the Ball object zero neighbours or includes every other Ball as a neighbour regardless of distance (I can't figure out why it does what it does, but it definitely isn't what I need it to do). At the time of writing this question my current code for detecting neighbours is this:
BallGroup = function BallGroup(n) {
this.members = []; //the main array I need to iterate over
//other BallGroup code here
this.step = function step() { //this function will run once per frame
for (a = 0; a < this.members.length; a++) { //members[a] to be current element
for (b = 0; b < this.members.length; b++) { //members[b] to be all other elements
if (a != b) { //make sure the same element isn't being compared against itself
var distance = Math.sqrt(Math.pow(Math.abs(this.members[a].x - this.members[b].x), 2) + Math.pow(Math.abs(this.members[a].y - this.members[b].y), 2));
function getNeighbour(element, index, array) {
if (distance < element.PERCEPTION) {
return true;
}
}
this.members[a].neighbours = this.members.filter(getNeighbour);
}
}
}
}
}
I hope my problem makes sense and is explained well enough. I know exactly what I need to do in the context of my own project, but putting that into words for others to understand who have no idea about my project has been a challenge. I'm learning Javascript as I go and have been doing great so far, but this particular situation has me utterly lost. I'm in too deep, but I don't want to give up - I want to learn!
Many, many, many thanks for those who took the time read my very long post and tried provide some insight.
edit: changed a > to a <
I was learning more on object literals, I'm trying to learn JS to ween myself off of my jQuery dependency. I'm making a simple library and I made a function that adds properties of one object to another object. It's untested, but I think if you were apply something similar it might help. I'll try to find my resources. Btw, I don't have the articles on hand right now, but I recall that using new could incur complications, sorry I can't go any further than that, I'll post more info as I find it.
xObject could be the ball group
Obj2 could be the members
Obj1 could be the destination
/* augment(Obj1, Obj2) | Adds properties of Obj2 to Obj1. */
// xObject has augment() as a method called aug
var xObject = {
aug: augument
}
/* Immediately-Invoked Function Expression (IIFE) */
(function() {
var Obj1 = {},
Obj2 = {
bool: true,
num: 3,
str: "text"
}
xObject.aug(Obj1, Obj2);
}()); // invoke immediately
function augment(Obj1, Obj2) {
var prop;
for (prop in Obj2) {
if (Obj2.hasOwnProperty(prop) && !Obj1[prop]) {
Obj1[prop] = Obj2[prop];
}
}
}

Grouping shapes in paper.js

Trying out paper.js for the first time, working on some generative visuals. Trying to figure out the best route to accomplish the following:
Each cloud shape is individual, but when they intersect each other, I want them to compound into one larger cloud.
Within that larger cloud, I want the individual shape to retain it's properties, so that it can eventually detach and become a single cloud again.
So I am running into a few problems trying to accomplish this. I check for intersections:
//cloudArray refers to an array of path items
Cloud.prototype.checkIntersection = function() {
//loop through all existing cloud shapes
for(var i=0;i<cloudArray.length;i++){
//avoid checking for intersections on the same cloud path
if(this.path !== cloudArray[i].path){
//if path intersects another, group the two, and
//sort them in the order of their id
if(this.path.intersects(cloudArray[i].path)){
tmpGrp = [this.path,cloudArray[i].path];
tmpGrp.sort(function(a,b){return a.id - b.id});
groupClouds(tmpGrp);
}
}
}
}
Now after intersections are checked, I attempt to group the clouds together into arrays:
function groupClouds(tmpGrp){
if(grps.length > 0){
for(var i=0;i<grps.length;i++){
if(tmpGrp !== grps[i]){
console.log('doesnt match');
grps.push(tmpGrp);
}else{
console.log('matches');
}
}
}else{
grps[0] = tmpGrp;
}
console.log(grps);
}
Now I know that I can't compare arrays this way, so I have tried to use the solution given here, but I didn't want to further clutter this question.
Is this method reasonable? I know that I could create a new compoundPath if I could create arrays for each group. The problem is assuring that each collection of intersecting clouds is correct, and that they are being updated efficiently.
Any advice?

Logic behind CAD apps multi-selection and alignment functionality

I am trying to build a small CAD app using HTML5 Canvas and Paper.js
Now I am stuck at a problem I'd hate asking for help for since I believe I should solve this on my own, but everytime I try to think about a solution my head hurts.
I am trying to build the following functions:
Align-left
Align-right
Align-center.
The user is supposed to select a bunch of elements on the canvas and click one of the above functions to align them together.
The whole concept is that the user selects a Reference Item in a way. The elements are aligned relative-to the Reference Item. This ensures that the alignments happen in a user predictable way.
There are 2 ways for the user to select elements
Selection-via-Intersection
The user clicks and drags a selection rectangle. While dragging I
can track in an array which items intersected with the selection
rectangle. I can track the first intersected item here just fine, so I
have a Reference Item .Works perfectly so far.
Selection-via-Click+Shift
The user holds Shift and clicks on elements to do a multi-selection.
It's a functionality I'm sure everyone has seen in any modern graphics
editor. I have yet to figure out a way how to track a first-selected
item here, since items can be set as first-selected and then
deselected.
The problem:
The above selection methods can be COMBINED together -
The user can select-via-intersection and then start select/de-select items using Shift+Click.
Which one from the selectedItems is the Reference-Item now?
An example:
Item 2 was the first-selection on selection-via-intersection. Item 2
was de-selected using Shift+Click. Which one is the Reference-Item
now?.
Here is a GIF that illustrates what I am talking about (although it's invinsible in the GIF the items get selected as soon as the intersection rectangle touches them.)
I am aware that the whole thing will involve some array logic but I cannot seem to grasp how to implement the whole thing.
Notes:
Apart from the complexity of this thing, I have performance constraints as well. Intersection Detection which is used for the Selection-via-Intersection is already a heavy operation and it get's fired continously while the user drags a selection rectangle.
This is the code for Select-Via-Intersection
The following function getSortedIntersects get's called continuously while the user drags a selection rectangle. I log in the console the first selected item:
var checkedSegments = new Array();
function getSortedIntersects(rect) {
//children are all the items drawn on the canvas
var children = project.activeLayer.children;
//I clone the children array into another array and I exclude all items other than Paths
//Intersection Detection works only on paths.
var pathsOnCanvas = children.filter(function (el) {
return el instanceof Path
});
var newCheckedSegments = new Array();
var firstSelection = "";
//Start looping over all Paths on the canvas
for (var i = 0; i < pathsOnCanvas.length; i++) {
//create an array which lists all the intersections. The array is called ''intersections''
var intersections = pathsOnCanvas[i].getIntersections(rect);
//If there is at least 1 intersection OR the rect contains a path then proceed
if (intersections.length > 0 || rect.contains(pathsOnCanvas[i].bounds)) {
//Checks if the selected item at index i is in the newSegments array. If so, it continues iterating the loop without going further at this iteration.
if (newCheckedSegments.indexOf(pathsOnCanvas[i]) >= 0)
continue;
pathsOnCanvas[i].selected = true;
newCheckedSegments.push(pathsOnCanvas[i]);
}
}
for (var i = 0; i < newCheckedSegments.length; i++) {
if (checkedSegments.indexOf(newCheckedSegments[i]) < 0)
checkedSegments.push(newCheckedSegments[i]);
firstSelection = (checkedSegments[0].id);
console.log("first selection is " + firstSelection);
}
for (var i = checkedSegments.length - 1; i >= 0; i--) {
if (newCheckedSegments.indexOf(checkedSegments[i]) < 0) {
checkedSegments[i].selected = false;
checkedSegments.splice(i, 1);
}
}
}

Categories

Resources