Snap circle to a polyline in Leaflet - javascript

I have a polyline in leaflet on which I want to snap center a circle with which I want to move among the polyline so the circle is always at the center of the polyline.
Is there a way how to snap center of circle into center so when I move the circle it always centers on the polyline?

I've made a fiddle, where the circle is moved just on mousemove event: http://jsfiddle.net/v0bseuqz/32/
The main idea is to create a line (when mouse is moved) that strokes cursor coordinates from the top of the map to the bottom, and check if it intersects the polyline to which the circle should be snapped. It they intersect, their intersection should be new centre of the circle.
document.onload = loadMap();
function loadMap() {
var map = L.map('map').setView([0, 0], 12);
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpandmbXliNDBjZWd2M2x6bDk3c2ZtOTkifQ._QA7i5Mpkd_m30IGElHziw', {
maxZoom: 18,
id: 'mapbox.streets',
accessToken: 'pk.eyJ1IjoiZW======V6ZTdlb2V5cyJ9.3HqHQ4BMRvSPaYe8ToA7YQ'
}).addTo(map);
L.polyline([
[-50, 1000],
[0, 0]
], {
color: 'red',
weight: 1
}).addTo(map);
var circle = L.circle([0, 0], 500, {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5
}).addTo(map);
$( "#map" ).mousemove(function( event ) {
var cursorPoint = new L.Point(event.clientX, event.clientY);
var cursorLatLng = map.containerPointToLatLng(cursorPoint);
var intersection = (checkLineIntersection(0, 0, 1000, -50, cursorLatLng.lng, 1, cursorLatLng.lng, -1));
if (intersection.onLine1 && intersection.onLine2) {
circle.setLatLng(new L.LatLng(intersection.y, intersection.x));
}
});
}
function checkLineIntersection(line1StartX, line1StartY, line1EndX, line1EndY, line2StartX, line2StartY, line2EndX, line2EndY) {
// if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point
var denominator, a, b, numerator1, numerator2, result = {
x: null,
y: null,
onLine1: false,
onLine2: false
};
denominator = ((line2EndY - line2StartY) * (line1EndX - line1StartX)) - ((line2EndX - line2StartX) * (line1EndY - line1StartY));
if (denominator == 0) {
return result;
}
a = line1StartY - line2StartY;
b = line1StartX - line2StartX;
numerator1 = ((line2EndX - line2StartX) * a) - ((line2EndY - line2StartY) * b);
numerator2 = ((line1EndX - line1StartX) * a) - ((line1EndY - line1StartY) * b);
a = numerator1 / denominator;
b = numerator2 / denominator;
// if we cast these lines infinitely in both directions, they intersect here:
result.x = line1StartX + (a * (line1EndX - line1StartX));
result.y = line1StartY + (a * (line1EndY - line1StartY));
/*
// it is worth noting that this should be the same as:
x = line2StartX + (b * (line2EndX - line2StartX));
y = line2StartX + (b * (line2EndY - line2StartY));
*/
// if line1 is a segment and line2 is infinite, they intersect if:
if (a > 0 && a < 1) {
result.onLine1 = true;
}
// if line2 is a segment and line1 is infinite, they intersect if:
if (b > 0 && b < 1) {
result.onLine2 = true;
}
// if line1 and line2 are segments, they intersect if both of the above are true
return result;
};
UPDATE
If you have a polyline that is defined by more than two points, you should check intersection of it's every segment with the perpendicular line. Jut do it in a loop. Here is the fiddle: http://jsfiddle.net/v0bseuqz/39/
var coords = [
[0.003, 0.080],
[-0.008, 0.041],
[0, 0]
];
L.polyline(coords, {
color: 'red',
weight: 1
}).addTo(map);
var circle = L.circle([0, 0], 500, {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5
}).addTo(map);
var startY, startX, endY, endX, cursorPoint, cursorLatLng, intersection;
$( "#map" ).mousemove(function( event ) {
for(i = coords.length, i >0; i--;) {
if (i - 1 >= 0) {
startY = coords[i][0];
startX = coords[i][1];
endY = coords[i - 1][0];
endX = coords[i - 1][1];
cursorPoint = new L.Point(event.clientX, event.clientY);
cursorLatLng = map.containerPointToLatLng(cursorPoint);
intersection = (checkLineIntersection(startX, startY, endX, endY, cursorLatLng.lng, 1, cursorLatLng.lng, -1));
if (intersection.onLine1 && intersection.onLine2) {
circle.setLatLng(new L.LatLng(intersection.y, intersection.x));
}
}
}
});
I've taken the algorithm of detecting the intersection of two lines here.

Related

Intersection between 2 circles javascript

