Angle of tracking compass arrow - javascript

I am currently writing a tracking app that is able to get your currently facing angle and the angle to a destination and point an arrow in the direction you need to travel. (As the crow flies)
I am having trouble taking these 2 angles and making them work together so that the arrow points in the correct direction.
My knowledge of Mathematics is extremely limited so any help would be appreciated.
function deviceOrientationListener(event) {
facing = Math.round(event.alpha);
deg = bearing(lat, lng, getUrlVars()["lat"], getUrlVars()["long"]);
finaldeg = facing - deg;
c.innerHTML = '<img style="-ms-transform: rotate(' + finaldeg + 'deg);-webkit-transform: rotate(' + finaldeg + 'deg);transform: rotate(' + finaldeg + 'deg);" src="images/arrow.png" />';
}
The finaldeg is what i am having trouble with working out.

I did like this with Ionic and the plugin cordova-plugin-device-orientation
to_turn is the variable in which I record the computed angle
degreeStyle is the style applied to the arrow shown on end-user's
screen
last_lat/last_lng is the current position of the user
target_lat/target_lng is the destination
`
this.destinationBearing = this.bearing(
this.last_lat,
this.last_lng,
this.target_lat,
this.target_lng);
this.compass = this.deviceOrientation.watchHeading().subscribe(
(data: DeviceOrientationCompassHeading) => {
this.currentHeading = data.trueHeading;
if (this.destinationBearing > this.currentHeading) {
this.to_turn = this.destinationBearing - this.currentHeading;
} else {
this.to_turn = 360 - (this.currentHeading - this.destinationBearing);
}
this.degreeStyle = 'rotate(' + this.to_turn + 'deg)';
}
);
bearing(startLat, startLng, destLat, destLng){
startLat = this.toRadians(startLat);
startLng = this.toRadians(startLng);
destLat = this.toRadians(destLat);
destLng = this.toRadians(destLng);
let y = Math.sin(destLng - startLng) * Math.cos(destLat);
let x = Math.cos(startLat) * Math.sin(destLat) -
Math.sin(startLat) * Math.cos(destLat) * Math.cos(destLng - startLng);
let brng = Math.atan2(y, x);
brng = this.toDegrees(brng);
return (brng + 360) % 360;
}
toRadians(degrees) {
return degrees * Math.PI / 180;
};
toDegrees(radians) {
return radians * 180 / Math.PI;
}

Related

CSS rotation does not work properly on ios

I'm working on a wheel of fortune project. Each time someone press "spin" button, we get the prize from an api and calculate the angle based on our target gift and then rotate. In some cases, on iphone, after rotation the result doesn't match our target gift. Therefore I'm looking for a way to double check the result with target and keep rotating if they doesn't match. here is my rotation Javascrip functions:
function startRotation(target){
let giftIndex = parseInt(target.attr('id'))
angle = 360 - ((giftIndex - 1) * (360 / wheelItems.lenght)) - ((360 / wheelItems.lenght) / 3)
angle = angle === 360 ? 0 : angle
rotate(target)}
function rotate(target) {
randomNum = angle + (1800 * ri);
ri++
$(".mainCircle").css('transform', `translate(-50%, -50%) rotate(${randomNum}deg)`)
setTimeout(() => {
winPrize(target)
}, 7500)}
and here is where I add gifts to the wheel:
function createCircle(wheelItems){
let colors = ['#16A085', '#2989B9', '#8E44AD', '#F39C12', '#D35400', '#C0392B', 'cyan', 'brown', 'gray', 'pink', 'maroon', 'gold']
let count = wheelItems.lenght
for (let i = 0; i < count; i++) {
let img = document.createElement('img')
let text = document.createElement('span')
text.innerHTML = wheelItems[i].title
img.src = '/wheel/'+wheelItems[i].title;
img.style.transform = 'skewY('+ Math.abs(90 - (360 / count)) +'deg) rotate('+ 360 / count / 2 + 'deg)'
img.style.width = (40 - ((count - 4) * 3.125)) + '%'
img.style.left = (34 - ((count - 4) * 1.75)) + '%'
img.style.paddingTop = (43 + ((count - 4) * 3.25)) + '%'
text.style.transform = 'skewY('+ Math.abs(90 - (360 / count)) +'deg) rotate('+ 360 / count / 2 + 'deg)'
text.style.width = (50 - ((count - 4) * 3.125)) + '%'
text.style.left = (25 - ((count - 4) * 1.75)) + '%'
text.style.paddingTop = (75 + ((count - 4) * 3.25)) + '%'
$('<li>')
.css({'transform': 'rotate(' + i * (360 / count) + 'deg) skewY(-' + Math.abs(90 - (360 / count)) + 'deg)', 'background' : colors[i]})
.appendTo('ul.mainCircle')
.addClass('item' + i)
.append(img)
.append(text)
}}
I already tried to check the overlap between pin element and the target gift element (<li>) but apparently the <li> elements overlap each other so the result is not always accurate. I also tried to find a way to calculate the angle difference of the wheel after rotation, but I couldn't find one that does not use the css transform rotate value.
I would really appreciate it if you can help me find a way to make sure this problem doesn't happen again.
thanks

Forcing If Condition Within Function to Run Every Time

So, I've built a quick function to change hue values of a target element in JavaScript, and it works mostly fine now, but I do have some more questions that go beyond the initial post's scope. So I'll open a new question and post them here.
Here's the code:
document.getElementById('left').style.filter = "hue-rotate(" + 20 + "deg)";
document.getElementById('right').style.filter = "hue-rotate(" + 20 + "deg)";
document.querySelectorAll('div').forEach(occurence => {
occurence.addEventListener('click', (e) => {
const filter = e.target.style.filter;
var deg = parseInt(filter.replace(/[^0-9.]/g, ""));
deg += 40;
e.target.style.filter = "hue-rotate(" + deg + "deg)";
if (deg >= 360) {deg -= 360}
console.log(e.target.id + " is " + deg + "deg");
});
});
My main question (1) is that I've coded an if statement to log the current hue value within 360º (the hue-rotate works anyway with values over +360º, but I find it to be clearer this way). However, while the check works perfectly the first time around, it stops working after the function loops once through the 360º (on subsequent loops, it goes beyond 360º).
For clarification, the statement has been positioned after the degree value is already set (and animated) so as to sidestep the quick loop animation that happens when it goes from, say, 340 to 20º (instead of going there directly, it seems to loop back through the whole hue wheel).
Also, (2) the initial hue-rotate states are defined (at the top) within the script because the function does not work otherwise, although both DIVs do have defined CSS values.
That's it! Thanks in advance!
Since the degree value on the element is always set before limiting the degrees to 360, the 2nd time it loops subtracting 360 wont be enough.
style logged value
0 0
360 (-360) 0
720 (-360) 360
etc
To limit the logged value between [0, 360], use the % operator instead
document.querySelectorAll('div').forEach(occurence => {
occurence.addEventListener('click', (e) => {
const filter = e.target.style.filter;
var deg = parseInt(filter.replace(/[^0-9.]/g, ""));
deg += 40;
e.target.style.filter = "hue-rotate(" + deg + "deg)";
deg %= 360; // deg = deg % 360
console.log(e.target.id + " is " + deg + "deg");
});
});
For (2):
To get the style of the element from css, use getComputedStyle
occurence.addEventListener('click', (e) => {
const filter = getComputedStyle(e.target).filter;
...
});
e.target.style.filter = "hue-rotate(" + deg + "deg)";
above line should be below if condition if (deg >= 360) {deg -= 360}
document.getElementById('left').style.filter = "hue-rotate(" + 20 + "deg)";
document.getElementById('right').style.filter = "hue-rotate(" + 20 + "deg)";
document.querySelectorAll('div').forEach(occurence => {
occurence.addEventListener('click', (e) => {
const filter = e.target.style.filter;
console.log(filter);
let deg = parseInt(filter.replace(/[^0-9.]/g, ""));
deg += 40;
if (deg >= 360) {
deg -= 360
}
e.target.style.filter = "hue-rotate(" + deg + "deg)";
console.log(e.target.id + " is " + deg + "deg");
});
});
<div id="left">left</div>
<div id="right">right</div>

Cesium calculate bearing rotation from 2 positions

I'm adding a plane entity to Cesium as the folowing:
let position = Cesium.Cartesian3.fromDegrees(long, lat, alt)
let planeEntity = this.viewer.entities.add({
position: position,
model: {
uri: './assets/cesium/Cesium_Air.glb',
minimumPixelSize : 64
}
});
I get plane locations at realtime, each time location arrived I do:
planeEntity.position = Cesium.Cartesian3.fromDegrees(long, lat, alt);
and move the plane to that location.
I want to rotate the plane head to the right place (if the plane flight up, the head cant stay to the left).
How can I calculate bearing rotation from 2 positions? (current position and the next position)?
I found solution here:
[Calculate bearing between 2 points with javascript
// Converts from degrees to radians.
toRadians(degrees) {
return degrees * Math.PI / 180;
}
// Converts from radians to degrees.
toDegrees(radians) {
return radians * 180 / Math.PI;
}
bearing(startLat, startLng, destLat, destLng){
startLat = this.toRadians(startLat);
startLng = this.toRadians(startLng);
destLat = this.toRadians(destLat);
destLng = this.toRadians(destLng);
let y = Math.sin(destLng - startLng) * Math.cos(destLat);
let x = Math.cos(startLat) * Math.sin(destLat) - Math.sin(startLat) * Math.cos(destLat) * Math.cos(destLng - startLng);
let brng = Math.atan2(y, x);
let brngDgr = this.toDegrees(brng);
return (brngDgr + 360) % 360;
}
Cesium way (ES6) based on larry ckey's answer:
import * as Cesium from 'cesium';
const calculateBearing = (startPoint, endPoint) => {
const start = Cesium.Cartographic.fromCartesian(startPoint);
const end = Cesium.Cartographic.fromCartesian(endPoint);
const y = Math.sin(end.longitude - start.longitude) * Math.cos(end.latitude);
const x =
Math.cos(start.latitude) * Math.sin(end.latitude) -
Math.sin(start.latitude) * Math.cos(end.latitude) *
Math.cos(end.longitude - start.longitude);
const bearing = Math.atan2(y, x);
return Cesium.Math.toDegrees(bearing);
}

Positioning image on Google Maps with rotate / scale / translate

I'm developing a user-interface for positioning an image on a google map.
I started from : http://overlay-tiler.googlecode.com/svn/trunk/upload.html which is pretty close to what I want.
But instead of 3 contact points I want a rotate tool, a scale tool and a translate tool (the later exists).
I tried to add a rotate tool but it doesn't work as I expected :
I put a dot on the left bottom corner that control the rotation (around the center of the image). The mouse drag the control dot and I calculate the 3 others points.
My code is based on the mover object but I changed the onMouseMove function :
overlaytiler.Rotater.prototype.rotateDot_ = function(dot, theta, origin) {
dot.x = ((dot.x - origin.x) * Math.cos(theta) - (dot.y - origin.y) * Math.sin(theta)) + origin.x;
dot.y = ((dot.x - origin.x) * Math.sin(theta) + (dot.y - origin.y) * Math.cos(theta)) + origin.y;
dot.render();
};
overlaytiler.Rotater.prototype.onMouseMove_ = function(e) {
var dots = this.controlDots_;
var center = overlaytiler.Rotater.prototype.getCenter_(dots);
// Diagonal length
var r = Math.sqrt(Math.pow(this.x - center.x, 2) + Math.pow(this.y - center.y, 2));
var old = {
x: this.x,
y: this.y
};
// Real position
var newPos = {
x: this.x + e.clientX - this.cx,
y: this.y + e.clientY - this.cy
}
var newR = Math.sqrt(Math.pow(newPos.x - center.x, 2) + Math.pow(newPos.y - center.y, 2));
var theta = - Math.acos((2 * r * r - (Math.pow(newPos.x - old.x, 2) + Math.pow(newPos.y - old.y, 2))) / (2 * r * r));
// Fixed distance position
this.x = (newPos.x - center.x) * (r / newR) + center.x;
this.y = (newPos.y - center.y) * (r / newR) + center.y;
dots[1].x = center.x + (center.x - this.x);
dots[1].y = center.y + (center.y - this.y);
dots[1].render();
overlaytiler.Rotater.prototype.rotateDot_(dots[2], theta, center);
overlaytiler.Rotater.prototype.rotateDot_(dots[0], theta, center);
// Render
this.render();
this.cx = e.clientX;
this.cy = e.clientY;
};
Unfortunately there is a problem with precision and angle sign.
http://jsbin.com/iQEbIzo/4/
After a few rotations the image is highly distorted and rotation is supported only in one direction.
I wonder how I can achieve a great precision and without any distortion.
Maybe my approach is useless here (try to move the corners at the right coordinates), I tried to rotate the image with the canvas but my attempts were unsuccessful.
Edit : Full working version : http://jsbin.com/iQEbIzo/7/
Here is my version of it. #efux and #Ben answers are far more complete and well designed however the maps don't scale in/out when you zoom in/out. Overlays very likely need to do this since they are used to put a "second map" or photograph over the existing map.
Here is the JSFiddle: http://jsfiddle.net/adelriosantiago/3tzzwmsx/4/
The code that does the drawing is the following:
DebugOverlay.prototype.draw = function() {
var overlayProjection = this.getProjection();
var sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
var ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
var div = this.div_;
div.style.left = sw.x + 'px';
div.style.top = ne.y + 'px';
div.style.width = (ne.x - sw.x) + 'px';
div.style.height = (sw.y - ne.y) + 'px';
div.style.transform = 'rotate(' + rot + 'deg)';
};
For sure this code could be implemented on efux and Ben code if needed but I haven't tried yet.
Note that the box marker does not updates its position when the rotation marker moves...
rotation is supported only in one direction
This is due to how you calculate the angle between two vectors.
It always gives you the same vector no matter if the mouse is right of the dot or not. I've found a solution in a german math board (unfortunately I cant access the site without using the cache of Google : cached version).
Note that in this example the angle α is on both sides the same and not as you would expect -α in the second one. To find out if the vector a is always on "the same side" of vector b you can use this formula.
ax*by - ay*bx
This is either positive or negative. You you simply can change the sign of the angle to α * -1.
I modified some parts of your code.
overlaytiler.Rotater.prototype.rotateDot_ = function(dot, theta, origin) {
// translate to origin
dot.x -= origin.x ;
dot.y -= origin.y ;
// perform rotation
newPos = {
x: dot.x*Math.cos(theta) - dot.y*Math.sin(theta),
y: dot.x*Math.sin(theta) + dot.y*Math.cos(theta)
} ;
dot.x = newPos.x ;
dot.y = newPos.y ;
// translate back to center
dot.x += origin.x ;
dot.y += origin.y ;
dot.render();
};
If you want to know, how I rotate the points please reference to this site and this one.
overlaytiler.Rotater.prototype.onMouseMove_ = function(e) {
var dots = this.controlDots_;
var center = overlaytiler.Rotater.prototype.getCenter_(dots);
// get the location of the canvas relative to the screen
var rect = new Array() ;
rect[0] = dots[0].canvas_.getBoundingClientRect() ;
rect[1] = dots[1].canvas_.getBoundingClientRect() ;
rect[2] = dots[2].canvas_.getBoundingClientRect() ;
// calculate the relative center of the image
var relCenter = {
x: (rect[0].left + rect[2].left) / 2,
y: (rect[0].top + rect[2].top) / 2
} ;
// calculate a vector from the center to the bottom left of the image
dotCorner = {
x: rect[1].left - (rect[1].left - relCenter.x) * 2 - relCenter.x,
y: rect[1].top - (rect[1].top - relCenter.y) * 2 - relCenter.y
} ;
// calculate a vector from the center to the mouse position
mousePos = {
x: e.clientX - relCenter.x,
y: e.clientY - relCenter.y
} ;
// calculate the angle between the two vector
theta = calculateAngle(dotCorner, mousePos) ;
// is the mouse-vector left of the dot-vector -> refer to the german math board
if(dotCorner.y*mousePos.x - dotCorner.x*mousePos.y > 0) {
theta *= -1 ;
}
// calculate new position of the dots and render them
overlaytiler.Rotater.prototype.rotateDot_(dots[2], theta, center);
overlaytiler.Rotater.prototype.rotateDot_(dots[1], theta, center);
overlaytiler.Rotater.prototype.rotateDot_(dots[0], theta, center);
// Render
this.render();
this.cx = e.clientX;
this.cy = e.clientY;
};
You can see that I wrote some function for vector calculations (just to make the code more readable):
function calculateScalarProduct(v1,v2)
{
return (v1.x * v2.x + v1.y * v2.y) ;
}
function calculateLength(v1)
{
return (Math.sqrt(v1.x*v1.x + v1.y*v1.y)) ;
}
function calculateAngle(v1, v2)
{
return (Math.acos(calculateScalarProduct(v1,v2) / (calculateLength(v1)*calculateLength(v2)))) ;
}
This is my working solution. Comment if you don't understand something, so I can make my answer more comprehensive.
Working example: JSBin
Wow, this was a tough one.

Firebug changes the Javascript environment of Firefox 3.6

I was doing some debugging of a site in Firefox 3.6.21 using Firebug 1.7.3. When the page first loads, the Javascript I am trying to execute doesn't load at all. I open up Firebug to see what the problem is, I hit reload, and all of a sudden it's working.
I have no idea what is going on here.
function initStations() {
//console.log("in stations")
ringContainer = $("#ringContainer");
ringWidth = ringHeight = ringContainer.innerWidth();
//console.log(ringWidth + " " + ringHeight);
originX = (ringWidth / 2) - 0;
originY = (ringHeight / 2);
radius = originY + 20;
//console.log(originX + " " + originY + " " + radius);
// get the ul containing the stations
stationList = $("#stationList");
// an array of the li elements
stationLiElems = $("#stationList li");
// how many stations
length = stationLiElems.size();
// distance between stations in degrees
spacing = (360 / length)
// 360 degrees in circle divided by the number of stations
// array of stations
stations = [];
// debug
//console.log(stationList);
stationLiElems.each(function(index, element) {
//console.log(index +" - "+ spacing);
stations[index] = {
'element' : element,
// http://stackoverflow.com/questions/925118/algorithm-for-finding-out-pixel-coordinates-on-a-circumference-of-a-circle
'x' : originX + radius * Math.sin(spacing * index * 0.0174532925 ),
'y' : originY + radius * Math.cos(spacing * index * 0.0174532925 )
}
$(element).css({'top' : stations[index].y , 'left' : stations[index].x });
});
Had this same issue, and removing console.log fixed it for me. I see you have commented it out, but check you aren't using it elsewhere.

Categories

Resources