I have a bunch of leaflet polygons on a map I created. Each polygon represents something different. A specific set of information is displayed in a popup depending on the page the user is on. I need to find a way to make the "popup" bubble open in the center of the polygon it represents.
Each polygon is drawn using the following code:
var L20 = [
[74.0995, -99.92615],
[74.14008, -99.4043],
[74.07691, -99.33838],
[74.03617, -99.86023]
];
var L19 = [
[74.02559, -99.84924],
[74.06636, -99.32739],
[74.0029, -99.26147],
[73.96197, -99.77783]
];
var L18 = [
[73.95142, -99.76684],
[73.99235, -99.25048],
[73.92889, -99.18456],
[73.8878, -99.69543]
];
var set1 = L.polygon([L20, L19, L18], {
color: "#fff",
weight: 1,
stroke: true,
opacity: 0.05,
fillColor: "#346B1F",
}).addTo(map);
The popup is drawn using the following code:
var popup = L.popup({})
.setLatLng([73.64017, -100.32715])
.setContent(content).openOn(map);
var popup = L.popup();
So I need to find a way for .setLatLang to determin or be given the center of the polygon.
I came up with 3 solutions that may work, not sure how to go about it.
find a way to use the coordinates of a polygon to determine the center of the polygon where the popup will open.
call one point of the polygon, then offset the position of the popup.
Use an id for each polygon, so each popup knows the box area (polygon) it can be opened in.
Can someone help me please?
Since some time Leaflet has built-in getCenter() method:
polygon.getBounds().getCenter();
There are a few ways to approximate the centroid of a polygon.
The easiest (but least accurate method) is to get the center of the bounding box that contains the polygon, as yarl suggested, using polygon.getBounds().getCenter();
I originally answered the question with the formula for finding the centroid of the points, which can be found by averaging the coordinates of its vertices.
var getCentroid = function (arr) {
return arr.reduce(function (x,y) {
return [x[0] + y[0]/arr.length, x[1] + y[1]/arr.length]
}, [0,0])
}
centerL20 = getCentroid(L20);
While the centroid of the points is a close enough approximation to trick me, a commenter pointed out that it is not the centroid of the polygon.
An implementation based on the formula for a centroid of a non-self-intersecting closed polygon gives the correct result:
var getCentroid2 = function (arr) {
var twoTimesSignedArea = 0;
var cxTimes6SignedArea = 0;
var cyTimes6SignedArea = 0;
var length = arr.length
var x = function (i) { return arr[i % length][0] };
var y = function (i) { return arr[i % length][1] };
for ( var i = 0; i < arr.length; i++) {
var twoSA = x(i)*y(i+1) - x(i+1)*y(i);
twoTimesSignedArea += twoSA;
cxTimes6SignedArea += (x(i) + x(i+1)) * twoSA;
cyTimes6SignedArea += (y(i) + y(i+1)) * twoSA;
}
var sixSignedArea = 3 * twoTimesSignedArea;
return [ cxTimes6SignedArea / sixSignedArea, cyTimes6SignedArea / sixSignedArea];
}
The problem you are trying to solve is called the pole of inaccessibility problem. Finding the best place to put a label in a polygon isn't completely solved by finding the center of the bounding box. Consider a polygon in the shape of the letter U. The center of the bounding box puts the label outside of the polygon. It took me forever to find this outstanding library: https://github.com/mapbox/polylabel
From the README.MD:
A fast algorithm for finding polygon pole of inaccessibility, the most distant internal point from the polygon outline (not to be confused with centroid), implemented as a JavaScript library. Useful for optimal placement of a text label on a polygon.
It's an iterative grid algorithm, inspired by paper by Garcia-Castellanos & Lombardo, 2007. Unlike the one in the paper, this algorithm:
guarantees finding global optimum within the given precision
is many times faster (10-40x)
Usage:
Given polygon coordinates in GeoJSON-like format and precision (1.0 by default), Polylabel returns the pole of inaccessibility coordinate in [x, y] format.
var p = polylabel(polygon, 1.0);
How the algorithm works:
This is an iterative grid-based algorithm, which starts by covering the polygon with big square cells and then iteratively splitting them in the order of the most promising ones, while aggressively pruning uninteresting cells.
Generate initial square cells that fully cover the polygon (with cell size equal to either width or height, whichever is lower). Calculate distance from the center of each cell to the outer polygon, using negative value if the point is outside the polygon (detected by ray-casting).
Put the cells into a priority queue sorted by the maximum potential distance from a point inside a cell, defined as a sum of the distance from the center and the cell radius (equal to cell_size * sqrt(2) / 2).
Calculate the distance from the centroid of the polygon and pick it as the first "best so far".
Pull out cells from the priority queue one by one. If a cell's distance is better than the current best, save it as such. Then, if the cell potentially contains a better solution that the current best (cell_max - best_dist > precision), split it into 4 children cells and put them in the queue.
Stop the algorithm when we have exhausted the queue and return the best cell's center as the pole of inaccessibility. It will be guaranteed to be a global optimum within the given precision.
assuming each polygon has only 4 sides it is simple
var L20 = [
[74.0995, -99.92615],
[74.14008, -99.4043],
[74.07691, -99.33838],
[74.03617, -99.86023]
];
using this example get max and min lat: 74.03617 and 74.14008 respectively so same for long: -99.92615 and 99.33838 respectively
Then get the middle value for each: (max - min) / 2 = 0.051955 and -0.293885 then add them to the minimum amount
gives you a centre of 74.088125, -99.632265
To move a polygon into view and center it use:
map.fitBounds(poly.getBounds())
This will also set the zoom level correctly.
Related
I have a 500 pixel by 500 pixel image that I am using to pull data from a 250,000 index array where each index represents 1 pixel.
The user is able to draw a rectangle at any orientation, and I am capturing the coordinates for each corner.
I am trying to capture each pixel within the rectangle to reference the data array and extract the related data.
I looked at Bresenham algorithm in Javascript and while I can get all the points between each of the coordinates using this solution, I am unable to loop through these points as the paths do not always contain the same number of pixels.
An example of the values I'm looking for using the following coordinates would be:
corner1 = [100,100]
corner2 = [100,105]
corner3 = [105,105]
corner4 = [105,100]
And the result (sort order is not important):
pixelsInRectangle = [
[100,100],[100,101],[100,102],[100,103],[100,104],[100,105],
[101,100],[101,101],[101,102],[101,103],[101,104],[101,105],
[102,100],[102,101],[102,102],[102,103],[102,104],[102,105],
[103,100],[103,101],[103,102],[103,103],[103,104],[103,105],
[104,100],[104,101],[104,102],[104,103],[104,104],[104,105],
[105,100],[105,101],[105,102],[105,103],[105,104],[105,105]
]
One set of coordinates I'm trying to solve for are:
corner1 = [183,194]
corner2 = [190,189]
corner3 = [186,184]
corner4 = [179,190]
Any recommendations would be greatly appreciated!
If rectangle is not axis aligned:
Sort vertices by Y-coordinate.
Get the lowest one. From two next Y-coordinates choose left and right ones.
Start simple line rasterization scan along left edge and along right edge simultaneously: for current integer Y value calculate corresponding rounded X-coordinate for left edge, for right edge, and output all horizonatal line between (xleft, y)-(xright,y)
For edge between vertices (x0,y0)-(x1,y1) formula is
x = x0 + (x1-x0)*(y-y0)/(y1-y0)
(example for triangle exploiting the same technique)
When some vertex is reached, change equation of corresponding edge, continue.
Using this way, you fill triangle, parallelogramm, another triangle (or just two triangles if two vertices share the same Y)
(You can use Bresenham or DDA, or another line rasterization algorithm if necessary)
I am confused when implementing (measure) angles in an HTML5 canvas especially after rotating objects
Let's assume I have drawn a shape like this
ctx.moveTo(100,100)//center of canvas
ctx.lineTo(0,200)//left bottom
ctx.lineTo(200,200)//right bottom
We know it is a 45 degrees or pi*2/4 angle.
But how do I figure it out using Math functions to see if the shape was rotated or not?
And how to re-measure it after changing the x and y of the shape(first point) ?
First things first, you will need to make sure the points are stored in some sort of data structure, as it won't be easy to pull the points from the canvas itself. Something like an array of arrays:
var angle = [[100,100], [0,200], [200,200]];
Now, you need to convert your lines to vectors:
var AB = [angle[1][0]-angle[0][0], angle[1][1]-angle[0][1]];
var BC = [angle[2][0]-angle[1][0], angle[2][1]-angle[1][1]];
Now find the dot-product of the two:
var dot_product = (AB[0]*BC[0]) + (AB[1]*BC[1]);//not dot-product
Now you need to find the length (magnitude) of the vectors:
var ABmagnitude = Math.sqrt(Math.pow(AB[0],2) + Math.pow(AB[1],2));
var BCmagnitude = Math.sqrt(Math.pow(BC[0],2) + Math.pow(BC[1],2));
Now you put it all together by dividing the dot product by the product of the two magnitudes and getting the arcosine:
var theta = Math.acos(dot_product/(ABmagnitude*BCmagnitude));
You mentioned rotation, but unless you are only rotating one line, the angle will stay the same.
This one requires a bit of visualisation, so sorry if my explanation sucks.
So, I have a central point at 0,0. From this point, I am plotting random points on its circumference, at a radius of 350 pixels (random number). For this I am using this code:
var angle = Math.random()*Math.PI*2;
var x = Math.cos(angle)*radius;
var y = Math.sin(angle)*radius;
x+=parent.position.x;
y+=parent.position.y;
The parent.position this is because each point that is plotted also acts as a central node, which has children that act as nodes and so on. This just sets the position of the new node relative the position of its parent.
So this code works perfectly well for the central node. The problem is that once you've branched away from the centre, you want to continue moving in a particular direction to avoid a big cluster of nodes interfering with each other. So, whereas this code plots a point on the circumference, I need to be able to plot a point on a segment of the circumference. I'm thinking maybe about a third of the circumference should be accessible. The other obstacle is that this has to be the CORRECT segment of the circumference i.e If the nodes are branching upwards, I don't want the segment to be the bottom half of the circumference, the branch needs to continue moving in the upwards direction.
I can establish a general direction based on the position of the new parent node relative to the position of its parent. But does anyone have any ideas of how to use this data to reduce the field to the a segment in this direction?
Let me know if that made no sense, it's kinda hard to explain without diagrams.
I think one easy way of doing that would be to split your circle in n segments (each covering 2*PI / n angle). You could set n to whatever you want, depending on how precise you want to be. Then when you calculate a new point x, first get the segment in which x.parent is (relative to its own parent), and use that to put x in the same section wrt x.parent. You could then have something like this:
var getSection = function(point) {
var parent = point.parent;
var angle = Math.acos((point.x - parent.x) / radius) % (Math.PI*2);
var section = Math.floo(angle / (Math.PI * 2 / n))
return section;
}
var section = getSection(parent); // return the index of the section
var angle = (Math.random() + section) * Math.PI * 2 / n
var x = Math.cos(angle)*radius;
var y = Math.sin(angle)*radius;
x+=parent.position.x;
y+=parent.position.y;
Need some help here. I'm a UI designer who isn't good at numbers doing an experimental web form design and I need to know which input element is closest to a clicked point on a web page. I know how to do nearest neighbor with points but the input elements are rectangles not points so I'm stuck.
I'm using jQuery. I just need help with this little algo. Once I'm done with my experiment I'll show you guys what I'm doing.
UPDATE
I thought about how it can work. Look at this diagram:
Each rectangle has 8 points (or rather 4 points and 4 lines) which are significant. Only the x value is significant for horizontal points (red dot) and only the y value is significant for vertical points (green dot). Both x and y are significant for the corners.
Orange crosses are the points to be measured against – mouse clicks in my use case. The light purple lines are the distances between the orange cross and it's possible nearest point.
So… for any given orange cross, loop through each of the 8 points n every rectangle to find the nearest edge or corner closest of each rectangle to the orange cross. The rectangle with the lowest value is the nearest one.
I can conceptualize and visualize it but can't put it into code. Help!
Your algorithm is correct. Since you need help in code, and not in the algorithm, here's the code:
It may not be the most efficient. But it works.
// Define the click
var click = Array(-1, -2); // coodinates in x,y
// Define the buttons
// Assuming buttons do not overlap
var button0 = Array(
Array(0, 0), // bottom-left point (assuming x is horizontal and y is vertical)
Array(6, 6) // upper-right point
);
var button1 = Array(
Array(10, 11),
Array(17, 15)
);
var button2 = Array(
Array(-8, -5),
Array(-3, -1)
);
// Which button to trigger for a click
i = which(click, Array(button0, button1, button2));
alert(i);
function which(click, buttons){
// Check if click is inside any of the buttons
for (i in buttons){
var button = buttons[i];
var bl = button[0];
var tr = button[1];
if ( (click[0] >= bl[0] && click[0] <= tr[0]) &&
(click[1] >= bl[1] && click[1] <= tr[1]) ){
return i;
}
}
// Now calculate distances
var distances = Array();
for (i in buttons){
var button = buttons[i];
var bl = button[0];
var tr = button[1];
if ( (click[0] >= bl[0] && click[0] <= tr[0])) {
distances[i] = Math.min( Math.abs(click[1]-bl[1]), Math.abs(click[1]-tr[1]) );
}
else if ( (click[1] >= bl[1] && click[1] <= tr[1])) {
distances[i] = Math.min( Math.abs(click[0]-bl[0]), Math.abs(click[0]-tr[0]) );
}
else{
distances[i] = Math.sqrt(
(Math.pow(Math.min( Math.abs(click[0]-bl[0]), Math.abs(click[0]-tr[0]) ), 2)) +
(Math.pow(Math.min( Math.abs(click[1]-bl[1]), Math.abs(click[1]-tr[1]) ), 2))
);
}
}
var min_id = 0;
for (j in distances){
if (distances[j] < distances[min_id]){
min_id = j;
}
}
return min_id;
}
The addition of the relatively new elementFromPoint() API lets us take an alternative, potentially lighter approach: we can hit test around the mouse cursor, going in larger circles until we find the nearest element.
I put together a quick, non-production example here: http://jsfiddle.net/yRhhs/ (Chrome/Safari only due to use of webkitMatchesSelector). The performance can get laggy due to the dots used in visualizing the algorithm.
The core of the code, outside of the light performance optimizations and event bindings, is this bit:
function hitTest(x, y){
var element, i = 0;
while (!element){
i = i + 7; // Or some other threshold.
if (i > 250){ // We do want some safety belts on our while loop.
break;
}
var increment = i/Math.sqrt(2);
var points = [
[x-increment, y-increment], [x+increment, y-increment],
[x+increment, y+increment], [x-increment, y+increment]
];
// Pop additional points onto the stack as the value of i gets larger.
// ...
// Perhaps prematurely optimized: we're using Array.prototype.some to bail-out
// early once we've found a valid hit target.
points.some(function(coordinates){
var hit = document.elementFromPoint.apply(document, coordinates);
// isValidHit() could simply be a method that sees whether the current
// element matches the kinds of elements we'd like to see.
if (isValidHit(hit)){
element = hit;
return true;
}
});
}
You could look for the nearest corner point of all rectangles. This works in the most cases, is fast and easy to implement. As long as your rectangles are aligned on a regular grid this method gives you the nearest rectangle.
The way I'd do it is not with numbers, but with logic.
I'm assuming that you want to end up with something that says, "if x is the nearest element then do something when I clicked elsewhere then do something to x"
You could do this if each of the elements you want to do something with were in simple <div> containers that were larger than the element you want to treat, but no larger than halfway between the object it contains and it's next nearest object. A grid in fact.
give all the containers the same class.
Then you could say, "if y is clicked go do something to x", you would already know which element is in each container.
I'd write the code but I'm leaving work...
If you want to find the distance between two points on a 2D grid, you can use the following formula:
(for 2D points A & B)
distanceX = A.x - B.x
distanceY = A.y - B.y
totalDistance = squareRoot ((distX * distX) + (distY * distY))
Once you can check the distance between two points you can pretty easily figure out which rectangle corner your mouse click is closest too. There are numerous things you can do to optimise your intended algorithm, but this should give you a good start.
lol, the question is why are you thinking of shapes?
your question really is "if i click a coordinate, find me the nearest node/point to my click" which is a matter of going through the various nodes and calculating distances.
If same X, use y difference
If same y, use x difference
otherwise use hypotheneuse
Once you find the nearest point you can get the parent shape right?
This will work because you're trying to snap to nearest point. So it'll even work with fancy shapes like stars.
I am working on a project in javascript involving google maps.
The goal is to figure out 16-20 coordinate points within n kilometers from a set of latitude longitude coordinates such that the 16 points if connected will form a circle around the original coordinates.
The end goal is to make it so I can figure out coordinates to plot and connect on google maps to make a circle around a given set of coordinates.
The code would go something like:
var coordinates = Array();
function findCoordinates(lat, long, range) {
}
coordinates = findCoordinates(-20, 40, 3);
Now to make the magic happen in the findCoordinates() function.
Basically what you're trying to do is find N points on the radius of a circle from a given point with a given radius. One simple way of doing it is splitting the 360 degrees of a circle in to N equal chunks, and finding the points at regular intervals.
The following should do roughly what you're after -
function findCoordinates(lat, long, range)
{
// How many points do we want? (should probably be function param..)
var numberOfPoints = 16;
var degreesPerPoint = 360 / numberOfPoints;
// Keep track of the angle from centre to radius
var currentAngle = 0;
// The points on the radius will be lat+x2, long+y2
var x2;
var y2;
// Track the points we generate to return at the end
var points = [];
for(var i=0; i < numberOfPoints; i++)
{
// X2 point will be cosine of angle * radius (range)
x2 = Math.cos(currentAngle) * range;
// Y2 point will be sin * range
y2 = Math.sin(currentAngle) * range;
// Assuming here you're using points for each x,y..
p = new Point(lat+x2, long+y2);
// save to our results array
points.push(p);
// Shift our angle around for the next point
currentAngle += degreesPerPoint;
}
// Return the points we've generated
return points;
}
The array of points you get back can then easily be used to draw the circle you wish on your google map.
If your overall goal however is just to draw a circle at a fixed radius around a point, then a far easier solution may be to use an overlay. I've found KMBox to be very easy to set up - you give it a central point, a radius and an image overlay (in your case, a transparent circle with a visible line around the edge) and it takes care of everything else, including resizing it on zoom in/out.
I had to find some code to calculate Great Circle distances a while back (just Google "Great Circle" if you don't know what I'm talking about) and I found this site:
http://williams.best.vwh.net/gccalc.htm
You might be able to build up your own JavaScript code to do your lat/lon range calculations using the JavaScript from that site as a reference. It sounds to me like you just need to divide up the 360 degrees of a circle into an equal number of pieces and draw a line out to an equal distance from the center at each "bearing". Once you know the lat/lon at the other end of each bearing/distance line, then connecting the dots to form a polygon is trivial.