I’m trying to do a function to detect intersections between two circles. If yes it scores true, otherwise it scores false, but I think I got lost so it does not display what I want. If anyone can help me please . Thank you
Surely I have incorrectly coded in javascript if there is a person who knows the answer I am all ears
function AreCirclesIntersecting(c0,c1) {
x0 = c0['center']['x'];
y0 = c0['center']['y'];
r0 = c0['center']['r'];
x1 = c1['center']['x'];
y1 = c1['center']['y'];
r1 = c1['center']['r'];
var a, dx, dy, d, h, rx, ry;
var x2, y2;
/* dx and dy are the vertical and horizontal distances between
* the circle centers.
*/
dx = x1 - x0;
dy = y1 - y0;
/* Determine the straight-line distance between the centers. */
d = Math.sqrt((dy*dy) + (dx*dx));
/* Check for solvability. */
if (d > (r0 + r1)) {
/* no solution. circles do not intersect. */
return false;
}
if (d < Math.abs(r0 - r1)) {
/* no solution. one circle is contained in the other */
return false;
}
/* 'point 2' is the point where the line through the circle
* intersection points crosses the line between the circle
* centers.
*/
/* Determine the distance from point 0 to point 2. */
a = ((r0*r0) - (r1*r1) + (d*d)) / (2.0 * d) ;
/* Determine the coordinates of point 2. */
x2 = x0 + (dx * a/d);
y2 = y0 + (dy * a/d);
/* Determine the distance from point 2 to either of the
* intersection points.
*/
h = Math.sqrt((r0*r0) - (a*a));
/* Now determine the offsets of the intersection points from
* point 2.
*/
rx = -dy * (h/d);
ry = dx * (h/d);
/* Determine the absolute intersection points. */
var xi = x2 + rx;
var xi_prime = x2 - rx;
var yi = y2 + ry;
var yi_prime = y2 - ry;
return [xi, xi_prime, yi, yi_prime];
}
const circles = [
{center: {x: 10.0, y: 10.0}, radius: 5.0},
{center: {x: 20.0, y: 20.0}, radius: 15.0},
{center: {x: 20.0, y: 10.0}, radius: 5.0},
{center: {x: 20.0, y: 25.0}, radius: 7.5},
];
const q7_result1 = AreCirclesIntersecting(circles[0], circles[1]);
console.log(q7_result1); // Expected output: true
const q7_result2 = AreCirclesIntersecting(circles[0], circles[2]);
console.log(q7_result2); // Expected output: true
const q7_result3 = AreCirclesIntersecting(circles[1], circles[3]);
console.log(q7_result3); // Expected output: false
const q7_result4 = AreCirclesIntersecting(circles[2], circles[3]);
console.log(q7_result4); // Expected output: false
I can't speak to the math, but there's an issue in how you're retrieving your r values. in circles they're radius directly on the object, but you're trying to retrieve them with ['center']['r']. I modified your code and it's outputing results now:
function AreCirclesIntersecting(c0,c1) {
x0 = c0['center']['x'];
y0 = c0['center']['y'];
r0 = c0['radius']; // MODIFICATION HERE
x1 = c1['center']['x'];
y1 = c1['center']['y'];
r1 = c1['radius']; // MODIFICATION HERE
var a, dx, dy, d, h, rx, ry;
var x2, y2;
/* dx and dy are the vertical and horizontal distances between
* the circle centers.
*/
dx = x1 - x0;
dy = y1 - y0;
/* Determine the straight-line distance between the centers. */
d = Math.sqrt((dy*dy) + (dx*dx));
/* Check for solvability. */
if (d > (r0 + r1)) {
/* no solution. circles do not intersect. */
return false;
}
if (d < Math.abs(r0 - r1)) {
/* no solution. one circle is contained in the other */
return false;
}
/* 'point 2' is the point where the line through the circle
* intersection points crosses the line between the circle
* centers.
*/
/* Determine the distance from point 0 to point 2. */
a = ((r0*r0) - (r1*r1) + (d*d)) / (2.0 * d) ;
/* Determine the coordinates of point 2. */
x2 = x0 + (dx * a/d);
y2 = y0 + (dy * a/d);
/* Determine the distance from point 2 to either of the
* intersection points.
*/
h = Math.sqrt((r0*r0) - (a*a));
/* Now determine the offsets of the intersection points from
* point 2.
*/
rx = -dy * (h/d);
ry = dx * (h/d);
/* Determine the absolute intersection points. */
var xi = x2 + rx;
var xi_prime = x2 - rx;
var yi = y2 + ry;
var yi_prime = y2 - ry;
return [xi, xi_prime, yi, yi_prime];
}
const circles = [
{center: {x: 10.0, y: 10.0}, radius: 5.0},
{center: {x: 20.0, y: 20.0}, radius: 15.0},
{center: {x: 20.0, y: 10.0}, radius: 5.0},
{center: {x: 20.0, y: 25.0}, radius: 7.5},
];
const q7_result1 = AreCirclesIntersecting(circles[0], circles[1]);
console.log(q7_result1); // Expected output: true
const q7_result2 = AreCirclesIntersecting(circles[0], circles[2]);
console.log(q7_result2); // Expected output: true
const q7_result3 = AreCirclesIntersecting(circles[1], circles[3]);
console.log(q7_result3); // Expected output: false
const q7_result4 = AreCirclesIntersecting(circles[2], circles[3]);
console.log(q7_result4); // Expected output: false
// draw the circles for debugging
(() => {
const canvas = document.createElement('canvas');
canvas.width = canvas.height = '100';
document.body.append(canvas);
const context = canvas.getContext('2d');
for (const {center: {x,y},radius :r} of circles) {
context.beginPath();
context.arc(x,y,r,0, 2 * Math.PI);
context.stroke();
}
})()
Javascript natively offers a hypothenus function,
useful here to calculate the distance between 2 points on a 2 D system
const circles =
[ { center: { x: 10.0, y: 10.0 } , radius: 5.0 }
, { center: { x: 20.0, y: 20.0 } , radius: 15.0 }
, { center: { x: 20.0, y: 10.0 } , radius: 5.0 }
, { center: { x: 20.0, y: 25.0 } , radius: 7.5 }
]
function AreCirclesIntersecting(c0,c1)
{
let delta_x = Math.abs(c0.center.x - c1.center.x)
, delta_y = Math.abs(c0.center.y - c1.center.y)
, dist = Math.hypot(delta_x, delta_y)
, out = (dist > (c0.radius + c1.radius) )
, c0_in_c1 = !out && ( c1.radius > (c0.radius + dist ))
, c1_in_c0 = !out && ( c0.radius > (c1.radius + dist ))
;
return !(out || c0_in_c1 || c1_in_c0)
}
function testing (name, expected, indx_a, indx_b )
{
let test = AreCirclesIntersecting(circles[indx_a], circles[indx_b])
, divTest = document.createElement('div')
, c0 = circles[indx_a]
, c1 = circles[indx_b]
;
divTest.className = 'test'
divTest.innerHTML = `
<p>
<strong>${name}</strong> <br>
${JSON.stringify(circles[indx_a]) } green <br>
${JSON.stringify(circles[indx_b]) } red <br><br>
expected: ${expected}<br>result:<strong>${test}</strong><br>sucess: ${(expected===test)? '✅':'❌'}
</p>
<svg viewBox="0 0 50 40" xmlns="http://www.w3.org/2000/svg" width="200" heigth="160">
<circle cx="${c0.center.x}" cy="${c0.center.y}" r="${c0.radius}" fill="none" stroke="green"/>
<circle cx="${c1.center.x}" cy="${c1.center.y}" r="${c1.radius}" fill="none" stroke="red"/>
</svg>`
document.body.appendChild(divTest)
}
testing('q7_result1',true, 0,1)
testing('q7_result2',true, 0,2)
testing('q7_result3',false,1,3)
testing('q7_result4',false,2,3)
body {
font-family: Arial, Helvetica, sans-serif;
font-size: 14px;
}
div.test {
display : inline-block;
margin : .5em;
border : 1px solid grey;
padding : 1em;
width : 400px;
}
div.test p { margin: .3em; }
You could do like this (check the interactive demo):
document.addEventListener('keydown', (function () {
function getCircleGeometry(circle) {
const {
top: circleTop,
left: circleLeft,
right: circleRight,
bottom: circleBottom,
} = circle.getBoundingClientRect();
return {
circleRadius: (circleBottom - circleTop) / 2,
circleCenterX: (circleLeft + circleRight) / 2,
circleCenterY: (circleTop + circleBottom) / 2
}
}
function collision(circle1, circle2) {
const {
circleRadius: circle1Radius,
circleCenterX: circle1CenterX,
circleCenterY: circle1CenterY
} = getCircleGeometry(circle1);
const {
circleRadius: circle2Radius,
circleCenterX: circle2CenterX,
circleCenterY: circle2CenterY
} = getCircleGeometry(circle2);
const deltaX = circle1CenterX - circle2CenterX;
const deltaY = circle1CenterY - circle2CenterY;
// Is the distance between the centers less than the sum of the radii
return deltaX ** 2 + deltaY ** 2 < (circle1Radius + circle2Radius) ** 2;
}
const circle1 = document.getElementById('circle1');
const circle2 = document.getElementById('circle2');
let circleTop = 26;
let circleLeft = 8;
return function (e) {
e.preventDefault();
switch(e.keyCode) {
case 37: // left
circleLeft -= 1;
break;
case 38: // up
circleTop -= 1;
break;
case 39: // right
circleLeft += 1;
break;
case 40: // down
circleTop += 1;
break;
}
circle1.style.top = `${circleTop}px`;
circle1.style.left = `${circleLeft}px`;
circle1.style['background-color'] = circle2.style['background-color'] = collision(circle1, circle2) ? 'red' : 'blue';
}
})());
.circle {
position: absolute;
border-radius: 50%;
height: 20px;
width: 20px;
}
<div>Use the keyboard arrows to move the circle</div>
<div class="circle" id="circle1" style="background-color: red;"></div>
<div class="circle" id="circle2" style="background-color: red;"></div>
This is a link only answer because there is a great answer to this question on the Stack Overflow Mathematics site.
In a comment to the answer on Mathematics, contributor Jared Updike posted a link to a gist he wrote for a JavaScript function intersectTwoCircles to provide the intersection points of two circles. Gist comments contain ports of the code into several other languages.
While the parameter formats of the intersectTwoCircles and the posted AreCirclesIntersecting functions differ slightly, they appear to be returning the same information.
According to a staff answer about the licensing of gists on Github:
If you set your pages and repositories to be viewed publicly, you grant each User of GitHub a nonexclusive, worldwide license to use, display, and perform Your Content through the GitHub Service and to reproduce Your Content solely on GitHub as permitted through GitHub’s functionality (for example, through forking).
which, along with the authors comment on Mathematica, seems to indicate while you are free to use it as you see fit, I am not licensed to display a copy of the code here.
As a matter of etiquette (and required under many open source software licenses) please provide attribution of the source of intersectionTwoCircles if you use it.

