Get the row and column in a canvas - javascript

I am making a minimap for players to see each other in the world. I want to add a sector indicator which tells the individual player which sector they are in. The world is a 5000x5000 matrix.

Take the scale of the world and divide by the rows and columns to get the scale. Then divide the players x, y by the scale to get the sectors. I made COLUMNS and ROWS arrays in case you want to change to naming system. This method returns a string.
function getSector(x, y) {
// The dimensions of the map
const WIDTH = 5000, HEIGHT = 5000;
// The sectors row and column labels
const COLUMNS = ['1','2','3','4','5'];
const ROWS = ['A','B','C','D','E'];
// Scale map to number of sectors
const colScale = Math.floor(WIDTH / COLUMNS.length);
const rowScale = Math.floor(HEIGHT / ROWS.length);
// Convert xy coordinates to sectors
const col = Math.floor(x / colScale);
const row = Math.floor(y / rowScale);
// Returns string of current sector
return `${ROWS[row]}${COLUMNS[col]}`;
}
console.log(getSector(0, 0))
console.log(getSector(4999, 4999))

Related

How to set circle radius based on % of a value?

Currently I have circles drawn on a map and I set the radius doing:
radius: density * (1 - 0.65)
That works somehow as it avoids getting too big since that density value could be huge. Yet everything changes when we have little density value as the circles gets too small.
UPDATE
One idea could be, since these circles are a bunch of data on the map but we also have the total density called globalDensity which gives us the sum of all densities, maybe there is a way to get a % of that?
UPDATE TWO
Actually that idea is not possible since the total is calculated after all circles have been placed on the map.
UPDATE THREE
We have plenty of different circles on a map with dynamic density values, these various from a min of zero to a max of X number
When I do radius: density * (1 - 0.65) works well when sizing the big values but doesn't work when we have let's say, 1 as density
value
At some point after the loop, I calculate also ALL densities but unfortunately that happens after the loop e.g. after I push all densities in an array and I do the sum, so it's too late to use that as a maxValue to do the calculation.
A comment below the question has suggested to use a minValue and a maxValue which makes sense but I'm not sure how to then calculate the % based on that, I tried the following but I'm doing it wrong as the result is a Huge circle.
var minRadius = 1000;
var maxRadius = density;
var percentage = maxRadius * (1 - 0.40);
var radius = minRadius + percentage * (maxRadius -minRadius).toFixed(0);
Looping over data is fast, don't worry about it.
One may think that with big data it would be better to avoid looping twice, but actually given how JIT nowadays does an awesome job at optimizing, having two loops performing a single action is often faster than having a single one doing two things.
So simply do a first pass over your data to get the extents:
const data = [ ...
const extents = data.reduce( (extents, datum) => {
const value = datum.value;
extents.min = Math.min( extents.min, value );
extents.max = Math.max( extents.max, value );
return extents;
}, { min: Infinity, max: -Infinity } );
Now, you need to define the minRadius and maxRadius values to the visual extents you want. (I guess it will be in pixels).
const minRadius = 5; // circles can't be smaller than that
const maxRadius = 30; // circles can't be bigger than that
Finally you can set your circles radii based on these extents:
const valDistance = extents.max - extents.min;
const radiusDistance = maxRadius - minRadius;
data.forEach( datum => {
datum.radius = (datum.value - extents.min) / valDistance * radiusDistance + minRadius;
} );
const data = Array.from({ length: 20 }, () => ({
value: Math.random() * 300 - 150
}));
const extents = data.reduce((extents, datum) => {
const value = datum.value;
extents.min = Math.min(extents.min, value);
extents.max = Math.max(extents.max, value);
return extents;
}, { min: Infinity, max: -Infinity });
const minRadius = 5; // circles can't be smaller than that
const maxRadius = 30; // circles can't be bigger than that
const valDistance = extents.max - extents.min;
const radiusDistance = maxRadius - minRadius;
data.forEach(datum => {
datum.radius = (datum.value - extents.min) / valDistance * radiusDistance + minRadius;
});
console.log(data);

How to get the intersection points of an Arc and a line?

I need to place several number of line segments inside an arc, but to do that i need to have intersection points so that i can place the lines inside the arc perfectly;
I thought about a way to calculate the distance and check if it is less than radius, but the thing is i need to know the C,D & E points so that i can place the line segments, so i'm lost here, any one can help please?
EDIT
The radius is specified
Number of line segments may vary, but there are 2 lines at least
Lines start at starting border, end at the ending border; e.g: Start is C, end point is D
EDIT
In order to be clear about what i'm trying to do, i'm uploading another illustration;
I need to get the coordinates of [CD],[EI],[JK] lines,
Ok... Here we go. The following snippet should work for any arc (defined with an angle and a radius) and for any number of equally spaced segments you want.
Currently, it assumes that the arc is perfectly placed horizontally (like in your example), but it can be "easily" extended to allow translated/rotated arcs.
The getLinesCoords() function will return an object whose x and y properties contains arrays with the corresponding coordinates for each segment.
"y" coordinates are the "height" of the segment from the center (G in your image) and "x" are the start/end position, always from center (left/right depends on sign +/-).
If you have any question, please ask.
// *** ARC ***
const R = 100; // RADIUS
const PHI = 180; // ANGLE (DEG)
// *** LINES ***
const LINES = 10; // NUMBER OF LINES TO BE PLACED
// *** CALCULATIONS ***
const coords = getLinesCoords(R, PHI, LINES);
console.log(coords);
function getLinesCoords(radius, angle, linesNum) {
let i = 0;
let arcAvailHeight = 0;
let linesSep = 0;
let linesYCoords = [];
let linesXCoords = [];
let angleRad = angle * Math.PI / 180;
// GET AVAILABLE HEIGHT FOR PLACING LINES
arcAvailHeight = radius * (1 - Math.cos(angleRad / 2));
// GET LINES SEPARATION
linesSep = arcAvailHeight / (linesNum + 1);
// GET Y COORDINATES FOR LINES
for (i = 0; i < linesNum; i++) {
linesYCoords[i] = linesSep * (i + 1);
}
// GET CORRESPONDING X COORDINATES FOR LINES
linesYCoords.forEach((y) => {
linesXCoords.push(Math.sqrt(radius**2 - (radius * Math.cos(angleRad / 2) + y)**2));
});
return ({x: linesXCoords, y: linesYCoords});
}

Mysql polygon between multiple coordinates

I have 4 points on the map(latitude, longitude), there can be a more.
And I need to build a MySQL POLYGON with 10 Kilometers wide between these points.
As you see from the picture - I have blue line, and I need a polygon (green outline).
My first thought was to create two separate points for each given point.
For example:
Given 1st point(Bremen)
calculate 5km right and add a point,
calculate 5km left and add a point.
Logic is simple.
BUT the problem is - I don't know what to calculate(right,top,bottom,left), i need some kind of angle, but I am stuck here.
I just need an algorithm, no need for a full code example.
function in javascript i tried:
var meters = 10000 / 2;
var my_lat = 52.51978;
var my_long = 13.388211;
// number of km per degree = ~111km (111.32 in google maps, but range varies
/* between 110.567km at the equator and 111.699km at the poles) */
// 1km in degree = 1 / 111.32km = 0.0089
// 1m in degree = 0.0089 / 1000 = 0.0000089
var coef = meters * 0.0000089;
var new_lat = my_lat + coef;
// pi / 180 = 0.018
var new_long = my_long - coef / Math.cos(my_lat * 0.018);

Isometric tilemap using canvas (with click detection)

I am currently developing a game, which requires a map consisting of various tile images. I managed to make them display correctly (see second image) but I am now unsure of how to calculate the clicked tile from the mouse position.
Are there any existing libraries for this purpose?
Please also note, that the tile images aren't drawn perfectly "corner-facing-camera", they are slightly rotated clockwise.
Isometric Transformations
Define a projection
Isometric display is the same as standard display, the only thing that has changed is the direction of the x and y axis. Normally the x axis is defined as (1,0) one unit across and zero down and the y axis is (0,1) zero units across and one down. For isometric (strictly speaking your image is a dimetric projection) you will have something like x axis (0.5,1) and y axis (-1,0.5)
The Matrix
From this you can create a rendering matrix with 6 values Two each for both axes and two for the origin, which I will ignore for now (the origin) and just use the 4 for the axis and assume that the origin is always at 0,0
var dimetricMatrix = [0.5,1.0,-1,0.5]; // x and y axis
Matrix transformation
From that you can get a point on the display that matches a given isometric coordinate. Lets say the blocks are 200 by 200 pixels and that you address each block by the block x and y. Thus the block in the bottom of your image is at x = 2 and y = 1 (the first top block is x = 0, y = 0)
Using the matrix we can get the pixel location of the block
var blockW = 200;
var blockH = 200;
var locX = 2;
var locY = 1;
function getLoc(x,y){
var xx,yy; // intermediate results
var m = dimetricMatrix; // short cut to make code readable
x *= blockW; // scale up
y *= blockH;
// now move along the projection x axis
xx = x * m[0];
yy = x * m[1];
// then add the distance along the y axis
xx += y * m[2];
yy += y * m[3];
return {x : xx, y : yy};
}
Befoer I move on you can see that I have scaled the x and y by the block size. We can simplify the above code and include the scale 200,200 in the matrix
var xAxis = [0.5, 1.0];
var yAxis = [-1, 0.5];
var blockW = 200;
var blockH = 200;
// now create the matrix and scale the x and y axis
var dimetricMatrix = [
xAxis[0] * blockW,
xAxis[1] * blockW,
yAxis[0] * blockH,
yAxis[1] * blockH,
]; // x and y axis
The matrix holds the scale in the x and y axis so that the two numbers for x axis tell us the direction and length of a transformed unit.
Simplify function
And redo the getLoc function for speed and efficiency
function transformPoint(point,matrix,result){
if(result === undefined){
result = {};
}
// now move along the projection x axis
result.x = point.x * matrix[0] + point.y * matrix[2];
result.y = point.x * matrix[1] + point.y * matrix[3];
return result;
}
So pass a point and get a transformed point back. The result argument allows you to pass an existing point and that saves having to allocate a new point if you are doing it often.
var point = {x : 2, y : 1};
var screen = transformPoint(point,dimetricMatrix);
// result is the screen location of the block
// next time
screen = transformPoint(point,dimetricMatrix,screen); // pass the screen obj
// to avoid those too
// GC hits that kill
// game frame rates
Inverting the Matrix
All that is handy but you need the reverse of what we just did. Luckily the way matrices work allows us to reverse the process by inverting the matrix.
function invertMatrix(matrix){
var m = matrix; // shortcut to make code readable
var rm = [0,0,0,0]; // resulting matrix
// get the cross product of the x and y axis. It is the area of the rectangle made by the
// two axis
var cross = m[0] * m[3] - m[1] * m[2]; // I call it the cross but most will call
// it the determinate (I think that cross
// product is more suited to geometry while
// determinate is for maths geeks)
rm[0] = m[3] / cross; // invert both axis and unscale (if cross is 1 then nothing)
rm[1] = -m[1] / cross;
rm[2] = -m[2] / cross;
rm[3] = m[0] / cross;
return rm;
}
Now we can invert our matrix
var dimetricMatrixInv = invertMatrix(dimetricMatrix); // get the invers
And now that we have the inverse matrix we can use the transform function to convert from a screen location to a block location
var screen = {x : 100, y : 200};
var blockLoc = transformPoint(screen, dimetricMatrixInv );
// result is the location of the block
The Matrix for rendering
For a bit of magic the transformation matrix dimetricMatrix can also be used by the 2D canvas, but you need to add the origin.
var m = dimetricMatrix;
ctx.setTransform(m[0], m[1], m[2], m[3], 0, 0); // assume origin at 0,0
Now you can draw a box around the block with
ctx.strokeRect(2,1,1,1); // 3rd by 2nd block 1 by 1 block wide.
The origin
I have left out the origin in all the above, I will leave that up to you to find as there is a trillion pages online about matrices as all 2D and 3D rendering use them and getting a good deep knowledge of them is important if you wish to get into computer visualization.

Leaflet calculating meters per pixel at zoom level

I am trying to determine a way to calculate the number of meters represented by 1 pixel at a given zoom level and geo centerpoint in Leaflet. Could anyone direct me to the math involved or if there is a way to do this out of the box in leaflet? I am not finding much out there.
You can use the containerPointToLatLng conversion function of L.Map to get the latLngcoordinates for a given pixelcoordinate. If you take one of the first pixel, and one of the next, you can use the distanceTo utility method of L.LatLng to calculate the distance in meters between them. See the following code (assuming map is an instance of L.Map):
var centerLatLng = map.getCenter(); // get map center
var pointC = map.latLngToContainerPoint(centerLatLng); // convert to containerpoint (pixels)
var pointX = [pointC.x + 1, pointC.y]; // add one pixel to x
var pointY = [pointC.x, pointC.y + 1]; // add one pixel to y
// convert containerpoints to latlng's
var latLngC = map.containerPointToLatLng(pointC);
var latLngX = map.containerPointToLatLng(pointX);
var latLngY = map.containerPointToLatLng(pointY);
var distanceX = latLngC.distanceTo(latLngX); // calculate distance between c and x (latitude)
var distanceY = latLngC.distanceTo(latLngY); // calculate distance between c and y (longitude)
That should work, thanks to Jarek PiĆ³rkowski for pointing my mistake before the edit.
You can use this to work out the metres per pixel:
metresPerPixel = 40075016.686 * Math.abs(Math.cos(map.getCenter().lat * Math.PI/180)) / Math.pow(2, map.getZoom()+8);
Take a look at openstreetmap.org page on zoom levels. It gives this formula for calculating the meters per pixel:
The distance represented by one pixel (S) is given by
S=C*cos(y)/2^(z+8) where...
C is the (equatorial) circumference of the Earth
z is the zoom level
y is the latitude of where you're interested in the scale.
Correct me if I am wrong, IMHO, the number of meters per pixel = map height in meters / map height in pixels
function metresPerPixel() {
const southEastPoint = map.getBounds().getSouthEast();
const northEastPoint = map.getBounds().getNorthEast();
const mapHeightInMetres = southEastPoint.distanceTo(northEastPoint);
const mapHeightInPixels = map.getSize().y;
return mapHeightInMetres / mapHeightInPixels;
}

Categories

Resources