Google Maps API V3 - Showing progress along a route - javascript

I want to show a custom route on a route along with the current progress. I've overlayed the a Polyline to show the route and I am able to find the LatLng of the current postion and place a marker. What I want to do now is to highlight the traveled part of the Polyline using a different colour. I'm pretty sure it's not possible to have multiple colours for one Polyline so I plan to overlay a second Polyline over the first the first to give this effect. Here's my question:
Knowing the LatLng of the current position and armed with the array of LatLng points used to create the orginal Polyline how best can I create the second 'Progess' route?
Thanks ahead.

With a few assumptions:
Your path is quite dense (if not, you could interpolate intermediate points)
Your route doesn't overlap with itself (wouldn't work with a repeating circular path, say)
..one crude way would be as follows:
Using Python'y pseudo-code, say you have a route like this:
points = [LatLng(1, 1), LatLng(2, 2), LatLng(3, 3), LatLng(4, 4)]
You draw this as a PolyLine as usual.
Then, given your current position, you would find the nearest point on the route:
cur_pos = LatLng(3.1, 3.0123)
nearest_latlng = closest_point(points, to = cur_pos)
Then nearest_latlng would contain LatLng(3, 3)
Find nearest_latlng in the list, then simply draw a second polyline up to this point. In other words, you truncate the points list at the current LatLng:
progress_points = [LatLng(1, 1), LatLng(2, 2), LatLng(3, 3)]
..then draw that on the map
As mentioned, this will break if the path loops back on itself (closest_point would only ever find the first or last point)
If you know how far has been travelled, there is an epoly extension which gives a few methods that could be used, mainly:
.GetIndexAtDistance(metres)
Returns the vertex number at or after the specified distance along the path.
That index could be used instead of the closest_point calculated one above

Typescript example:
getRouteProgressPoints(directionResult: google.maps.DirectionsResult | undefined, targetDistance: number) {
if (!directionResult || targetDistance <= 0) return [];
const route = directionResult.routes[0];
const leg: google.maps.DirectionsLeg = route.legs[0];
const routePoints: google.maps.LatLng[] = [];
// collect all points
leg.steps.forEach((step) => {
step.path.forEach((stepPath) => {
routePoints.push(stepPath);
});
});
let dist = 0;
let olddist = 0;
const points = [];
// go throw all points until target
for (let i = 0; i < routePoints.length && dist < targetDistance; i++) {
const currentPoint = routePoints[i];
// add first point
if (!i) {
points.push(currentPoint);
continue;
}
const prevPoint = routePoints[i - 1];
olddist = dist;
// add distance between points
dist += google.maps.geometry.spherical.computeDistanceBetween(prevPoint, currentPoint);
if (dist > targetDistance) {
const m = (targetDistance - olddist) / (dist - olddist);
const targetPoint = new google.maps.LatLng(
prevPoint.lat() + (currentPoint.lat() - prevPoint.lat()) * m,
prevPoint.lng() + (currentPoint.lng() - prevPoint.lng()) * m,
);
points.push(targetPoint);
} else {
points.push(currentPoint);
}
}
return points;
}

That is going to be really difficult unless you already have a way of determining that the vehicle has passed certain points already. If you are aware that it has passed certain points you just create the second polyline with the passed points and the current marker as the end of the polyline. You will probably want to change the width of the polyline in order to make it viewable when it is overlaying the initial route.
All of that being said if you don't know if the points have been passed I am not sure what you can do other than write some sort of code that determines if your current position is past a certain set of points, but I don't know how you do that if a route zigzags all over the place possibly..... Now if your points go from straight west to straight east etc, than coding something will be easy, but I doubt that is the case.
Hope this helps somewhat....

Related

Three JS Raycasting - Find point closest to cursor