Sort points in counter-clockwise in javascript

I have a set of points introduced into a canvas:
My canvas with set of points
I have to apply this algorithm on:
Algo NoObtuse and example of graph produced by this algo
My problem is to find, starting from the rightmost point, the following point in the counter-clockwise order (point 2 in algo).
How, then, can we find the following point in this direction each time starting from a point?
EDIT: -> Result of the code by Blindman67
//First points (before sort and anti-clockwise)
//(6) [Point, Point, Point, Point, Point, Point]
0: Point {x: 458, y: 249, col: "red"}
1: Point {x: 333, y: 40, col: "red"}
2: Point {x: 138, y: 111, col: "red"}
3: Point {x: 336, y: 209, col: "red"}
4: Point {x: 237, y: 251, col: "red"}
5: Point {x: 60, y: 351, col: "red"}
//Points after sort and anti-clockwise
//(6) [Point, Point, Point, Point, Point, Point]
0: Point {x: 336, y: 209, col: "red", angle: 6.456745983859364}
1: Point {x: 333, y: 40, col: "red", angle: 5.156558533568968}
2: Point {x: 138, y: 111, col: "red", angle: 3.75120843247896}
3: Point {x: 60, y: 351, col: "red", angle: 2.4782921522301162}
4: Point {x: 237, y: 251, col: "red", angle: 1.9481922940313214}
5: Point {x: 458, y: 249, col: "red", angle: 0.26263427391514854}
Sorting points in rotational order
To sort points in some direction starting at the right most and using the spatial center as a reference point.
// Array of points;
const points = [{x:?,y:?},{x:?,y:?},{x:?,y:?},...?];
// Find min max to get center
// Sort from top to bottom
points.sort((a,b)=>a.y - b.y);
// Get center y
const cy = (points[0].y + points[points.length -1].y) / 2;
// Sort from right to left
points.sort((a,b)=>b.x - a.x);
// Get center x
const cx = (points[0].x + points[points.length -1].x) / 2;
// Center point
const center = {x:cx,y:cy};
// Pre calculate the angles as it will be slow in the sort
// As the points are sorted from right to left the first point
// is the rightmost
// Starting angle used to reference other angles
var startAng;
points.forEach(point => {
var ang = Math.atan2(point.y - center.y,point.x - center.x);
if(!startAng){ startAng = ang }
else {
if(ang < startAng){ // ensure that all points are clockwise of the start point
ang += Math.PI * 2;
}
}
point.angle = ang; // add the angle to the point
});
// Sort clockwise;
points.sort((a,b)=> a.angle - b.angle);
UPDATE correction
// ****************************************************
// UPDATE the following code is incorrect
// ****************************************************
// Sort anti clockwise;
// points.sort((a,b)=> b.angle - a.angle);
// ****************************************************
//=====================================================
// the correct way to sort anticlockwise
//=====================================================
// first sort clockwise
points.sort((a,b)=> a.angle - b.angle);
// then reverse the order
const ccwPoints = points.reverse();
// move the last point back to the start
ccwPoints.unshift(ccwPoints.pop());
Example
Click canvas to rerun on a new set of random points sorted in counter clockwise order.
//.......................................................
// support code not part of the answer
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); }; // the ; after while loop is important don't remove
const setOf = (count, cb = (i)=>i) => {var a = [],i = 0; while (i < count) { a.push(cb(i ++)) } return a };
const eachOf = (array, cb) => { var i = 0; const len = array.length; while (i < len && cb(array[i], i++, len) !== true ); };
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
//.......................................................
// set up canvas and context
const ctx = canvas.getContext("2d");
canvas.width = 500;
canvas.height = 250;
ctx.font = "12px airal";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// create random points and then sort them in counterclockwise order
// starting at the right most
function doIt() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
function drawPoints() {
eachOf(points, (point, i) => {
ctx.beginPath();
ctx.lineTo(center.x, center.y);
ctx.lineTo(point.x, point.y);
ctx.stroke();
ctx.fillStyle = "white"
ctx.fillText(i, point.x-2, point.y);
ctx.fillText(i, point.x+2, point.y);
ctx.fillText(i, point.x, point.y-2);
ctx.fillText(i, point.x, point.y+2);
ctx.fillStyle = "black"
ctx.fillText(i, point.x, point.y);
})
}
// Array of points;
var points = setOf(8, () => ({
x : rand(20, canvas.width - 40),
y : rand(20, canvas.height - 40),
angle : 0
}));
// Find min max to get center
// Sort from top to bottom
points.sort((a, b) => a.y - b.y);
// Get center y
const cy = (points[0].y + points[points.length - 1].y) / 2;
// Sort from right to left
points.sort((a, b) => b.x - a.x);
// Get center x
const cx = (points[0].x + points[points.length - 1].x) / 2;
// Center point
var center = {
x : cx,
y : cy
};
// Pre calculate the angles as it will be slow in the sort
// As the points are sorted from right to left the first point
// is the rightmost
// Starting angle used to reference other angles
var startAng;
points.forEach(point => {
var ang = Math.atan2(point.y - center.y, point.x - center.x);
if (!startAng) {
startAng = ang
} else {
if (ang < startAng) { // ensure that all points are clockwise of the start point
ang += Math.PI * 2;
}
}
point.angle = ang; // add the angle to the point
});
// first sort clockwise
points.sort((a, b) => a.angle - b.angle);
// then reverse the order
const ccwPoints = points.reverse();
// move the last point back to the start
ccwPoints.unshift(ccwPoints.pop());
drawPoints();
}
doIt()
canvas.onclick = doIt;
canvas { border : 2px solid black; }
<canvas id="canvas"></canvas>

