Leaflet - How to delete arcs from map? - javascript

So I have a map on which I draw trade routes(as arcs) between countries with arc.js. The arcs are different based on the year, and I basically want to be able to redraw the arcs based on what year I pick.
I create the arcs like so:
//for every trade route, draw a arc from start to end
var line_to_add;
for(var q = 0; q < dest_Coor.length; q++) {
//create the arc
var start = {x: from_Coor[0][0], y: from_Coor[0][1]};
var end = {x: dest_Coor[q][0], y: dest_Coor[q][1]};
var generator = new arc.GreatCircle(start,end);
var line = generator.Arc(100, {offset: 10});
//add it to map
line_to_add = L.geoJson(line.json(), {
color: "#004c00"
});
//add the arc to global array of arcs
allArrows.push(line_to_add);
line_to_add.addTo(map);
}
When a different year is picked, I then try to delete the current arcs through this method, with allArrows being a global array of all arc objects(according to this question):
function deleteOldArcs() {
for(var i = 0; i < allArrows.length; i++) {
map.removeLayer(allArrows[u]);
}
//then reset the list of arrows:
allArrows.length = 0;
}
However, this doesn't work and program execution just freezes inside map.removeLayer(allArrows[u]), and I don't understand why. I checked the list of map layers with map._layers, and the arc objects are in there... so why would calling map.removelayer not work?
Any help would be greatly appreciated, thanks!!

You've got several options:
Use Leaflet.Arc. It will create Leaflet layers directly, which might be easier to manage than the json blobs.
Store line_to_add somewhere in an array, and call its remove() method later. From what I see, you want to reference allArrows[u], but you never fill up the allArrows data structure.
Use a L.LayerGroup. Add the LayerGroup to the map, the arcs to the LayerGroup, then call its clearLayers() method.

Related

Tooltips for data in javascript using p5.js

I am trying to make tooltips for a data visualization I made using p5.js but I am completely lost. Nothing I tried works. This is my code as is.
var table;
var i;
var j;
var cellValue;
var label;
var test;
function preload() {
matrix = loadTable("dataLayer2matrix.csv","csv")
labels = loadTable("dataLayer2labels.csv","csv")
test = matrix
}
function setup() {
createCanvas(1500,1500)
noStroke()
fill(0,0,255,10)
angleMode(DEGREES)
background(255,255,255)
matrixStartX = 200
matrixStartY = 250
var matrixRows = matrix.getRows()
var matrixSize = matrixRows.length
// Experiment with grid
fill(75, 75, 75, 50)
for (r = 0; r <= matrixSize; r++) {
rect(matrixStartX , matrixStartY + r * 20 - 1 , 20 * matrixSize, 1)
rect(matrixStartX + r * 20 - 1 , matrixStartY, 1, 20 * matrixSize)
}
// Draw matrix
for (var mr = 0; mr < matrixSize; mr++) {
for (var mc = 0; mc < matrixSize; mc++) {
cellValue = matrixRows[mr].getNum(mc)
fill(49,130,189,cellValue*10)
rect(mc * 20 + matrixStartX, mr * 20 + matrixStartY, 19 ,19)
}
}
// Labels - horizontal
fill(75, 75, 75, 255)
labelsRow = labels.getRows()
for (mc = 0; mc < matrixSize; mc++) {
label = labelsRow[0].getString(mc)
text(label, 10, mc*20+matrixStartY + 15)
}
// Labels - vertical
push()
translate(matrixStartX + 15, matrixStartY - 15)
rotate(-90)
for (mc = 0; mc < matrixSize; mc++) {
label = labelsRow[0].getString(mc)
text(label, 0, mc*20)
}
pop()
//Tooltip when clicked
}
/* if(mouseIsPressed){
fill(50);
text(cellValue, 10,10,70,80);
}*/
}
}
It makes this image:
I want it so that when I go over a square I get the data in it. I really can't seem to do it. Thanks.
I think the advice telling you to use bootstrap is missing the fact that you're using p5.js. Bootstrap is more for dealing with html components, not internal Processing sketches.
Instead, you probably want to do this with p5.js code. The best thing you can do is break your problem down into smaller steps:
Step 1: Can you draw a single rectangle?
Instead of trying to add this new functionality to your existing sketch, it might be easier if you start with a simpler example sketch with just a single rectangle.
Step 2: Can you detect when the mouse is inside that rectangle?
If you know where you're drawing the rectangle, you know its coordinates. You also know the coordinates of the mouse from the mouseX and mouseY variables. So to detect whether the mouse is inside the rectangle, you simply have to use if statements that compare the coordinates of the mouse to the coordinates of the rectangle. There are a ton of resources on google for this, and it might help if you draw some examples out on a piece of paper.
Also, don't worry about the tooltip just yet. Just do something simple like change the color of the rectangle when the mouse is inside it.
Step 3: Can you display the information box?
Again, do this in its own sketch first. Maybe create a function that takes a position and the information you want to display as parameters and displays it in a rectangle. Don't worry about making it a tooltip yet. Just get it displaying. Use hard-coded values for the information.
Step 4: Can you combine your small example sketches?
You have code that triggers when the mouse is inside a rectangle. You have code that draws the tooltip. Can you make it so the tooltip is drawn when the mouse is inside the rectangle?
Step 5: Only when all of the above works, then you should start thinking about adding it to your full sketch.
Instead of using an example rectangle, you'll have to use the rectangles you're drawing on the screen. Instead of calling the tooltip function with hard-coded values, use the values you get from the squares.
Take on those pieces one at a time, and make small steps toward your goal. Then if you get stuck, you can post an MCVE of the specific step you're on. Good luck!