Three.js r85
When raycasting with Three JS, a series of points is returned, and I'd like to find the point that is closest to the cursor. The first point returned seems to be the point that is closest to the camera.
Is there a way to find the distance between the cursor position and a point?
Here's the code I'm using to debug this right now:
var raygun = new THREE.Raycaster();
raygun.setFromCamera(mouse, camera);
var hits = raygun.intersectObjects([plotpoints]);
if (hits.length > 0) {
scope.remove(dotPlot);
scope.remove(dotPlot2);
// All points except the first one - Grey
dotGeo = new THREE.Geometry();
for (var i=1; i < hits.length; i++) {
dotGeo.vertices.push(plotpoints.geometry.vertices[hits[i].index]);
}
dotPlot = new THREE.Points(dotGeo, dotMat);
scope.add(dotPlot);
// First point - Orange
var geo2 = new THREE.Geometry();
geo2.vertices.push(plotpoints.geometry.vertices[hits[0].index]);
dotPlot2 = new THREE.Points(geo2, dotMat2);
scope.add(dotPlot2);
scope.render();
}
And here's what I'm seeing:
Ah, figured it out with math!
First thing to note is that hits[].points returns a point directly under the cursor, but it doesn't "snap" to points.
In order to get the actual position of the point, we need to use hits[].index first to get the index number of the point/vertex we hit. We can then access that vertex directly by using GEOMETRY.vertices[] which returns a THREE.Vector3 of the vertex point we hit with our raycast.
So by feeding in the index, we can get the exact position of each vertex hit by our raycast:
GEOMETRY.vertices[hits[i].index]
This provides rudimentary "snapping" to vertices.
Note: When using THREE.LineSegments, the result will always be the starting point, and not the ending point. To get the ending point, you can just add 1 to the index value:
GEOMETRY.vertices[hits[i+1].index]
To snap directly to the vertex closest to the cursor, we need to find the vertex that has the shortest perpendicular distance from the raycaster's ray. To do this we use a cross product of 2 vectors. This is more of a math concept than a programming concept though, so if you want to understand the why behind this, look up something like: perpendicular distance from a point to a line
I just took the code from this question and translated it: http://answers.unity3d.com/questions/568773/shortest-distance-from-a-point-to-a-vector.html
And the end result:
// Variables to record and compare
var smallestDist = 99;
var smallestPointIndex = 0;
// Declare variables outside of loop to save memory
var m_ray = raycaster.ray;
var raydir = m_ray.direction;
var origin = m_ray.origin;
var hitray = new THREE.Vector3(0,0,0);
var dist = 1;
// Loop over all points to find the closest
for (var i=0; i<hits.length; i++){
// Math is magic
hitray.subVectors(plotpoints.geometry.vertices[hits[i].index], origin);
dist = new THREE.Vector3().crossVectors(raydir, hitray).lengthSq();
// Record the closest point
if (dist < smallestDist) {
smallestDist = dist;
smallestPointIndex = i;
}
}
// Now we can use that single point
Here's the result :)

Snapping to closest Marker Method always returning false

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

Custom THREE.Curve.create displays incorrectly

As suggested in this answer, I've created a linearly interpolated curve like this:
THREE.Linear3 = THREE.Curve.create(
function ( points, label /* array of Vector3 */) {
this.points = (points == undefined) ? [] : points;
this.label = label;
},
function ( t ) {
var v = new THREE.Vector3();
var c = [];
var points = this.points, point, intPoint, weight;
point = ( points.length - 1 ) * t;
intPoint = Math.floor( point );
weight = point - intPoint;
c[ 1 ] = intPoint;
c[ 2 ] = intPoint > points.length - 2 ? points.length - 1 : intPoint + 1;
var pt1 = points[ c[1] ],
pt2 = points[ c[2] ];
v.copy( pt1 ).lerp( pt2, weight );
return v;
}
);
However, when I'm trying to display a trajectory at different lengths (in an animated kinda-way) I get the following behavior i.e. instead of the curve going through the points, it kinda cuts through the space, note that in the example below each trajectory is supposed to go through the coordinates of each of the spheres (animated gif below):
I am not sure I understand the getPoint function or what is it supposed to return. Any Help is greatly appreciated.
JSFiddle
This is a minimal example but you can see how the right corner has a jerky motion as the tube expands.
http://jsfiddle.net/ElDeveloper/3uyf3sq3/1/
Cleaning some code
That helped me investigate the issue.
You are leaking geometries, you need to dispose the geometry after removing the mesh from the scene
scene.remove(c_mesh)
c_tube && c_tube.dispose();
Use WebGLRenderer. The CanvasRenderer leaks removed objects, and you're creating new objects on each frame. (If you're stuck with CanvasRenderer for some reason, sorry for you)
(For the fiddle) slow the motion, requestAnimationFrame isn't required for a test, setTimeout(animate, 500); allows the user to see what's happening.
What's the point of a 0-segment tube ?
if (index >= points.length - 1){
index = 1; //start with 2 points
}
Works as expected
The TubeGeometry does a tube of N (second argument in constructor, 16 in fiddle) segments. (I'll come back to that later, but I don't think you always want 16 segments)
The default behaviour of Curve.getPoinAt (the method used by TubeGeometry and probably lots of other geometries) is to return equidistant points. You can expect: distance(getPointAt(0),getPointAt(0.1)) == distance(getPointAt(0.1),getPointAt(0.2)) to be true.
Because of these points, no matter how many points you put in your path, the TubeGeometry will build a 16-segments tube with all segment of the same length, going from the first to the last point of your path. There is little chance that one of the 15 intermediate points will be exactly at the position of an edge. That should explain what you see.
Trying to fix the stuff
First get rid of that equidistant way-to-be of the TubeGeometry+Path. Overloading getUtoTmapping should be enough (I found that reading the source):
THREE.Linear3.prototype.getUtoTmapping = function(u) {
return u;
};
I changed your getPoint. It probably does the same thing, but I was more comfortable with my code the investigate
function ( t ) {
var points = this.points;
var index = ( points.length - 1 ) * t;
var floorIndex = Math.floor(index);
if(floorIndex == points.length-1)
return points[floorIndex];
var floorPoint = points[floorIndex];
var ceilPoint = points[floorIndex+1];
return floorPoint.clone().lerp(ceilPoint, index - floorIndex);
}
Give the correct number of segments to the TubeGeometry constructor:
var pathPoints = points.slice(0, index);
c_path = new THREE.Linear3(pathPoints, 'Test');
c_tube = new THREE.TubeGeometry(c_path, pathPoints.length-1, 10, 16, false, true);
At this point, you should have roughly what you were expecting
You should see the tube always going through the edges. You should also see that the TubeGeometry isn't meant to have angles. You could improve this angle issue either by looking at how TubeGeometry and Curve handles tangents, or (if you don't care about slowness) by increasing the number of segments to a very large number:
c_tube = new THREE.TubeGeometry(c_path, 200/*pathPoints.length-1*/, 10, 16, false, true);
That's all for the answer. You can find the last version of my experiments here: http://jsfiddle.net/dqn73m98/5/. You can also ask the three.js developers if such a feature exists, or request an implementation in a github issue (if someone as time to do it), here