Binning data into a hexagonal grid in Google Maps

I'm trying to display geospatial data in a hexagonal grid on a Google Map.
In order to do so, given a hexagon tile grid size X I need to be able to convert ({lat, lng}) coordinates into the ({lat, lng}) centers of the hexagon grid tiles that contain them.
In the end, I would like to be able to display data on a Google Map like this:
Does anybody have any insight into how this is done?
I've tried porting this Python hexagon binning script, binner.py to Javascript but it doesn't seem to be working properly- the output values are all the same as the input ones.
For the sake of this example, I don't care if there are multiple polygons in a single location, I just need to figure out how to bin them into the correct coordinates.
Code below, (Plunker here!)
var map;
var pointCount = 0;
var locations = [];
var gridWidth = 200000; // hex tile size in meters
var bounds;
var places = [
[44.13, -69.51],
[45.23, -67.42],
[46.33, -66.53],
[44.43, -65.24],
[46.53, -64.15],
[44.63, -63.06],
[44.73, -62.17],
[43.83, -63.28],
[44.93, -64.39],
[44.13, -65.41],
[41.23, -66.52],
[44.33, -67.63],
[42.43, -68.74],
[44.53, -69.65],
[40.63, -70.97],
]
var SQRT3 = 1.73205080756887729352744634150587236;
$(document).ready(function(){
bounds = new google.maps.LatLngBounds();
map = new google.maps.Map(document.getElementById("map_canvas"), {center: {lat: 0, lng: 0}, zoom: 2});
// Adding a marker just so we can visualize where the actual data points are.
// In the end, we want to see the hex tile that contain them
places.forEach(function(place, p){
latlng = new google.maps.LatLng({lat: place[0], lng: place[1]});
marker = new google.maps.Marker({position: latlng, map: map})
// Fitting to bounds so the map is zoomed to the right place
bounds.extend(latlng);
});
map.fitBounds(bounds);
// Now, we draw our hexagons! (or try to)
locations = makeBins(places);
locations.forEach(function(place, p){
drawHorizontalHexagon(map, place, gridWidth);
})
});
function drawHorizontalHexagon(map,position,radius){
var coordinates = [];
for(var angle= 0;angle < 360; angle+=60) {
coordinates.push(google.maps.geometry.spherical.computeOffset(position, radius, angle));
}
// Construct the polygon.
var polygon = new google.maps.Polygon({
paths: coordinates,
position: position,
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.35,
geodesic: true
});
polygon.setMap(map);
}
// Below is my attempt at porting binner.py to Javascript.
// Source: https://github.com/coryfoo/hexbins/blob/master/hexbin/binner.py
function distance(x1, y1, x2, y2){
console.log(x1, y1, x2, y2);
result = Math.sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)));
console.log("Distance: ", result);
return result;
}
function nearestCenterPoint(value, scale){
div = (value / (scale / 2));
mod = value % (scale / 2);
if(div % 2 == 1){
increment = 1;
} else {
increment = 0;
}
rounded = (scale / 2) * (div + increment);
if(div % 2 === 0){
increment = 1;
} else {
increment = 0;
}
rounded_scaled = (scale / 2) * (div + increment)
result = [rounded, rounded_scaled];
return result;
}
function makeBins(data){
bins = [];
data.forEach(function(place, p){
x = place[0];
y = place[1];
console.log("Original location:", x, y);
px_nearest = nearestCenterPoint(x, gridWidth);
py_nearest = nearestCenterPoint(y, gridWidth * SQRT3);
z1 = distance(x, y, px_nearest[0], py_nearest[0]);
z2 = distance(x, y, px_nearest[1], py_nearest[1]);
console.log(z1, z2);
if(z1 > z2){
bin = new google.maps.LatLng({lat: px_nearest[0], lng: py_nearest[0]});
console.log("Final location:", px_nearest[0], py_nearest[0]);
} else {
bin = new google.maps.LatLng({lat: px_nearest[1], lng: py_nearest[1]});
console.log("Final location:", px_nearest[1], py_nearest[1]);
}
bins.push(bin);
})
return bins;
}
Use google.maps.geometry.poly.containsLocation.
for (var i = 0; i < hexgrid.length; i++) {
if (google.maps.geometry.poly.containsLocation(place, hexgrid[i])) {
if (!hexgrid[i].contains) {
hexgrid[i].contains = 0;
}
hexgrid[i].contains++
}
}
Example based off this related question: How can I make a Google Maps API v3 hexagon tiled map, preferably coordinate-based?. The number in the white box in the center of each hexagon is the number of markers contained by it.
proof of concept fiddle
code snippet:
var map = null;
var hexgrid = [];
function initMap() {
var myOptions = {
zoom: 8,
center: new google.maps.LatLng(43, -79.5),
mapTypeControl: true,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
},
navigationControl: true,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
map = new google.maps.Map(document.getElementById("map"),
myOptions);
createHexGrid();
var bounds = new google.maps.LatLngBounds();
// Seed our dataset with random locations
for (var i = 0; i < hexgrid.length; i++) {
var hexbounds = new google.maps.LatLngBounds();
for (var j = 0; j < hexgrid[i].getPath().getLength(); j++) {
bounds.extend(hexgrid[i].getPath().getAt(j));
hexbounds.extend(hexgrid[i].getPath().getAt(j));
}
hexgrid[i].bounds = hexbounds;
}
var span = bounds.toSpan();
var locations = [];
for (pointCount = 0; pointCount < 50; pointCount++) {
place = new google.maps.LatLng(Math.random() * span.lat() + bounds.getSouthWest().lat(), Math.random() * span.lng() + bounds.getSouthWest().lng());
bounds.extend(place);
locations.push(place);
var mark = new google.maps.Marker({
map: map,
position: place
});
// bin points in hexgrid
for (var i = 0; i < hexgrid.length; i++) {
if (google.maps.geometry.poly.containsLocation(place, hexgrid[i])) {
if (!hexgrid[i].contains) {
hexgrid[i].contains = 0;
}
hexgrid[i].contains++
}
}
}
// add labels
for (var i = 0; i < hexgrid.length; i++) {
if (typeof hexgrid[i].contains == 'undefined') {
hexgrid[i].contains = 0;
}
var labelText = "<div style='background-color:white'>" + hexgrid[i].contains + "</div>";
var myOptions = {
content: labelText,
boxStyle: {
border: "1px solid black",
textAlign: "center",
fontSize: "8pt",
width: "20px"
},
disableAutoPan: true,
pixelOffset: new google.maps.Size(-10, 0),
position: hexgrid[i].bounds.getCenter(),
closeBoxURL: "",
isHidden: false,
pane: "floatPane",
enableEventPropagation: true
};
var ibLabel = new InfoBox(myOptions);
ibLabel.open(map);
}
}
function createHexGrid() {
// === Hexagonal grid ===
var point = new google.maps.LatLng(42, -78.8);
map.setCenter(point);
var hex1 = google.maps.Polygon.RegularPoly(point, 25000, 6, 90, "#000000", 1, 1, "#00ff00", 0.5);
hex1.setMap(map);
var d = 2 * 25000 * Math.cos(Math.PI / 6);
hexgrid.push(hex1);
var hex30 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 30), 25000, 6, 90, "#000000", 1, 1, "#00ffff", 0.5);
hex30.setMap(map);
hexgrid.push(hex30);
var hex90 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 90), 25000, 6, 90, "#000000", 1, 1, "#ffff00", 0.5);
hex90.setMap(map);
hexgrid.push(hex90);
var hex150 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 150), 25000, 6, 90, "#000000", 1, 1, "#00ffff", 0.5);
hex150.setMap(map);
hexgrid.push(hex150);
var hex210 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 210), 25000, 6, 90, "#000000", 1, 1, "#ffff00", 0.5);
hex210.setMap(map);
hexgrid.push(hex210);
hex270 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 270), 25000, 6, 90, "#000000", 1, 1, "#ffff00", 0.5);
hex270.setMap(map);
hexgrid.push(hex270);
var hex330 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 330), 25000, 6, 90, "#000000", 1, 1, "#ffff00", 0.5);
hex330.setMap(map);
hexgrid.push(hex330);
var hex30_2 = google.maps.Polygon.RegularPoly(EOffsetBearing(EOffsetBearing(point, d, 30), d, 90), 25000, 6, 90, "#000000", 1, 1, "#ff0000", 0.5);
hex30_2.setMap(map);
hexgrid.push(hex30_2);
var hex150_2 = google.maps.Polygon.RegularPoly(EOffsetBearing(EOffsetBearing(point, d, 150), d, 90), 25000, 6, 90, "#000000", 1, 1, "#0000ff", 0.5);
hex150_2.setMap(map);
hexgrid.push(hex150_2);
var hex90_2 = google.maps.Polygon.RegularPoly(EOffsetBearing(EOffsetBearing(point, d, 90), d, 90), 25000, 6, 90, "#000000", 1, 1, "#00ff00", 0.5);
hex90_2.setMap(map);
hexgrid.push(hex90_2);
// This Javascript is based on code provided by the
// Community Church Javascript Team
// http://www.bisphamchurch.org.uk/
// http://econym.org.uk/gmap/
//]]>
}
google.maps.event.addDomListener(window, 'load', initMap);
// EShapes.js
//
// Based on an idea, and some lines of code, by "thetoy"
//
// This Javascript is provided by Mike Williams
// Community Church Javascript Team
// http://www.bisphamchurch.org.uk/
// http://econym.org.uk/gmap/
//
// This work is licenced under a Creative Commons Licence
// http://creativecommons.org/licenses/by/2.0/uk/
//
// Version 0.0 04/Apr/2008 Not quite finished yet
// Version 1.0 10/Apr/2008 Initial release
// Version 3.0 12/Oct/2011 Ported to v3 by Lawrence Ross
google.maps.Polygon.Shape = function(point, r1, r2, r3, r4, rotation, vertexCount, strokeColour, strokeWeight, Strokepacity, fillColour, fillOpacity, opts, tilt) {
var rot = -rotation * Math.PI / 180;
var points = [];
var latConv = google.maps.geometry.spherical.computeDistanceBetween(point, new google.maps.LatLng(point.lat() + 0.1, point.lng())) * 10;
var lngConv = google.maps.geometry.spherical.computeDistanceBetween(point, new google.maps.LatLng(point.lat(), point.lng() + 0.1)) * 10;
var step = (360 / vertexCount) || 10;
var flop = -1;
if (tilt) {
var I1 = 180 / vertexCount;
} else {
var I1 = 0;
}
for (var i = I1; i <= 360.001 + I1; i += step) {
var r1a = flop ? r1 : r3;
var r2a = flop ? r2 : r4;
flop = -1 - flop;
var y = r1a * Math.cos(i * Math.PI / 180);
var x = r2a * Math.sin(i * Math.PI / 180);
var lng = (x * Math.cos(rot) - y * Math.sin(rot)) / lngConv;
var lat = (y * Math.cos(rot) + x * Math.sin(rot)) / latConv;
points.push(new google.maps.LatLng(point.lat() + lat, point.lng() + lng));
}
return (new google.maps.Polygon({
paths: points,
strokeColor: strokeColour,
strokeWeight: strokeWeight,
strokeOpacity: Strokepacity,
fillColor: fillColour,
fillOpacity: fillOpacity
}))
}
google.maps.Polygon.RegularPoly = function(point, radius, vertexCount, rotation, strokeColour, strokeWeight, Strokepacity, fillColour, fillOpacity, opts) {
rotation = rotation || 0;
var tilt = !(vertexCount & 1);
return google.maps.Polygon.Shape(point, radius, radius, radius, radius, rotation, vertexCount, strokeColour, strokeWeight, Strokepacity, fillColour, fillOpacity, opts, tilt)
}
function EOffsetBearing(point, dist, bearing) {
var latConv = google.maps.geometry.spherical.computeDistanceBetween(point, new google.maps.LatLng(point.lat() + 0.1, point.lng())) * 10;
var lngConv = google.maps.geometry.spherical.computeDistanceBetween(point, new google.maps.LatLng(point.lat(), point.lng() + 0.1)) * 10;
var lat = dist * Math.cos(bearing * Math.PI / 180) / latConv;
var lng = dist * Math.sin(bearing * Math.PI / 180) / lngConv;
return new google.maps.LatLng(point.lat() + lat, point.lng() + lng)
}
html,
body,
#map {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px
}
<script src="https://maps.googleapis.com/maps/api/js?libraries=geometry&key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script>
<script src="https://google-maps-utility-library-v3.googlecode.com/svn/trunk/infobox/src/infobox.js"></script>
<div id="map"></div>
Take a look at deck.gl and its hexagon layer
https://deck.gl/docs/api-reference/geo-layers/h3-hexagon-layer
With the getHexagon function, you can retrieve the H3 hexagon index of each object.
Then, you can use H3's cellToLatLng function to retrieve the hexagon cell's center coordinates.
But actually, you won't need this step if you just want to create a hexagonal data grid like the one you have shown in the image. You can simply feed the data into the deck.gl hexagon layer and the framework will do the rest to produce the output you want.

