I'm trying to write code to track real-time positions of roughly 100 vehicles via GPS. I'd like to smoothly "animate" the google map marker for each vehicle by setting its position along an interpolated path between its last X/Y point and the current one. I call the URL to get a JSON object with all the current vehicle positions every 15 sec via a setInterval call. Inside that I iterate over each vehicle in the JSON object and set the vehicle position. I have functions to animate the motion but it only works reliably for one vehicle, I believe because my nested setInterval function will not complete before the next step in the for loop it's enclosed in. Is there anyway to have the inner setInterval function run to completion before the next "i" in my for loop?
setInterval(function() {
$(document).ready(function() {
$.getJSON("http://localhost:8080/portal/frfeed/query/tampa_sw/paraVehicle?r=" + Math.random(),function(vehicles){
$.each(vehicles, function(index, d){
if(d.heading>=0 && d.heading<22.5) direction="NORTH";
else if(d.heading>=22.5 && d.heading<67.5) direction="NORTHEAST";
else if(d.heading>=67.5 && d.heading<112.5) direction="EAST";
else if(d.heading>=112.5 && d.heading<157.5) direction="SOUTHEAST";
else if(d.heading>=157.5 && d.heading<202.5) direction="SOUTH";
else if(d.heading>=202.5 && d.heading<247.5) direction="SOUTHWEST";
else if(d.heading>=247.5 && d.heading<292.5) direction="WEST";
else if(d.heading>=292.5 && d.heading<338) direction="NORTHWEST";
else direction="NORTH";
vehicle = "";
for (var i=0; i<vMarkers.length; i++) {
if( vMarkers[i][0] === d.internalVehicleId ) {
var path;
var latlng = new google.maps.LatLng(d.latitude,d.longitude);
vMarkers[i][2] = vMarkers[i][1].getPosition().lat();
vMarkers[i][3] = vMarkers[i][1].getPosition().lng();
vMarkers[i][4] = latlng;
vMarkers[i][1].setTitle('Vehicle: ' + d.internalVehicleId + '\r\n' + 'Last Update: ' + d.time + '\r\n' + 'Traveling: ' + direction + ' # ' + d.speed + ' mph');
path = vPolys[i][1].getPath();
path.push(latlng);
vPolys[i][1].setPath(path);
vehicle = vMarkers[i][0];
var lat = vMarkers[i][2];
var lng = vMarkers[i][3];
var latlngTo = vMarkers[i][4];
var latLngFrom = new google.maps.LatLng(lat,lng);
j = 0;
// function below only works correctly if filtered for one vehicle as below, otherwise, all
// markers randomly move and don't stop due to the setInterval being called inside the for loop
if (distance(latlngTo.lat(), latlngTo.lng(),latLngFrom.lat(), latLngFrom.lng()) > 20 && vMarkers[i][0] == "1329") {
iv = window.setInterval(function() {
j++;
var pos = mercatorInterpolate(map, latLngFrom, latlngTo, j/50);
vMarkers[i][1].setPosition(pos);
if (j >= 50) {
window.clearInterval(iv);
}
}, 20);
}
else {
vMarkers[i][1].setPosition(latlngTo);
};
break;
}
}
if( vehicle == "") {
color = get_random_color();
marker = new StyledMarker({
styleIcon:new StyledIcon(StyledIconTypes.BUBBLE,{color:color, fore: "ffffff",text: d.internalVehicleId}),
position: new google.maps.LatLng(d.latitude,d.longitude),
title: 'Vehicle: ' + d.internalVehicleId + '\r\n' + 'Last Update: ' + d.time + '\r\n' + 'Traveling: ' + direction + ' # ' + d.speed + ' mph',
map: map
});
var polyOptions = {
strokeColor: color,
strokeOpacity: 1.0,
map: map,
strokeWeight: 3
};
poly = new google.maps.Polyline(polyOptions);
var latlng = new google.maps.LatLng(d.latitude,d.longitude);
vMarkers.push([d.internalVehicleId, marker, d.latitude, d.longitude, latlng]);
var path = poly.getPath();
path.push(latlng);
poly.setPath(path);
vPolys.push([d.internalVehicleId, poly])
vehicle = "";
}
});//$.each(vehicles, function(index, d){
function mercatorInterpolate(map, latLngFrom, latLngTo, fraction) {
// Get projected points
var projection = map.getProjection();
var pointFrom = projection.fromLatLngToPoint(latLngFrom);
var pointTo = projection.fromLatLngToPoint(latLngTo);
// Adjust for lines that cross the 180 meridian
if (Math.abs(pointTo.x - pointFrom.x) > 128) {
if (pointTo.x > pointFrom.x)
pointTo.x -= 256;
else
pointTo.x += 256;
}
// Calculate point between
var x = pointFrom.x + (pointTo.x - pointFrom.x) * fraction;
var y = pointFrom.y + (pointTo.y - pointFrom.y) * fraction;
var pointBetween = new google.maps.Point(x, y);
// Project back to lat/lng
var latLngBetween = projection.fromPointToLatLng(pointBetween);
return latLngBetween;
}
function distance(lat1,lon1,lat2,lon2) {
var R = 6371;
var dLat = (lat2-lat1) * Math.PI / 180;
var dLon = (lon2-lon1) * Math.PI / 180;
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180 ) * Math.cos(lat2 * Math.PI / 180 ) *
Math.sin(dLon/2) * Math.sin(dLon/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c;
return Math.abs(d*1000);
}
}); //$.getJSON(...., function(vehicles) {
}); //$(document).ready(function() {
}, 16000); // setInterval(function(){
No, setInterval is Asynchronous. You have to program in a way that works with asycronous code, rather then trying to force it to be synchronous.
For animation you should really be using requestAnimationFrame to get smooth results.
I would create a frames array, pushing an array of the cars every 15 seconds. Each car storing it's position
var frames = [[{x: 10, y:2},{x: 5, y:6}], [{x: 12, y:4},{x: 7, y:8}]]
I would then use requestAnimationFrame and interpolate the current position of each car
var currentFrame = 0;
var startTime = 0;
function update(){
var currentTime = newDate();
startTime || startTime = currentTime;
var elaspedTime = Math.floor(currentTime.getTime() - startTime.getTime())/1000;
// increment the current frame if 15 seconds have elapsed
elaspedTime%15 === 0 && currentFrame++;
// Get the current frame
var frame = frames[currentFrame],
nextFrame = frames[++currentFrame];
// Loop over each car in the frame
for(var i = 0; i < frame.length; i++){
// Calculate the difference in location
var xDiff = nextFrame[i].x - frame[i].x;
var yDiff = nextFrame[i].y - frame[i].y;
// interpolate the current position of the cars
var xPos = xDiff / elaspedTime%15;
var yPos = yDiff / elaspedTime%15;
// do some work here to set the position of the cars
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
You could optimize this much better then what I've done, but this is how I'd approach it.
Instead of trying to run multiple setInterval(), run just one, and iterate over all your vehicles within that one function call.
For example:
iv = setInterval(function() {
for (int i=0; i<vehicleArray.length;i++) {
// Do stuff for each vehicle.
}
}, 40);
Note that setInterval() doesn't guarantee the frequency at which it will be called, just the minumum interval. This can lead to haphazard tracking. Avoid this by reading the clock every time you enter the setInterval() function, and calculating new positions based on that.
Your code is trying to achieve 50 frames per second, which might prove to be optimistic. You can get a smooth effect at half that. i.e. at 40ms intervals.
Related
I tried to find a better way to calculate distance between two points on google maps, but the result it wasn't what I expected.
For example, those two solutions which I found here: Calculate distance between two points in google maps V3 are similarly (it produces aprox. 249.3424456 Km - but its wrong because distance between Bacau and Bucharest - Romania - are aprox. 300 Km).
The plnkr link: http://plnkr.co/edit/cv25C6pga0lla7xyBDRO?p=preview
Or:
$( document ).ready(function() {
var myLatLng = new google.maps.LatLng({ lat: 46.5610058, lng: 26.9098054 });
var myLatLng2 = new google.maps.LatLng({ lat: 44.391403, lng: 26.1157184 });
var calc1 = google.maps.geometry.spherical.computeDistanceBetween(myLatLng, myLatLng2)/1000;
console.log('calc1 = ' + calc1);
var start = { lat: 46.5610058, lng: 26.9098054 };
var stop = { lat: 44.391403, lng: 26.1157184 };
var rad = function(x) {
return x * Math.PI / 180;
};
var getDistance = function(p1, p2) {
var R = 6378137; // Earth’s mean radius in meter
var dLat = rad(p2.lat - p1.lat);
var dLong = rad(p2.lng - p1.lng);
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(rad(p1.lat)) * Math.cos(rad(p2.lat)) *
Math.sin(dLong / 2) * Math.sin(dLong / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c;
return d; // returns the distance in meter
};
var calc2 = getDistance(start, stop)/1000;
console.log('calc2 = ' + calc2);
document.getElementById("test").innerHTML = 'calc1 = ' + calc1 + '<br/>calc2 = ' + calc2;
});
When I try to calculate these distance with google maps web api http://maps.googleapis.com/maps/api/distancematrix/json?origins=46.5610058,26.9098054&destinations=44.391403,26.1157184 it tell me that the distance is 299 Km (which is the good value - distance between Bacau and Bucharest - Romania - are aprox. 300 Km).
I prefer to calculate locally rather than to access every time a website but why when I use 'google.maps.geometry.spherical.computeDistanceBetween' function or Haversine formula it calculate a wrong value?
The solution is to compute locally using distancematrix api: https://developers.google.com/maps/documentation/javascript/distancematrix (Have to enable Distance Matrix from API Dashboard)
var from = new google.maps.LatLng(46.5610058, 26.9098054);
var fromName = 'Bacau';
var dest = new google.maps.LatLng(44.391403, 26.1157184);
var destName = 'Bucuresti';
var service = new google.maps.DistanceMatrixService();
service.getDistanceMatrix(
{
origins: [from, fromName],
destinations: [destName, dest],
travelMode: 'DRIVING'
}, callback);
function callback(response, status) {
if (status == 'OK') {
var origins = response.originAddresses;
var destinations = response.destinationAddresses;
for (var i = 0; i < origins.length; i++) {
var results = response.rows[i].elements;
console.log(results);
for (var j = 0; j < results.length; j++) {
var element = results[j];
var distance = element.distance.text;
var duration = element.duration.text;
var from = origins[i];
var to = destinations[j];
}
}
}
}
I found this article which includes a great example. What I would like to do differently, however, is to add a bearing for the rotation to focus on.
I've managed to get my current position as follows:
var lat = null;
var lng = null;
var intId = null;
function showPosition(position) {
lat = position.coords.latitude.toFixed(7);
lng = position.coords.longitude.toFixed(7);
$('#status').html('Latitude: ' + lat + '<br>Longitude: ' + lng);
}
function showError(error) {}
var watchId = null;
if (navigator.geolocation) {
var optn = {
enableHighAccuracy : true,
timeout : Infinity,
maximumAge : 0
};
watchId = navigator.geolocation.watchPosition(showPosition, showError, optn);
intId = setInterval(function() {
if (lat !== null && lng !== null) {
clearInterval(intId);
}
}, 1000);
} else {
$('#status').html('Geolocation is not supported in your browser.');
}
Likewise, I can take my current position along with another fixed postion, and get the bearing with the following functions:
function getBearing(lat1, lng1, lat2, lng2) {
var dLon = toRad(lng2-lng1);
lat1 = toRad(lat1);
lat2 = toRad(lat2);
var y = Math.sin(dLon) * Math.cos(lat2);
var x = Math.cos(lat1)*Math.sin(lat2) - Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
var rad = Math.atan2(y, x);
var brng = toDeg(rad);
return (brng + 360) % 360;
}
function toRad(deg) {
return deg * Math.PI / 180;
}
function toDeg(rad) {
return rad * 180 / Math.PI;
}
Where I am struggling is how to bring the two together (device orientation & bearing)?
Combined with the code from the link above, here is a JSFiddle. I believe I have all the information I need (gamma, beta, alpha & bearing) but I don't know how to apply them together in style.transform so the image points at a specific location rather than north.
Can anyone help me please!?
I have been trying to select a marker on google maps closest to the leap motion coordinate. I have tried converting the leap motion coordinate to google maps coordinate and find the closest marker on google maps to the leap motion device by doing marker.lat and marker.lng for the markers compared to the lat and longitude calculated. However, it isn't working instead it is returning the marker at the bottom of the screen or the top of the screen. Here is my current javaScript code.
var results = document.getElementById('resultsTable');
console.log("Key Tap Gesture at: " + keyTapGesture.position[0]);
//google.maps.event.dispatchEvent(ktEvent);
var closestMarkerDistance = 10000000000000000000000000; //big number to start, so any calculation will override this value
var closestMarker = null;
var distance = 10000000000000000000000000;
console.log("marker distances to keytap are: " + results.length);
var hand = frame.hands[0];
var stabilized = hand.stabilizedPalmPosition;
for (var i = 0; i < markers.length; i++) {
var markerPos = 0;
var keyTapX = 0;
var keyTapY = 0;
var newLatLngPt = 0;
var keyLng = 0;
var keyLat = 0;
markerPos = markers[i].position;
keyTapX = stabilized[0];
keyTapY = stabilized[1];
newLatLngPt = convertToLatLng(keyTapX, keyTapY);
var scaling = 4.0 / Math.pow(2, map.getZoom() - 1);
keyLng = keyTapX * scaling;
keyLat = keyTapY * scaling;
//var keyTapCoord = new google.maps.LatLng(keyLat, keyLng);
distance = getDistanceFromLatLonInKm(markerPos.lat(), markerPos.lng(), newLatLngPt.lat(), newLatLngPt.lng());
if (distance < closestMarkerDistance) {
closestMarkerDistance = distance;
closestMarker = markers[i];
}
console.log(" \n" + distance + markers[i].getTitle());
}
if(closestMarker != null) {
console.log("\nclosest marker is : " + closestMarker.name + " title: " + closestMarker.getTitle() + " at pos: " + closestMarker.getPosition());
infowindow.setContent(closestMarker.getTitle());
infowindow.open(map,closestMarker);
console.log("\n ALSO: --> " + stabilized[0] + " ::::::" + stabilized[1]);
console.log("\n ANNNNNND: --> " + keyLng + " ::::::" + keyLat + "and then real la/lo = " + markerPos.lng() + " ____ " + markerPos.lat());
//document.getElementById(choices).innerHTML = place.name + "<br/>" + place.vicinity;
}
else {
console.log("\nclosest marker Does Not Exist");
}
function getDistanceFromLatLonInKm(lat1, lon1, lat2, lon2) {
var R = 6371; // Radius of the earth in km
var dLat = deg2rad(lat2 - lat1); // deg2rad below
var dLon = deg2rad(lon2 - lon1);
var a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2)
;
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c; // Distance in km
return d;
}
function convertToLatLng(x, y) {
// retrieve the lat lng for the far extremities of the (visible) map
var latLngBounds = map.getBounds();
var neBound = latLngBounds.getNorthEast();
var swBound = latLngBounds.getSouthWest();
// convert the bounds in pixels
var neBoundInPx = map.getProjection().fromLatLngToPoint(neBound);
var swBoundInPx = map.getProjection().fromLatLngToPoint(swBound);
// compute the percent of x and y coordinates related to the div containing the map; in my case the screen
var procX = x / window.innerWidth;
var procY = y / window.innerHeight;
// compute new coordinates in pixels for lat and lng;
// for lng : subtract from the right edge of the container the left edge,
// multiply it by the percentage where the x coordinate was on the screen
// related to the container in which the map is placed and add back the left boundary
// you should now have the Lng coordinate in pixels
// do the same for lat
var newLngInPx = (neBoundInPx.x - swBoundInPx.x) * procX + swBoundInPx.x;
var newLatInPx = (swBoundInPx.y - neBoundInPx.y) * procY + neBoundInPx.y;
// convert from google point in lat lng and have fun :)
var newLatLng = map.getProjection().fromPointToLatLng(new google.maps.Point(newLngInPx, newLatInPx));
return newLatLng;
}
function deg2rad(deg) {
return deg * (Math.PI / 180)
}
The Leap Motion coordinate system has nothing to do with your browser window. The X axis extends from approx. -300mm to +300mm. The Y axis extends from about 0 to +600mm -- it also is positive upward, while your browser Y-axis is more positive downward. If I read your code correctly, you are trying to use the Leap Motion coordinates as if they were pixel coordinates. They aren't.
You need to map the Leap Motion coordinates to something that makes sense for your application. This article in the documentation may help: https://developer.leapmotion.com/documentation/javascript/devguide/Leap_Coordinate_Mapping.html
In particular, have a look at the InteractionBox class, with which you can normalize the Leap Motion coordinates to the range [0..1]. It is generally easier to map the normalized coordinates to your application coordinate system.
Currently the formula I am using is below, but it is less accurate as the Vincenty formula, which you can find on this link:
http://www.movable-type.co.uk/scripts/latlong-vincenty-direct.html
My question is, can someone help simplify the javascript code so i can implement it in my formula? I am trying to learn javascript but it is a bit beyond my capabilities.
ex = lat2
ey = lon2
Im thinking the easiest way would be to run through the code and do an array of 360 degrees to calculate the ex/ey coordinates.
<script type="text/javascript">
function drawCircle(point, radius, dir, addtoBounds) {
var d2r = Math.PI / 180; // degrees to radians
var r2d = 180 / Math.PI; // radians to degrees
var earthsradius = 6378137;
var points = 360;
// find the radius in lat/lon
var rlat = (radius / earthsradius) * r2d;
var rlng = rlat / Math.cos(point.lat() * d2r);
var extp = new Array();
if (dir==1) {var start=0;var end=points+1} // one extra here makes sure we connect the
else {var start=points+1;var end=0}
for (var i=start; (dir==1 ? i < end : i > end); i=i+dir)
{
var theta = Math.PI * (i / (points/2));//i is number of points + 1
var lat1=point.lat()*d2r;
var lon1=point.lng()*d2r;
var d=radius;
var R=earthsradius;
var ex = Math.asin( Math.sin(lat1)*Math.cos(d/R) +
Math.cos(lat1)*Math.sin(d/R)*Math.cos(theta));
var ey = lon1 + Math.atan2(Math.sin(theta)*Math.sin(d/R)*Math.cos(lat1),
Math.cos(d/R)-Math.sin(lat1)*Math.sin(ex));
extp.push(new google.maps.LatLng(ex*r2d, ey*r2d));
if (addtoBounds) bounds.extend(extp[extp.length-1]);
}
// alert(extp.length);
return extp;
}
Here is the direct formula converted to php. I am trying to put this code into the google maps code. The movable type link actually has this code in javascript, but since I know php much better, I converted it over to test it out, this works perfectly.
<?php
$lat1 = 29.10860062;
$lon1 = -95.46209717;
$a = 6378137;
$b = 6356752.314245;
$f = 1/298.257223563; // WGS-84 ellipsoid params
$brng = 32.8;
$s = 1796884.48;
$alpha1 = deg2rad($brng);
$sinAlpha1 = sin($alpha1);
$cosAlpha1 = cos($alpha1);
$tanU1 = (1-$f) * tan(deg2rad($lat1));
$cosU1 = 1 / sqrt((1 + pow($tanU1,2)));
$sinU1 = $tanU1*$cosU1;
$sigma1 = atan2($tanU1, $cosAlpha1);
$sinAlpha = $cosU1 * $sinAlpha1;
$cosSqAlpha = 1 - pow($sinAlpha,2);
$uSq = $cosSqAlpha * (pow($a,2) - pow($b,2)) / (pow($b,2));
$A = 1 + $uSq/16384*(4096+$uSq*(-768+$uSq*(320-175*$uSq)));
$B = $uSq/1024 * (256+$uSq*(-128+$uSq*(74-47*$uSq)));
$sigma = $s / ($b*$A);
$sigmaP = 2*pi;
$limit = 100;
$counter = 1;
while ( $counter <= $limit ) {
$cos2SigmaM = cos(2*$sigma1 + $sigma);
$sinSigma = sin($sigma);
$cosSigma = cos($sigma);
$deltaSigma = $B*$sinSigma*($cos2SigmaM+$B/4*($cosSigma*(-1+2*pow($cos2SigmaM,2))-$B/6*$cos2SigmaM*(-3+4*pow($sinSigma,2))*(-3+4*pow($cos2SigmaM,2))));
$sigmaP = $sigma;
$sigma = $s / ($b*$A) + $deltaSigma;
$counter = $counter+1;
};
$tmp = $sinU1*$sinSigma - $cosU1*$cosSigma*$cosAlpha1;
$lat2 = atan2($sinU1*$cosSigma + $cosU1*$sinSigma*$cosAlpha1,(1-$f)*sqrt(pow($sinAlpha,2)+ pow($tmp,2)));
$lambda = atan2($sinSigma*$sinAlpha1, $cosU1*$cosSigma - $sinU1*$sinSigma*$cosAlpha1);
$C = $f/16*$cosSqAlpha*(4+$f*(4-3*$cosSqAlpha));
$L = $lambda - (1-$C) * $f * $sinAlpha *($sigma + $C*$sinSigma*($cos2SigmaM+$C*$cosSigma*(-1+2*pow($cos2SigmaM,2))));
if (deg2rad($lon1)+$L+(3*pi)<(2*pi)) {
( $lon2 = (deg2rad($lon1)+$L+(3*pi))-pi);
} else {
( $lon2 = ((deg2rad($lon1)+$L+3*pi))%(2*pi))-pi;}
$revAz = atan2($sinAlpha, -$tmp); // final bearing, if required
?>
Since the link you provided already provides the formula in javascript the hard part is complete, you can just copy it and call it rather than rewriting it into your function. Just remember to attribute the source. I removed the variables that were not being used. Also, I just hard coded 361 into the formula since you were just assigning it to a points variable. You can change this back if you are going to be passing the number of degrees into the formula. I separated the for loops, to me this is more readable, and I dont think the way you had before was working like you intended it. When working with degrees and radians I always wrap these conversions into functions since it improves readability. To do this I hooked them up to the Number object in JavaScript using prototype as seen here:
Number.prototype.toRad = function() {
//'this' is the current number the function is acting on.
//e.g. 360.toRad() == 2PI radians
return this * Math.PI / 180;
}
Number.prototype.toDeg = function() {
return this * 180 / Math.PI;
}
Not too tough to understand, prototype allows you to extend objects in JavaScript, similar to inheritance in class based languages. There are plenty of resources online that can help clarify.
Here is the reworked drawCircle function:
function drawCircle(point, radius, dir, addtoBounds) {
//best practice is to use [] rather then new Array(),
//both do the same thing.
var extp = [];
if (dir == 1) {
for (var i = 0; i < 361; i++) {
//destVincenty function returns a object with
//lat, lon, and final bearing.
var destPoint = destVincenty(point.lat(), point.lng(), i, radius);
//add new point
extp.push(new google.maps.LatLng(destPoint.lat, destPoint.lon));
if (addtoBounds) bounds.extend(extp[extp.length - 1]);
}
}
else {
for (var i = 361; i > 0; i--) {
var destPoint = destVincenty(point.lat(), point.lng(), i, radius);
extp.push(new google.maps.LatLng(destPoint.lat, destPoint.lon));
if (addtoBounds) bounds.extend(extp[extp.length - 1]);
}
}
return extp;
}
here is a fiddle of it working.
I am working on a sort of itinerary mapper for packaged vacations, and I'm really happy with what I've done so far; I have the directions api implemented with a custom renderer, so I can take driving directions, and plot my own polyline complete with directional arrows that aren't google's awful ones spaced along the path. I am not exactly a math expert, and I am trying to figure out how I could make a path parallel to another path. For example, the itinerary goes from city 1 to city 2, and then back to city 1.
I want to offset the trip back to city 1's polyline, so that it mirrors the path, but travels parallel to it. Ideally, I would like to when I create the path, check for intersecting points in other paths, and if any are found, offset the path at those points only. This would be a better implementation, because you could for instance parallel the path only where it happens to intersect another one, like when it meets another path only for a short time.
I found this code for API2 from bill chadwick
The link is here: http://wtp2.appspot.com/ParallelLines.htm
Update: Somehow managed to convert this old v2 script to get it working in v3, but I'm experiencing some troubles...
It is more than doubling the original number of points, and following the path, but really throwing them in randomly. Screenshot here:
The class I converted is here:
function BDCCParallelLines(points, color, weight, opacity, opts, gapPx) {
console.log('Pllel COnstructor Initialized');
this.gapPx = gapPx;
this.points = points;
this.color = color;
this.weight = weight;
this.opacity = opacity;
this.opts = opts;
this.line1 = null;
this.line2 = null;
this.lstnZoom = null;
}
BDCCParallelLines.prototype = new google.maps.OverlayView();
BDCCParallelLines.prototype.onAdd = function() {
console.log('Pllel Initialized');
this.prj = map.getProjection();
var self = this;
this.lstnZoom = google.maps.event.addListener(map, "zoom_changed", function() {
self.recalc();
});
this.recalc();//first draw
}
BDCCParallelLines.prototype.onRemove = function() {
if(this.line2)
this.line2.setMap(null);
if(this.line1)
this.line1.setMap(null);
if(this.lstnZoom != null)
google.maps.event.removeListener(this.lstnZoom);
}
BDCCParallelLines.prototype.copy = function() {
return new BDCCParallelLines(this.points,this.color,this.weight,this.opacity,this.opts,this.gapPx);
}
BDCCParallelLines.prototype.draw = function(force) {
return; //do nothing
}
/**
* #param {google.maps.Map} map
* #param {google.maps.LatLng} latlng
* #param {int} z
* #return {google.maps.Point}
*/
BDCCParallelLines.prototype.latLngToPoint = function(latlng, z){
var normalizedPoint = map.getProjection().fromLatLngToPoint(latlng); // returns x,y normalized to 0~255
var scale = Math.pow(2, z);
var pixelCoordinate = new google.maps.Point(normalizedPoint.x * scale, normalizedPoint.y * scale);
return pixelCoordinate;
};
/**
* #param {google.maps.Map} map
* #param {google.maps.Point} point
* #param {int} z
* #return {google.maps.LatLng}
*/
BDCCParallelLines.prototype.pointToLatlng = function(point, z){
var scale = Math.pow(2, z);
var normalizedPoint = new google.maps.Point(point.x / scale, point.y / scale);
var latlng = map.getProjection().fromPointToLatLng(normalizedPoint);
return latlng;
};
BDCCParallelLines.prototype.recalc = function() {
var distallowance;
console.log('recalc called');
var zoom = map.getZoom();
distallowance = 1.6;
if(zoom > 6){
distallowance = 1.3;
if(zoom > 9){
distallowance = .7;
if( zoom > 13){
distallowance = .2;
if( zoom > 15){
distallowance = .0001;
}
}
}
}
console.log('Zoom Level: ' + zoom);
console.log('Allowance = ' + distallowance);
var pts1 = new Array();//left side of center
//shift the pts array away from the centre-line by half the gap + half the line width
var o = (this.gapPx + this.weight)/2;
var p2l,p2r;
for (var i=1; i<this.points.length; i++){
var p1lm1;
var p1rm1;
var p2lm1;
var p2rm1;
var thetam1;
var p1 = this.latLngToPoint(this.points[i-1], zoom)
var p2 = this.latLngToPoint(this.points[i], zoom)
var theta = Math.atan2(p1.x-p2.x,p1.y-p2.y);
theta = theta + (Math.PI/2);
var dl = Math.sqrt(((p1.x-p2.x)*(p1.x-p2.x))+((p1.y-p2.y)*(p1.y-p2.y)));
if(theta > Math.PI)
theta -= Math.PI*2;
var dx = Math.round(o * Math.sin(theta));
var dy = Math.round(o * Math.cos(theta));
var p1l = new google.maps.Point(p1.x+dx,p1.y+dy);
var p1r = new google.maps.Point(p1.x-dx,p1.y-dy);
p2l = new google.maps.Point(p2.x+dx,p2.y+dy);
p2r = new google.maps.Point(p2.x-dx,p2.y-dy);
if(i==1){ //first point
pts1.push(this.pointToLatlng(p1l,zoom));
}
else{ // mid this.points
if(distbetweentwo(this.points[i-1], this.points[i]) > distallowance){
if(theta == thetam1){
// adjacent segments in a straight line
pts1.push(this.pointToLatlng(p1l,zoom));
}
else{
var pli = this.intersect(p1lm1,p2lm1,p1l,p2l);
var pri = this.intersect(p1rm1,p2rm1,p1r,p2r);
var dlxi = (pli.x-p1.x);
var dlyi = (pli.y-p1.y);
var drxi = (pri.x-p1.x);
var dryi = (pri.y-p1.y);
var di = Math.sqrt((drxi*drxi)+(dryi*dryi));
var s = o / di;
var dTheta = theta - thetam1;
if(dTheta < (Math.PI*2))
dTheta += Math.PI*2;
if(dTheta > (Math.PI*2))
dTheta -= Math.PI*2;
if(dTheta < Math.PI){
//intersect point on outside bend
pts1.push(this.pointToLatlng(p2lm1,zoom));
pts1.push(this.pointToLatlng(new google.maps.Point(p1.x+(s*dlxi),p1.y+(s*dlyi)),zoom));
pts1.push(this.pointToLatlng(p1l,zoom));
}
else if (di < dl){
pts1.push(this.pointToLatlng(pli,zoom));
}
else{
pts1.push(this.pointToLatlng(p2lm1,zoom));
pts1.push(this.pointToLatlng(p1l,zoom));
}
}
}
else{
//console.log(distbetweentwo(this.points[i-1], this.points[i]));
}
}
p1lm1 = p1l;
p1rm1 = p1r;
p2lm1 = p2l;
p2rm1 = p2r;
thetam1 = theta;
//end loop
}
pts1.push(this.pointToLatlng(p2l,zoom));//final point
// console.log(pts1);
if(this.line1)
this.line1.setMap(null);
this.line1 = new google.maps.Polyline({
strokeColor: this.color,
strokeOpacity: this.opacity,
strokeWeight: this.weight,
map: map,
path: pts1 });
this.line1.setMap(map);
}
BDCCParallelLines.prototype.intersect = function(p0,p1,p2,p3)
{
// this function computes the intersection of the sent lines p0-p1 and p2-p3
// and returns the intersection point,
var a1,b1,c1, // constants of linear equations
a2,b2,c2,
det_inv, // the inverse of the determinant of the coefficient matrix
m1,m2; // the slopes of each line
var x0 = p0.x;
var y0 = p0.y;
var x1 = p1.x;
var y1 = p1.y;
var x2 = p2.x;
var y2 = p2.y;
var x3 = p3.x;
var y3 = p3.y;
// compute slopes, note the cludge for infinity, however, this will
// be close enough
if ((x1-x0)!=0)
m1 = (y1-y0)/(x1-x0);
else
m1 = 1e+10; // close enough to infinity
if ((x3-x2)!=0)
m2 = (y3-y2)/(x3-x2);
else
m2 = 1e+10; // close enough to infinity
// compute constants
a1 = m1;
a2 = m2;
b1 = -1;
b2 = -1;
c1 = (y0-m1*x0);
c2 = (y2-m2*x2);
// compute the inverse of the determinate
det_inv = 1/(a1*b2 - a2*b1);
// use Kramers rule to compute xi and yi
var xi=((b1*c2 - b2*c1)*det_inv);
var yi=((a2*c1 - a1*c2)*det_inv);
return new google.maps.Point(Math.round(xi),Math.round(yi));
}
This is working to a point... It is working as well as the original implementation. The entire path is recalculated on a zoom basis, and I kind of hacked the function to skip very short paths(weird angles) at higher zoom levels, it more closely follows the path the more you zoom in.
I would rather just have a fixed distance offset that is not recalculated, as it is pretty intensive... There are many programs which accomplish this feat, rhino3d, autocad, illustrator... I feel like it would be great for driving directions for google maps itself, an offsetting of the path so you can distinguish the return trip and the original trip.
If anybody has done anything similar to this in JS even if its not for google maps specifically, I would love to see it. Links I am investigating:
http://processingjs.nihongoresources.com/bezierinfo/
http://www.groupsrv.com/computers/about21532.html
Offsetting paths in general is a pretty tricky buisness. This paper (scientific paper alert) gives a good description of the steps taken for 'professional' offset algorithms.
http://cgcad.thss.tsinghua.edu.cn/~yongjh/papers/CiI2007V58N03P0240.pdf
You don't seem to want anything as fancy as in the demo. From what I gather you just want the same polyline, only shifted some pixels to the right and maybe some to the top so it doesn't overlap.
The code you posted has a latLngToPoint function and pointToLatLng function. I think the directions you get from Google are LatLng, so you can convert those to Points, increase the x and y property, and convert it back to a LatLng and draw your Polyline.
This should get you a line that exactly follows the original line. But, it wont look as fancy as the one in the demo. Since it wont be adding any points to smooth the line.