Snapping to closest Marker Method always returning false

hopefully you can help me with this problem :)
Here is my situation:
Recently I have been working with the Google Maps API. And with that I created a map, with markers representing a recomendation on a travel route on it. The data of each of these markers are stored in my DB, so that everytime I start my Webapplication I can represent them on the map. The markers however can be moved to another place on the map by the user, and so a new travel route is stored.
Other than the recommended trip, I also store other locations of the same country in my DB. This is due to the abilty of the user to move the other markers around to personalize his/her trip.
Here is my Problem:
With Javascript I listen to the Drop Event of the user and afterwards I get the new Location of that marker(location where it was dropped) and pass it on to another method:
//point1: contains marker which was dropped with its new location
//location: String containing the name of the place where the marker was dropped
arePointsNear: function (point1, location) {
//result: Obj I pass on to method: trip.getHotels in order to get a List containing
//all Hotels in the region 'location'
var result = { 'markerLat': point1.position.lat(), 'markerLng': point1.position.lng(), 'loc': location };
Trip.getHotels(result, function (hotels) {
var found = false;
//i: used as index of the hotels Array(res)
var i = 0
while (!found) {
//sw: SouthWest, ne: NorthEast
var sw = new google.maps.LatLng(hotels[i].lat - 1, hotels[i].lng - 1);
var ne = new google.maps.LatLng(hotels[i].lat + 1, hotels[i].lng + 1);
var bounds = new google.maps.LatLngBounds(sw, ne);
found = $.contains(bounds, point1);
if (found) { break; }
i++;
}
});
}
I am basically trying now to snapp the dropped marker to the closest hotel in my array. The Code above doesnt give out any error but the var found is always 'false', even though I am pretty shure that the location of the dropped marker is within the boundaries of one of the hotels.
Link to Related question: Snap to nearest marker
Here my questions ...
Why is found always false? Could it be that I am missing something?
Does the problem lie with the jquery contains method, or with the following line:
var sw = new google.maps.LatLng(hotels[i].lat - 1, hotels[i].lng - 1);
I would be glad to hear your answers and I thank you in advance
Edit!
After getting my question answered I tried it with '.contains'. This for some reason still wouldnt work, so I have written a pretty simple method myself to detect the closest point or marker to another point or marker on the map. This I want to share with everyone who may be looking for just that:
//marker: Point to which you want to find the closest Point to
arePointsClose: function (marker) {
//Trip: This is my Object and getHotels a method within my Object trip
//With getHotels I get a List of Hotels from the Controller using AJAX
//I pass a function to getHotels (For more information research: Callback)
Trip.getHotels(function (hotels) {
var closestPoint = undefined;
var closestHotel = 0;
//Here I start looping through my array of hotels
for (var i = 0; i < hotels.length ; i++) {
// Here I maka an if in one line and if the number resulting should be negative I multiply by -1 to evit that
var x = ((marker.position.lat() - hotels[i].lat) < 0) ? (marker.position.lat() - hotels[i].lat) * (-1) : (marker.position.lat() - hotels[i].lat);
var y = ((marker.position.lng() - hotels[i].lng) < 0) ? (marker.position.lng() - hotels[i].lng) * (-1) : (marker.position.lng() - hotels[i].lng);
var point = x + y;
//The point var is overwritten for the purpose of checking later in the if, if it is actually smaller than the actual closestPoint
if (closestPoint == undefined || point <= closestPoint) {
closestPoint = point;
closestHotel = hotels[i];
}
}
//Now closestPoint contains just what you wanted. Pass it on to another function now to maybe redraw the markers (and) their path
//Trip.yourFunction
});
}
The jQuery contains function is mean to work with DOM elements, e.g. does this div contain that span:
$.contains('#thisDiv', '#thatSpan')`;
Not if a Google Maps LatLngBounds object contains a specific point.
For that, you need to do use the LatLngBounds' own contains function, e.g.:
var bounds = new google.maps.LatLngBounds(sw, ne);
found = bounds.contains(point1);

Animating Paper.js path segments & handle info

I'm trying to animate between two complex paths using Paper.js and Tween.js. I've gotten pretty close, I can move all of the points in the path to the correct final positions, but I'm having problems with the handleIn and handleOut for each segment. It doesn't seem to be updating them.
Here's my code: http://codepen.io/anon/pen/rVBaZV?editors=101
var endPathData = 'M740,342.9c-32,...etc...';
var endPath = new Path(endPathData);
endPath.fillColor = '#4CC7A4';
beginPathData = 'M762.8,262.8c-48,...etc...';
var beginPath = new Path(beginPathData);
beginPath.fillColor = '#FFC1D1';
var numberOfSegments = beginPath.segments.length;
for (var i = 0; i < numberOfSegments; i++) {
var tween = new TWEEN.Tween(beginPath.segments[i].point)
.to({
x: endPath.segments[i].point.x,
y: endPath.segments[i].point.y
}, 3000)
.easing(TWEEN.Easing.Linear.None)
.start();
}
view.draw();
view.onFrame = function (event) {
TWEEN.update();
};
I'd like the pink path to end up exactly like the green one, but now I'm stuck. Is there anyway to achieve this?
You need to tween the handles too.
Each segment has two handles: segment.handleIn and segment.handleOut
in your example code you tween the segment.point (the segments position) resulting in the right location of the segments.
I don't know your Tween library, so it is up to you to implement it.
But It looks like you can add to more new tween one for the
beginPath.segments[i].handleIn
and one for the
beginPath.segments[i].handleOut
You can easily check that your code is right by letting paperjs smooth your path and taking care of the handles. By updating the onFrame function like this:
view.onFrame = function (event) {
TWEEN.update();
beginPath.smooth();
endPath.smooth();
};
and this results in the same shaped path.

How to Manipulate a line in Raphael to add/edit points on the line

I have quite a few years experience coding but very minimal javascript and raphael. I've been looking online and in a book I bought on raphael. Part of what I am trying to do is to have a line / path and provide the user the capability to add or edit points to this line.
Something similar to the route on google maps.
I know that a path can be constructed using arrays. I believe those elements can have a unique id that will help accessing them.
I'm thinking something similar to one of the approaches suggested in this question, in particular Adam MoszczyƄski's approach with regards to separating the drawing from the data; especially as down the road it's likely that this path data will need to be persisted/loaded.
The SVG path syntax is fairly simple if all you want to do is draw straight lines. You really only need to know two commands: M to set the location of the first point on the path and L to draw a line to the rest of the points.
For example, to draw the following polyline:
[5,2]___________[12,2]
/ \
/ \
/ [13,5]
/
[1,10]
you would use the following SVG path:
M 1 10 L 5 2 L 12 2 L 13 5
Note that SVG accepts , as number separators and spaces between commands and arguments are optional if they're unambiguous. So the above path can also be written as:
M 1,10 L 5,2 L 12,2 L 13,5
or even:
M1,10L5,2L12,2L13,5
But the first form is the most convenient for us to use programatically because it can simply be constructed by joining array elements with the space character:
var svgpath = [
'M', 1, 10,
'L', 5, 2,
'L', 12, 2,
'L', 13, 5
].join(' ');
Given this, it's easy to write a function that draws a path in Raphael. There are two ways this can be done:
Write your own independent line drawing function/library and use Raphael just as a tool to draw to screen. The simplest is just a function that draws the polyline.
function draw_polyline(paper,coords) {
var first = coords.shift();
var path = ['M', first[0], first[1]];
for (var i=0; i<coords.length; i++) {
path.push('L',coords[i][0],coords[i][1]);
}
return paper.path(path.join[' ']);
}
draw_polyline(paper,[[1,10],[5,2],[12,2],[13,5]]);
Write it as an extension to Raphael. The function is exactly the same but behaves as if it's part of Raphael:
Raphael.fn.polyline = function (coords) {
var first = coords.shift();
var path = ['M', first[0], first[1]];
for (var i=0; i<coords.length; i++) {
path.push('L',coords[i][0],coords[i][1]);
}
return this.path(path.join[' ']);
}
var paper = Raphael('div',640,480);
paper.polyline([[1,10],[5,2],[12,2],[13,5]]);
Note that the simple function above simply draws the line from the given coordinates. To add or remove points to the line you'll have to redraw the path by calling the polyline function with the updated coordinates. This works but is not exactly ideal since you'll have to keep track of the old line and delete it when you draw the new line.
A better solution is to have the line update itself. To do this we'll need to refactor the code above to separate the svg path generation from the drawing function:
function polyline_path(coords) {
var first = coords.shift();
var path = ['M', first[0], first[1]];
for (var i=0; i<coords.length; i++) {
path.push('L',coords[i][0],coords[i][1]);
}
return path.join(' ');
}
then the Raphael implementation is simply:
Raphael.fn.polyline = function (coords) {
return this.path(polyline_path(coords));
}
Now we can add some functionality to the Raphael path object that represents our polyline:
Raphael.fn.polyline = function (coords) {
var path = this.path(polyline_path(coords));
path.coords = function(xy) {
if (!xy) {return coords} // return coordinates if no arguments given
coords = xy;
path.attr('path',polyline_path(coords)); // otherwise update svg path
}
return path;
}
Now we can use it like this:
var route = paper.polyline([[1,10],[5,2],[12,2],[13,5]]);
// Add point to route:
route.coords(route.coords().push([15,10]));
That's still a bit ungainly but you get the idea. You can use this basic concept to implement more advanced API. For example, it would be nice if the polyline object implement most of the Array methods so you can simply do:
route.push([15,10]); // add point to route
route.shift(); // remove point from beginning of route
route.splice(1,1,[5,3]); // modify the second point
// etc..
It's really up to you how far you want to take this.

Google Maps API V3 - Showing progress along a route

I want to show a custom route on a route along with the current progress. I've overlayed the a Polyline to show the route and I am able to find the LatLng of the current postion and place a marker. What I want to do now is to highlight the traveled part of the Polyline using a different colour. I'm pretty sure it's not possible to have multiple colours for one Polyline so I plan to overlay a second Polyline over the first the first to give this effect. Here's my question:
Knowing the LatLng of the current position and armed with the array of LatLng points used to create the orginal Polyline how best can I create the second 'Progess' route?
Thanks ahead.
With a few assumptions:
Your path is quite dense (if not, you could interpolate intermediate points)
Your route doesn't overlap with itself (wouldn't work with a repeating circular path, say)
..one crude way would be as follows:
Using Python'y pseudo-code, say you have a route like this:
points = [LatLng(1, 1), LatLng(2, 2), LatLng(3, 3), LatLng(4, 4)]
You draw this as a PolyLine as usual.
Then, given your current position, you would find the nearest point on the route:
cur_pos = LatLng(3.1, 3.0123)
nearest_latlng = closest_point(points, to = cur_pos)
Then nearest_latlng would contain LatLng(3, 3)
Find nearest_latlng in the list, then simply draw a second polyline up to this point. In other words, you truncate the points list at the current LatLng:
progress_points = [LatLng(1, 1), LatLng(2, 2), LatLng(3, 3)]
..then draw that on the map
As mentioned, this will break if the path loops back on itself (closest_point would only ever find the first or last point)
If you know how far has been travelled, there is an epoly extension which gives a few methods that could be used, mainly:
.GetIndexAtDistance(metres)
Returns the vertex number at or after the specified distance along the path.
That index could be used instead of the closest_point calculated one above
Typescript example:
getRouteProgressPoints(directionResult: google.maps.DirectionsResult | undefined, targetDistance: number) {
if (!directionResult || targetDistance <= 0) return [];
const route = directionResult.routes[0];
const leg: google.maps.DirectionsLeg = route.legs[0];
const routePoints: google.maps.LatLng[] = [];
// collect all points
leg.steps.forEach((step) => {
step.path.forEach((stepPath) => {
routePoints.push(stepPath);
});
});
let dist = 0;
let olddist = 0;
const points = [];
// go throw all points until target
for (let i = 0; i < routePoints.length && dist < targetDistance; i++) {
const currentPoint = routePoints[i];
// add first point
if (!i) {
points.push(currentPoint);
continue;
}
const prevPoint = routePoints[i - 1];
olddist = dist;
// add distance between points
dist += google.maps.geometry.spherical.computeDistanceBetween(prevPoint, currentPoint);
if (dist > targetDistance) {
const m = (targetDistance - olddist) / (dist - olddist);
const targetPoint = new google.maps.LatLng(
prevPoint.lat() + (currentPoint.lat() - prevPoint.lat()) * m,
prevPoint.lng() + (currentPoint.lng() - prevPoint.lng()) * m,
);
points.push(targetPoint);
} else {
points.push(currentPoint);
}
}
return points;
}
That is going to be really difficult unless you already have a way of determining that the vehicle has passed certain points already. If you are aware that it has passed certain points you just create the second polyline with the passed points and the current marker as the end of the polyline. You will probably want to change the width of the polyline in order to make it viewable when it is overlaying the initial route.
All of that being said if you don't know if the points have been passed I am not sure what you can do other than write some sort of code that determines if your current position is past a certain set of points, but I don't know how you do that if a route zigzags all over the place possibly..... Now if your points go from straight west to straight east etc, than coding something will be easy, but I doubt that is the case.
Hope this helps somewhat....

Categories