KineticJS: Curved line between two draggable shapes

I am using KineticJS in my project. I need to connect two shapes using a curved line. One of the shapes can be dragged. I am able to put the curved line between shapes. The problem arises when user starts dragging the shapes. The requirement is that it should be properly curved (please refer to screen shots), irrespective of distance between them and their position with respect to each other. I am doing this:
var utils = {
_getCenter: function(x1, y1, x2, y2) {
return {
x: (x1 + x2) / 2,
y: (y1 + y2) / 2
}
},
// Converts from degrees to radians.
_radians: function(degrees) {
return degrees * Math.PI / 180;
},
// Converts from radians to degrees.
_degrees: function(radians) {
return radians * 180 / Math.PI;
}
};
function amplitude(point) {
var rad_90 = utils._radians(90);
var rad_45 = utils._radians(45);
var rad_60 = utils._radians(60);
console.log(rad_90);
return {
x: point.x * Math.cos(rad_60),
y: point.y * Math.sin(rad_60)
};
}
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Kinetic.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Kinetic.Layer();
var circle = new Kinetic.Circle({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
radius: 20,
fill: 'red',
stroke: 'black',
strokeWidth: 2
});
var attachedCircle = new Kinetic.Circle({
x: stage.getWidth() / 4,
y: stage.getHeight() / 4,
radius: 20,
fill: 'red',
stroke: 'black',
strokeWidth: 2,
draggable: true
});
var center = amplitude(utils._getCenter(circle.getX(), circle.getY(), attachedCircle.getX(), attachedCircle.getY()));
var line = new Kinetic.Line({
points: [circle.getX(), circle.getY(), center.x, center.y, attachedCircle.getX(), attachedCircle.getY()],
fill: 'black',
stroke: 'green',
strokeWidth: 3,
/*
* line segments with a length of 33px
* with a gap of 10px
*/
dash: [33, 10],
id: 'line',
tension: 0.5
});
attachedCircle.on('dragmove', function(e) {
var targetCircle = e.target;
var tempCenter = amplitude(utils._getCenter(circle.getX(), circle.getY(), targetCircle.getX(), targetCircle.getY()));
console.log(tempCenter);
line.setPoints([circle.getX(), circle.getY(), tempCenter.x, tempCenter.y, targetCircle.getX(), targetCircle.getY()]);
});
// add the shape to the layer
layer.add(line);
layer.add(attachedCircle);
layer.add(circle);
// add the layer to the stage
stage.add(layer);
I don't know what I am missing. I have created the plunkr for this.
To define you amplitude function you need to use two input points:
function amplidure2(p1, p2) {
var alpha = Math.atan((p1.x - p2.x) / (p1.y - p2.y)) + Math.PI / 2;
if (p1.y < p2.y) {
alpha += Math.PI;
}
var center = utils._getCenter(p1.x, p1.y, p2.x, p2.y);
var r = 50;
return {
x: center.x + r * Math.sin(alpha),
y: center.y + r * Math.cos(alpha)
}
}
DEMO