Finding inaccessible points on a 2D plane

I have been working on JavaScript / JQuery code which allows arrow key movement between input boxes (yes, I am aware this breaks standard UI).
It works by by looping through each element and finding the closest in each direction (left, right, up and down).
Example
P1:(0, 0), P2:(1, 0), P3:(0, 2)
P1 has one point to the right (P2) and one point up (P3).
P2 has one point to the left (P1) and one point up (P3).
No picture
P3 has two points down (P1 & P2) but P1 is closer.
Therefore the final movements are:
Up
1 -> 3
2 -> 3
Right
1 -> 2
Down
3 -> 1
Left
2 -> 1
For this example:
P1 has two incoming and two outgoing connections.
P2 has one incoming and two outgoing connections.
P3 has two incoming and one outgoing connections.
This made me think.
Is there a set of points such that one or more point is inaccessible (0 incoming connections), or can it be proven no such set exists?
Side note:
If you ignore the up / down component (only using left and right with a vertical split) then point P3 in inaccessible in P1: (0, 0), P2: (2, 0), P3: (1, 4).
Here is the JavaScript / JQuery code if it will help anyone.
function arrowKeyNavigation(elements) {
// Get the position of each element.
var elementOffsets = [];
elements.each(function(key, element) {
elementOffsets[key] = $(element).offset();
});
// Find the closest point in each direction and store the points in data('keyNav') for later use.
for (var i = 0; i < elementOffsets.length; i++) {
var closestPoints = [];
for (var j = 0; j < elementOffsets.length; j++) {
if (i != j) {
var distance = calcDistanceSquared(elementOffsets[i], elementOffsets[j]);
var quadrant = calcQuadrant(elementOffsets[i], elementOffsets[j]);
if (closestPoints[quadrant] == undefined || calcDistanceSquared(elementOffsets[i], elementOffsets[closestPoints[quadrant]]) > distance) {
closestPoints[quadrant] = j;
}
}
}
var closestElements = [];
for (var j = 0; j < closestPoints.length; j++) {
closestElements[j] = elements[closestPoints[j]];
}
$(elements[i]).data('keyNav', closestElements);
}
}
// Returns the distance between two points squared.
function calcDistanceSquared(offset1, offset2) {
...
}
// Returns 0, 1, 2 or 3 for left, up, right and down respectively.
// If a point is EXACTLY 45 degrees it will be classified as either left / right.
function calcQuadrant(offset1, offset2) {
...
}
I've thought about it more and I think I have the solution.
The proof sketch goes as follows:
Assume you have a finite number of points in the plane (R^2). Take an arbitrary point and call it your destination. Then take any other point. That point divides R^2 into four quadrants, as you've drawn in red. By definition the destination is in one of those four quadrants. Move in that direction. One of two things can happen:
1) you arrive at the destination and you're done
2) you move to another point.
If 2, then you've moved closer (EDIT: by the 1-norm distance, d((x1,y1),(x2,y2)) = |x1-x2|+|y1-y2|). This requires more proof but I'm just sketching. The destination is now in some quadrant of this new point.
Now note that If you repeat this, always moving one step closer in the direction of the destination (which may change each step) your distance to the destination point will decrease each step; you can never revisit a point because you distance is always decreasing. So, if you have a finite number of points you'll eventually reach your destination.