Draw Rectangles in Area on Google Maps

I'm drawing an area on Google Maps using the Geometry API. I want to know if it is possible to draw a repeating element onto an area that is dynamic in size?
For example, if I draw my area to look like this:
Then I want to be able to hit 'Next Step' and see something like this, with the rectangles drawn in the area, but only if they will fit. i.e., they have to be 'full' rectangles, not part rectangles:
The only problem is, I'm not entirely sure how to go about this. I would use HTML5 <canvas> but unfortunately, this needs to be as browser-friendly as possible, but if it has to be <canvas> then so be it!
Although I didn't use canvas, how about this code?
function onPolygonComplete(polygon) {
var bounds, paths, sw, ne, ystep, xstep,
boxH, boxW, posArry, flag, pos,
x, y, i, box, maxBoxCnt;
//Delete old boxes.
boxes.forEach(function(box, i) {
box.setMap(null);
delete box;
});
//Calculate the bounds that contains entire polygon.
bounds = new google.maps.LatLngBounds();
paths = polygon.getPath();
paths.forEach(function(latlng, i){
bounds.extend(latlng);
});
//Calculate the small box size.
maxBoxCnt = 8;
sw = bounds.getSouthWest();
ne = bounds.getNorthEast();
ystep = Math.abs(sw.lat() - ne.lat()) / maxBoxCnt;
boxH = Math.abs(sw.lat() - ne.lat()) / (maxBoxCnt + 1);
xstep = Math.abs(sw.lng() - ne.lng()) / maxBoxCnt;
boxW = Math.abs(sw.lng() - ne.lng()) / (maxBoxCnt + 1);
for (y = 0; y < maxBoxCnt; y++) {
for (x = 0; x < maxBoxCnt; x++) {
//Detect that polygon is able to contain a small box.
bounds = new google.maps.LatLngBounds();
posArry = [];
posArry.push(new google.maps.LatLng(sw.lat() + ystep * y, sw.lng() + xstep * x));
posArry.push(new google.maps.LatLng(sw.lat() + ystep * y, sw.lng() + xstep * x + boxW));
posArry.push(new google.maps.LatLng(sw.lat() + ystep * y + boxH, sw.lng() + xstep * x));
posArry.push(new google.maps.LatLng(sw.lat() + ystep * y + boxH, sw.lng() + xstep * x + boxW));
flag = true;
for (i = 0; i < posArry.length; i++) {
pos = posArry[i];
if (flag) {
flag = google.maps.geometry.poly.containsLocation(pos, polygon);
bounds.extend(pos);
}
}
//Draw a small box.
if (flag) {
box = new google.maps.Rectangle({
bounds : bounds,
map : mapCanvas,
strokeColor: '#00ffff',
strokeOpacity: 0.5,
strokeWeight: 1,
fillColor: '#00ffff',
fillOpacity : 0.5,
clickable: false
});
boxes.push(box);
}
}
}
}
This code works like this image.
I wrote a page that explains the code.
http://googlemaps.googlermania.com/google_maps_api_v3/en/poly_containsLocation.html
#Joshua M
Sorry for keeping you wait.
Ok, the new code is below.
You can specify the small box size at var boxSize = new google.maps.Size(10, 20);
<!DOCTYPE html>
<html>
<head>
<script src="http://maps.googleapis.com/maps/api/js?sensor=false&libraries=geometry"></script>
<script type='text/javascript'>
var mapCanvas, boxes = new google.maps.MVCArray();
function initialize() {
var mapDiv = document.getElementById("map_canvas");
mapCanvas = new google.maps.Map(mapDiv, {
center : new google.maps.LatLng(37.422191,-122.084585),
mapTypeId : google.maps.MapTypeId.SATELLITE,
zoom : 19,
tilt : 0
});
//Encoded path
var encodedPath = "eblcFnuchVv#D#q#P?a#eD]AC~#b#DCz#a#A";
var points = google.maps.geometry.encoding.decodePath(encodedPath);
//Draw a polygon
var polygonOpts = {
paths : points,
strokeWeight : 6,
strokeColor : "#FF0000",
strokeOpacity : 1,
//fillColor : "blue",
fillOpacity : 0,
map : mapCanvas,
editable : true
};
var poly = new google.maps.Polygon(polygonOpts);
var proc = function() {
onPolygonComplete(poly);
};
google.maps.event.addListener(mapCanvas, "projection_changed", proc);
google.maps.event.addListener(poly.getPath(), 'insert_at', proc);
google.maps.event.addListener(poly.getPath(), 'remove_at', proc);
google.maps.event.addListener(poly.getPath(), 'set_at', proc);
}
function onDrawMgr_complete(polygon) {
var path = polygon.getPath();
console.log(google.maps.geometry.encoding.encodePath(path));
}
function onPolygonComplete(polygon) {
var bounds, paths, sw, ne, ystep, xstep, boxH, boxW, posArry, flag, pos, x, y, i, box;
//Delete old boxes.
boxes.forEach(function(box, i) {
box.setMap(null);
delete box;
});
//Calculate the bounds that contains entire polygon.
bounds = new google.maps.LatLngBounds();
paths = polygon.getPath();
paths.forEach(function(latlng, i) {
bounds.extend(latlng);
});
var projection = mapCanvas.getProjection();
var zoom = mapCanvas.getZoom();
var powBase = Math.pow(2, zoom);
//Calculate the small box size.
sw = bounds.getSouthWest();
ne = bounds.getNorthEast();
var swPoint = projection.fromLatLngToPoint(sw);
var nePoint = projection.fromLatLngToPoint(ne);
var boxSize = new google.maps.Size(10, 20); //in pixels.
boxSize.width /= powBase;
boxSize.height /= powBase;
var maxX = Math.floor(Math.abs((swPoint.x - nePoint.x)) / boxSize.width);
var maxY = Math.floor(Math.abs((swPoint.y - nePoint.y)) / boxSize.height);
for ( y = 0; y < maxY; y++) {
for (x = 0; x < maxX; x++) {
//Detect that polygon is able to contain a small box.
bounds = new google.maps.LatLngBounds();
posArry = [];
posArry.push(new google.maps.Point(swPoint.x + boxSize.width * x, swPoint.y - boxSize.height * y));
posArry.push(new google.maps.Point(swPoint.x + boxSize.width * x, swPoint.y - boxSize.height * (y + 1)));
posArry.push(new google.maps.Point(swPoint.x + boxSize.width * (x + 1), swPoint.y - boxSize.height * y));
posArry.push(new google.maps.Point(swPoint.x + boxSize.width * (x + 1), swPoint.y - boxSize.height * (y + 1)));
var flag = true;
for (var i = 0; i < posArry.length; i++) {
pos = projection.fromPointToLatLng(posArry[i]);
if (flag) {
flag = google.maps.geometry.poly.containsLocation(pos, polygon);
bounds.extend(pos);
}
}
if (flag) {
box = new google.maps.Rectangle({
bounds : bounds,
map : mapCanvas,
strokeColor : 'green',
strokeOpacity : 1,
strokeWeight : 1,
fillColor : 'yellow',
fillOpacity : 0.5,
clickable : false
});
boxes.push(box);
}
}
}
}
google.maps.event.addDomListener(window, "load", initialize);
</script>
<style type="text/css">
window,html,#map_canvas{
width : 700px;
height : 500px;
}
</style>
</head>
<body>
<div id="map_canvas"/>
</body>
</html>

Categories