How do I find the the exact lat/lng coordinates of a birdseye scene in Virtual Earth?

I'm trying to find the latitude and longitude of the corners of my map while in birdseye view. I want to be able to plot pins on the map, but I have hundreds of thousands of addresses that I want to be able to limit to the ones that need to show on the map.
In normal view, VEMap.GetMapView().TopLeftLatLong and .BottomRightLatLong return the coordinates I need; but in Birdseye view they return blank (or encrypted values). The SDK recommends using VEBirdseyeScene.GetBoundingRectangle(), but this returns bounds of up to two miles from the center of my scene which in major cities still returns way too many addresses.
In previous versions of the VE Control, there was an undocumented VEDecoder object I could use to decrypt the LatLong values for the birdseye scenes, but this object seems to have disappeared (probably been renamed). How can I decode these values in version 6.1?
It always seems to me that the example solutions for this issue only find the centre of the current map on the screen, as if that is always the place you're going to click! Anyway, I wrote this little function to get the actual pixel location that you clicked on the screen and return a VELatLong for that. So far it seems pretty accurate (even though I see this as one big, horrible hack - but it's not like we have a choice at the moment).
It takes a VEPixel as input, which is the x and y coordinates of where you clicked on the map. You can get that easily enough on the mouse event passed to the onclick handler for the map.
function getBirdseyeViewLatLong(vePixel)
{
var be = map.GetBirdseyeScene();
var centrePixel = be.LatLongToPixel(map.GetCenter(), map.GetZoomLevel());
var currentPixelWidth = be.GetWidth();
var currentPixelHeight = be.GetHeight();
var mapDiv = document.getElementById("map");
var mapDivPixelWidth = mapDiv.offsetWidth;
var mapDivPixelHeight = mapDiv.offsetHeight;
var xScreenPixel = centrePixel.x - (mapDivPixelWidth / 2) + vePixel.x;
var yScreenPixel = centrePixel.y - (mapDivPixelHeight / 2) + vePixel.y;
var position = be.PixelToLatLong(new VEPixel(xScreenPixel, yScreenPixel), map.GetZoomLevel())
return (new _xy1).Decode(position);
}
Here's the code for getting the Center Lat/Long point of the map. This method works in both Road/Aerial and Birdseye/Oblique map styles.
function GetCenterLatLong()
{
//Check if in Birdseye or Oblique Map Style
if (map.GetMapStyle() == VEMapStyle.Birdseye || map.GetMapStyle() == VEMapStyle.BirdseyeHybrid)
{
//IN Birdseye or Oblique Map Style
//Get the BirdseyeScene being displayed
var birdseyeScene = map.GetBirdseyeScene();
//Get approximate center coordinate of the map
var x = birdseyeScene.GetWidth() / 2;
var y = birdseyeScene.GetHeight() / 2;
// Get the Lat/Long
var center = birdseyeScene.PixelToLatLong(new VEPixel(x,y), map.GetZoomLevel());
// Convert the BirdseyeScene LatLong to a normal LatLong we can use
return (new _xy1).Decode(center);
}
else
{
// NOT in Birdseye or Oblique Map Style
return map.GetCenter();
}
}
This code was copied from here:
http://pietschsoft.com/post/2008/06/Virtual-Earth-Get-Center-LatLong-When-In-Birdseye-or-Oblique-Map-Style.aspx
From the VEMap.GetCenter Method documentation:
This method returns null when the map
style is set to VEMapStyle.Birdseye or
VEMapStyle.BirdseyeHybrid.
Here is what I've found, though:
var northWestLL = (new _xy1).Decode(map.GetMapView().TopLeftLatLong);
var southEastLL = (new _xy1).Decode(map.GetMapView().BottomRightLatLong);
The (new _xy1) seems to work the same as the old undocumented VEDecoder object.
An interesting point in the Bing Maps Terms of Use..
http://www.microsoft.com/maps/product/terms.html
Restriction on use of Bird’s eye aerial imagery:
You may not reveal latitude, longitude, altitude or other metadata;
According to http://dev.live.com/virtualearth/sdk/ this should do the trick:
function GetInfo()
{
alert('The latitude,longitude at the center of the map is: '+map.GetCenter());
}

Categories