I need to know what is the current center of the Cesium Map.
I tried to use viewer.camera.position but it always gives the same z value (x: 16921255.101297915, y: 5578093.302269477, z: 12756274) and I'm not sure about the x and y values. Are they in meters?
Thanks a lot!
EDIT: Solution
With all the help I got (thanks!) I put this together:
getPosition(){
if (viewer.scene.mode == 3) {
var windowPosition = new Cesium.Cartesian2(viewer.container.clientWidth / 2, viewer.container.clientHeight / 2);
var pickRay = viewer.scene.camera.getPickRay(windowPosition);
var pickPosition = viewer.scene.globe.pick(pickRay, viewer.scene);
var pickPositionCartographic = viewer.scene.globe.ellipsoid.cartesianToCartographic(pickPosition);
console.log(pickPositionCartographic.longitude * (180 / Math.PI));
console.log(pickPositionCartographic.latitude * (180 / Math.PI));
} else if (viewer.scene.mode == 2) {
var camPos = viewer.camera.positionCartographic;
console.log(camPos.longitude * (180 / Math.PI));
console.log(camPos.latitude * (180 / Math.PI));
}
};
This function gives longitude/latitude coordinates in degrees.
viewer.camera.position gives you the position at which camera is located in X,Y,Z coordinates in meters in relation to earth center.
Depending on which scene mode you are using approach is different:
SCENE3D:
In order to see at what is the camera looking at you need to get the intersect point of camera's pick ray and map.
function getMapCenter() {
var windowPosition = new Cesium.Cartesian2(viewer.container.clientWidth / 2, viewer.container.clientHeight / 2);
var pickRay = viewer.scene.camera.getPickRay(windowPosition);
var pickPosition = viewer.scene.globe.pick(pickRay, viewer.scene);
var pickPositionCartographic = viewer.scene.globe.ellipsoid.cartesianToCartographic(pickPosition);
console.log(pickPositionCartographic.longitude * (180/Math.PI));
console.log(pickPositionCartographic.latitude * (180/Math.PI));
}
Based on this thread.
Also try to check if camera is looking at the map, and not a the sky.
SCENE2D:
This is a simple 2D view with camera pointing directly down.
From docs:
2D mode. The map is viewed top-down with an orthographic projection
var camPos = viewer.camera.positionCartographic;
console.log(camPos.longitude * (180/Math.PI));
console.log(camPos.latitude * (180/Math.PI));
Remaining case is 2.5D or COLUMBUS_VIEW
I want to display a google map on my site along with a caption of how large of an area is covered in miles. I will use the area between the left and right borders for this.
For example under the map it will say "3 miles" and then when the user zooms in, it will change to "1 mile" and so on
Thanks
The API has a distance method which returns the distance in meters. So you can do it like this:
var bounds = myMap.getBounds();
google.maps.geometry.spherical.computeDistanceBetween(bounds.getNorthEast(), bounds.getSouthWest());
Start by calling the getBounds() method on the Map object. This will return a LatLngBounds object from which you'll know the latitude and longitude of the bounding box the map defines.
Now that you're armed with the latitude and longitudes you'll simply need to apply the Haversine formula to computer the distance. For a good overview of this equation, and an example in Javascript see this page. An excerpt of which is:
var R = 6371; // km
var dLat = (lat2-lat1).toRad();
var dLon = (lon2-lon1).toRad();
var lat1 = lat1.toRad();
var lat2 = lat2.toRad();
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c;
Since the above equation operates in kilometers, and you asked for miles, one additional modification you could perform would be to convert the output (d) to miles. Converting kilometers to miles is simply a matter of dividing by 1.609344. So, you could simply:
d /= 1.609344;
Expanding upon this, you could then even allow the user to choose between miles/kilometers, and wrap the whole thing up in an if statement:
if(miles)
d /= 1.609344;
Because there is a limitation to 2048 characters in the get request, you are not able to generate an image with Google Static Maps which contains a polygon with a great number of polygon points.
Especially if you try to draw many complex polygons on one map.
If you use Google Maps API, you will have no problem - it works very well!
But I want to have an image (jpg or png)...
So, is there any opportunity to create an image from the Google Maps API? Or any way to 'trick' the 2048 char limitation?
Thanks!
There's no way to 'trick' the character limit, but it is possible to simplify your polyline to bring the encoded polyline string below the character limit. This may or may not result in a polygon of suitable fidelity for your needs.
One additional caveat is that (to the best of my knowledge) the Static Maps API only allows a single encoded polyline to be drawn on the map (this can look like a polygon, if you either close it yourself or fill it, but it's still a polyline, not a polygon).
One option for simplifying your polyline is the Douglas Peucker algorithm. Below is an implementation which extends the google.maps.Polyline object with a simplify method.
This relies on having the Google Maps JS API loaded, which you may not want if you're using Static Maps, but the code below could easily be re-written.
google.maps.Polyline.prototype.simplify = function(tolerance) {
var points = this.getPath().getArray(); // An array of google.maps.LatLng objects
var keep = []; // The simplified array of points
// Check there is something to simplify.
if (points.length <= 2) {
return points;
}
function distanceToSegment(p, v, w) {
function distanceSquared(v, w) {
return Math.pow((v.x - w.x),2) + Math.pow((v.y - w.y),2)
}
function distanceToSegmentSquared(p, v, w) {
var l2 = distanceSquared(v, w);
if (l2 === 0) return distanceSquared(p, v);
var t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
if (t < 0) return distanceSquared(p, v);
if (t > 1) return distanceSquared(p, w);
return distanceSquared(p, { x: v.x + t * (w.x - v.x), y: v.y + t * (w.y - v.y) });
}
// Lat/Lng to x/y
function ll2xy(p){
return {x:p.lat(),y:p.lng()};
}
return Math.sqrt(distanceToSegmentSquared(ll2xy(p), ll2xy(v), ll2xy(w)));
}
function dp( points, tolerance ) {
// If the segment is too small, just keep the first point.
// We push the final point on at the very end.
if ( points.length <= 2 ) {
return [points[0]];
}
var keep = [], // An array of points to keep
v = points[0], // Starting point that defines a segment
w = points[points.length-1], // Ending point that defines a segment
maxDistance = 0, // Distance of farthest point
maxIndex = 0; // Index of said point
// Loop over every intermediate point to find point greatest distance from segment
for ( var i = 1, ii = points.length - 2; i <= ii; i++ ) {
var distance = distanceToSegment(points[i], points[0], points[points.length-1]);
if( distance > maxDistance ) {
maxDistance = distance;
maxIndex = i;
}
}
// check if the max distance is greater than our tollerance allows
if ( maxDistance >= tolerance ) {
// Recursivly call dp() on first half of points
keep = keep.concat( dp( points.slice( 0, maxIndex + 1 ), tolerance ) );
// Then on second half
keep = keep.concat( dp( points.slice( maxIndex, points.length ), tolerance ) );
} else {
// Discarding intermediate point, keep the first
keep = [points[0]];
}
return keep;
};
// Push the final point on
keep = dp(points, tolerance);
keep.push(points[points.length-1]);
return keep;
};
This has been cobbled together with the help of a couple of examples (here and here).
You can now take your original polyline and feed it through this function with increasing tolerance until the resulting encoded polyline falls below the URL length limit (which will depend on the other parameters you're passing to Static Maps).
Something like this should work:
var line = new google.maps.Polyline({path: path});
var encoded = google.maps.geometry.encoding.encodePath(line.getPath());
var tol = 0.0001;
while (encoded.length > 1800) {
path = line.simplify(tol);
line = new google.maps.Polyline({path: path});
encoded = google.maps.geometry.encoding.encodePath(path);
tol += .005;
}
Another way is to use a javascript library that can convert your content of a canvas to an image. Something like
http://html2canvas.hertzen.com/
http://www.nihilogic.dk/labs/canvas2image/
Though I am not sure about it's performance for googlemaps with overlay's.
EDIT: If you're using html2canvas, be sure to checkout this question:
https://stackoverflow.com/a/17816195/2279924
As of September 2016 the URL limit has been changed to 8192 characters in size.
https://developers.google.com/maps/documentation/static-maps/intro#url-size-restriction
There was also a feature request in public issue tracker that was marked as Fixed.
I am displaying in google maps (v3) markers with the position where the photograph was taken and position of the subject and tracing a line to show direction in which photo was taken. I have also calculated the distance and the angle from camera position.
What I am trying now is to show the view from camera with a triangle that opens, lets say 30 degrees toward the subject. Seem to me is a third grade math, but can't figure out after 25 years, I know how to draw the polygon/triangle but not really how to calculate the points at subject's distance about 30 degrees in both directions, of course taking in mind the heading angle.
Almost there...
Used the formula Red answered below to create a function (found I needed to convert: lat1, lon1 and bearing to radians before the math.
I call this function each time a marker changes to calculate again pos1 and pos2 of the triangle and change the paths to redraw it.
Thing now is that triangle shows up but facing all kind of directions but the proper one.
Question now is:
Google return the heading in degrees negative to West and positive to East (-180/180) and seems that the formula need the bearing (heading) in 360 degress? Right now function CameraView(75) display correctly but I am passing the angle as 75 degrees instead of 15 degrees (to have 15 degrees view left, and 15 to right).
function getVertex(brng) {
var R = 6366.707;
var d = parseFloat( getObj('GPSDestDistance').value ) * 1.5 //distance on form field
//position of Camera
var lat1 = deg2rad( markerCam.getPosition().lat() )
var lon1 = deg2rad( markerCam.getPosition().lng() )
var lat2 = Math.asin( Math.sin(lat1) * Math.cos(d/R) + Math.cos(lat1) * Math.sin(d/R) * Math.cos(brng) );
var lon2 = lon1 + Math.atan2( Math.sin(brng) * Math.sin(d/R) * Math.cos(lat1), Math.cos(d/R) - Math.sin(lat1) * Math.sin(lat2) );
lat2 = rad2deg( lat2 )
lon2 = rad2deg( lon2 )
var pos = new google.maps.LatLng(lat2, lon2)
return pos;
}
function CameraView(angle) {
var brng = deg2rad( parseFloat( getObj('GPSDestBearing').value ) ); //get heading from form
if(brng<0){
//brng = Math.round(-brng / 360 + 180)
}
var pos1 = markerCam.getPosition();
var pos2 = getVertex(brng - angle);
var pos3 = getVertex(brng + angle);
var paths = [ pos1, pos2, pos3 ];
poly.setPath(paths);
}
function deg2rad(x) { return x * (Math.PI / 180); }
function rad2deg(x) { return x * (180 / Math.PI); }
A simpler way: use google maps geometry library
calculateAngle: function(subjectMarker, cameraMarker, angle) {
var distance, heading, left, right;
// Get the heading between two markers
heading = google.maps.geometry.spherical.computeHeading(subjectMarker.getPosition(), cameraMarker.getPosition());
// convert heading range from [-180,180] to [0,360]
heading = ((heading - -180) * 360) / 360;
// Get the distance between two markers
distance = google.maps.geometry.spherical.computeDistanceBetween(cameraMarker.getPosition(), subjectMarker.getPosition());
// Calculate angle
left = new google.maps.geometry.spherical.computeOffset(cameraMarker.getPosition(), distance / Math.cos(Math.PI * (angle / 2) / 180), heading - (angle / 2));
right = new google.maps.geometry.spherical.computeOffset(cameraMarker.getPosition(), distance / Math.cos(Math.PI * (angle / 2) / 180), heading + (angle / 2));
// Return an array of `google.maps.LatLng` objects for use when drawing a polygon.
return [cameraMarker.getPosition(), left, right];
}
The formula to get an endpoint latitude and longitude (lat 2,lon2) (in radians) from a starting location (lat1,lon1) (also in radians), range (d), radius of the Earth (R) and bearing (brng) is:
var lat2 = Math.asin( Math.sin(lat1)*Math.cos(d/R) + Math.cos(lat1)*Math.sin(d/R)*Math.cos(brng) );
var lon2 = lon1 + Math.atan2(Math.sin(brng)*Math.sin(d/R)*Math.cos(lat1), Math.cos(d/R)-Math.sin(lat1)*Math.sin(lat2));
Your start point will be one of the points of the triangle polygon. The other two can be found using this code snippet, putting in your bearing +/- 15 degrees (for the 30 degree triangle example) and a range that depends entirely on how big you want the triangle.
I've started to create a website where it's users are effectively tracked (they know they are being tracked). Users will walk a particular route (around Manchester, UK. to be more precise) of which there are 10 checkpoints. A checkpoint is a static position on the map. Using the Google Maps API I know that I can plot a position on a map i.e. a checkpoint. I am also storing the time at which a user reaches said checkpoint. Taking the distance between checkpoints I am then able to calculate their average speed using basic math.
Now what I would like to do is plot their estimated position based on their speed. The difficulty that I am having is plotting a new position x miles/meters (any unit) from the current position along the route.
Had it been a straight line, this would have been simple.
Is there a way to calculate a distance from the current position along the route?
Are there any restrictions on the number of points?
Are there specific ways of doing this that should be avoided?
To expand my example with an image:
Imagine that a user reached the first place marker at 07:00am and it's estimated they would reach the second place marker at 09:00am. The time now (for example) is 08:00am meaning that (estimated) the user should be about half way between the markers. I would then calculate the distance they have walked (again, estimated) and plot their position on the map "distance" away from the first place marker.
Hopefully I have explained the scenario clear enough for people to understand.
I'm relatively new to the Google maps API so any thoughts would be helpful. Other similar questions have been asked on SO but from what I can see, none have been answered or have requested as many details as I have.
Thanks in advance.
UPDATE: Having spent a lot of time trying to work it out I failed miserably. Here is what I know:
I should create the path using a PolyLine (I can do this, I have a list of lat/lng)
There is a JS extension called epoly.js but this isn't compatible with V3
Using spherical.interpolate wont work because it doesn't follow the path.
I used to do a lot of this stuff in a past life as a cartographer. Your polyline is made up of a succession of points (lat/long coordinates). Between each successive point you calculate the distance, adding it up as you go along until you get to the desired distance.
The real trick is calculating the distance between two lat/long points which are spherical coordinates (ie points on a curved surface). Since you are dealing with fairly small distances you could feasibly convert the lat/long coordinates to the local map grid system (which is flat). The distance between two points is then straight forward right angle pythagoras (sum of the squares and all that). Movable Type website has a lot of good (javascript) code on this here.
The second way would be to do the spherical distance calculation - not pretty but you can see it here
Personally I'd go the route of converting the coordinates to the local grid system which in the UK should be OSGB. Its the least contorted method.
Hope this helps
Edit:
I've assumed that you can extract your polyline coordinates using the google api. I havn't done this in version 3 of the api, but it should be straight forward. Also, the polyline coordinates should be fairly close together that you don't need to interpolate intermediate points - just grab the nearest polyline coordinate (saves you having to do a bearing and distance calculation).
Edit2 - With Code
I've had a go at putting some code together, but probably won't have time to finish it within your time limit (I do have a job). You should be able to get the jist. The coordinate conversion code is lifted from the movable type web site and the basic google maps stuff from one of google's examples. Basically it draws a polyline with mouse clicks, puts the lat/long of each mouse click in table field, converts the coordinate to OSGB and then to OS Grid (see here). After the first click it then calculates the distance between each subsequent point. Hope this gets you on the road.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map_canvas { height: 100% }
</style>
<script type="text/javascript"
src="http://maps.googleapis.com/maps/api/js?sensor=false">
</script>
<script src="Map.js" type="text/javascript"></script>
</head>
<body onload="initialize()" style="width:100%;height:100%">
<div style="margin-right:auto;margin-left:auto;margin-top:100px;width:900px;">
<div id="map_canvas" style="width:600px; height:500px;float:left;"></div>
<div style="float:right;">
<table>
<tr>
<td align="right">Latitude:</td>
<td><input id="txtLatitude" maxlength="11" type="text" class="inputField"/></td>
</tr>
<tr>
<td align="right">Longitude:</td>
<td><input id="txtLongitude" maxlength="11" type="text" class="inputField"/></td>
</tr>
<tr>
<td align="right">Eastings:</td>
<td><input id="txtEast" maxlength="11" type="text" class="inputField"/></td>
</tr>
<tr>
<td align="right">Northings:</td>
<td><input id="txtNorth" maxlength="11" type="text" class="inputField"/></td>
</tr>
<tr>
<td align="right">Distance:</td>
<td><input id="txtDistance" maxlength="11" type="text" class="inputField"/></td>
</tr>
<tr>
<td colspan=2 align="right">
</td>
</tr>
</table>
</div>
</div>
</body>
</html>
Map.js:
function initialize() {
var myOptions = {
center: new google.maps.LatLng(53.43057, -2.14727),
zoom: 18,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
var tempIcon = new google.maps.MarkerImage(
"http://labs.google.com/ridefinder/images/mm_20_green.png",
new google.maps.Size(12, 20),
new google.maps.Size(6, 20)
);
var newShadow = new google.maps.MarkerImage(
"http://labs.google.com/ridefinder/images/mm_20_shadow.png",
new google.maps.Size(22, 20),
new google.maps.Point(13, 13)
);
var tempMarker = new google.maps.Marker();
tempMarker.setOptions({
icon: tempIcon,
shadow: newShadow,
draggable: true
});
var latlngs = new google.maps.MVCArray();
var displayPath = new google.maps.Polyline({
map: map,
strokeColor: "#FF0000",
strokeOpacity: 1.0,
strokeWeight: 2,
path: latlngs
});
var lastEast;
var lastNorth;
function showTempMarker(e) {
//Pythagorean distance calculates the length of the hypotenuse (the sloping side)
//of a right angle triangle. Plain (cartesian) coordinates are all right angle triangles.
//The length of the hypotenuse is always the distance between two coordinates.
//One side of the triangle is the difference in east coordinate and the other is
//the difference in north coordinates
function pythagorasDistance(E, N) {
if (lastEast) {
if (lastEast) {
//difference in east coordinates. We don't know what direction we are going so
//it could be a negative number - so just take the absolute value (ie - get rid of any minus sign)
var EastDistance = Math.abs(E - lastEast);
//difference in north coordinates
var NorthDistance = Math.abs(N - lastNorth);
//take the power
var EastPower = Math.pow(EastDistance, 2);
var NorthPower = Math.pow(NorthDistance, 2);
//add them together and take the square root
var pythagorasDistance = Math.sqrt(EastPower + NorthPower );
//round the answer to get rid of ridiculous decimal places (we're not measuring to the neares millimetre)
var result = Math.floor(pythagorasDistance);
document.getElementById('txtDistance').value = result;
}
}
}
function calcCatesian(degLat, degLng) {
var OSGBLL = LL.convertWGS84toOSGB36(new LatLon(degLat, degLng));
var EN = LL.LatLongToOSGrid(OSGBLL);
document.getElementById('txtEast').value = EN.east;
document.getElementById('txtNorth').value = EN.north;
pythagorasDistance(EN.east, EN.north);
lastEast = EN.east;
lastNorth = EN.north;
}
tempMarker.setPosition(e.latLng);
var lat = e.latLng.lat();
var lng = e.latLng.lng();
document.getElementById('txtLatitude').value = lat;
document.getElementById('txtLongitude').value = lng;
calcCatesian(lat, lng);
google.maps.event.addListener(tempMarker, "drag", function() {
document.getElementById('txtLatitude').value = tempMarker.getPosition().lat();
document.getElementById('txtLongitude').value = tempMarker.getPosition().lng();
calcCatesian(lat, lng);
});
tempMarker.setMap(map);
var newLocation = new google.maps.LatLng(lat, lng);
latlngs.push(newLocation);
displayPath.setPath(latlngs);
}
google.maps.event.addListener(map, "click", showTempMarker);
}
// ---- the following are duplicated from LatLong.html ---- //
/*
* construct a LatLon object: arguments in numeric degrees & metres
*
* note all LatLong methods expect & return numeric degrees (for lat/long & for bearings)
*/
function LatLon(lat, lon, height) {
if (arguments.length < 3)
height = 0;
this.lat = lat;
this.lon = lon;
this.height = height;
}
function setPrototypes() {
/*
* represent point {lat, lon} in standard representation
*/
LatLon.prototype.toString = function() {
return this.lat.toLat() + ', ' + this.lon.toLon();
}
// extend String object with method for parsing degrees or lat/long values to numeric degrees
//
// this is very flexible on formats, allowing signed decimal degrees, or deg-min-sec suffixed by
// compass direction (NSEW). A variety of separators are accepted (eg 3ยบ 37' 09"W) or fixed-width
// format without separators (eg 0033709W). Seconds and minutes may be omitted. (Minimal validation
// is done).
String.prototype.parseDeg = function() {
if (!isNaN(this))
return Number(this); // signed decimal degrees without NSEW
var degLL = this.replace(/^-/, '').replace(/[NSEW]/i, ''); // strip off any sign or compass dir'n
var dms = degLL.split(/[^0-9.]+/); // split out separate d/m/s
for (var i in dms)
if (dms[i] == '')
dms.splice(i, 1);
// remove empty elements (see note below)
switch (dms.length) { // convert to decimal degrees...
case 3:
// interpret 3-part result as d/m/s
var deg = dms[0] / 1 + dms[1] / 60 + dms[2] / 3600;
break;
case 2:
// interpret 2-part result as d/m
var deg = dms[0] / 1 + dms[1] / 60;
break;
case 1:
// decimal or non-separated dddmmss
if (/[NS]/i.test(this))
degLL = '0' + degLL; // - normalise N/S to 3-digit degrees
var deg = dms[0].slice(0, 3) / 1 + dms[0].slice(3, 5) / 60 + dms[0].slice(5) / 3600;
break;
default:
return NaN;
}
if (/^-/.test(this) || /[WS]/i.test(this))
deg = -deg; // take '-', west and south as -ve
return deg;
}
// note: whitespace at start/end will split() into empty elements (except in IE)
// extend Number object with methods for converting degrees/radians
Number.prototype.toRad = function() { // convert degrees to radians
return this * Math.PI / 180;
}
Number.prototype.toDeg = function() { // convert radians to degrees (signed)
return this * 180 / Math.PI;
}
// extend Number object with methods for presenting bearings & lat/longs
Number.prototype.toDMS = function(dp) { // convert numeric degrees to deg/min/sec
if (arguments.length < 1)
dp = 0; // if no decimal places argument, round to int seconds
var d = Math.abs(this); // (unsigned result ready for appending compass dir'n)
var deg = Math.floor(d);
var min = Math.floor((d - deg) * 60);
var sec = ((d - deg - min / 60) * 3600).toFixed(dp);
// fix any nonsensical rounding-up
if (sec == 60) {
sec = (0).toFixed(dp);
min++;
}
if (min == 60) {
min = 0;
deg++;
}
if (deg == 360)
deg = 0;
// add leading zeros if required
if (deg < 100)
deg = '0' + deg;
if (deg < 10)
deg = '0' + deg;
if (min < 10)
min = '0' + min;
if (sec < 10)
sec = '0' + sec;
return deg + '\u00B0' + min + '\u2032' + sec + '\u2033';
}
Number.prototype.toLat = function(dp) { // convert numeric degrees to deg/min/sec latitude
return this.toDMS(dp).slice(1) + (this < 0 ? 'S' : 'N'); // knock off initial '0' for lat!
}
Number.prototype.toLon = function(dp) { // convert numeric degrees to deg/min/sec longitude
return this.toDMS(dp) + (this > 0 ? 'E' : 'W');
}
/*
* extend Number object with methods for converting degrees/radians
*/
Number.prototype.toRad = function() { // convert degrees to radians
return this * Math.PI / 180;
}
Number.prototype.toDeg = function() { // convert radians to degrees (signed)
return this * 180 / Math.PI;
}
/*
* pad a number with sufficient leading zeros to make it w chars wide
*/
Number.prototype.padLZ = function(w) {
var n = this.toString();
for (var i = 0; i < w - n.length; i++)
n = '0' + n;
return n;
}
};
setPrototypes();
LL = function() {
// ellipse parameters
var e = {
WGS84: {
a: 6378137,
b: 6356752.3142,
f: 1 / 298.257223563
},
Airy1830: {
a: 6377563.396,
b: 6356256.910,
f: 1 / 299.3249646
}
};
// helmert transform parameters
var h = {
WGS84toOSGB36: {
tx: -446.448,
ty: 125.157,
tz: -542.060, // m
rx: -0.1502,
ry: -0.2470,
rz: -0.8421, // sec
s: 20.4894
}, // ppm
OSGB36toWGS84: {
tx: 446.448,
ty: -125.157,
tz: 542.060,
rx: 0.1502,
ry: 0.2470,
rz: 0.8421,
s: -20.4894
}
};
return {
convertOSGB36toWGS84: function(p1) {
var p2 = this.convert(p1, e.Airy1830, h.OSGB36toWGS84, e.WGS84);
return p2;
},
convertWGS84toOSGB36: function(p1) {
var p2 = this.convert(p1, e.WGS84, h.WGS84toOSGB36, e.Airy1830);
return p2;
},
convert: function(p1, e1, t, e2) {
// -- convert polar to cartesian coordinates (using ellipse 1)
p1.lat = p1.lat.toRad();
p1.lon = p1.lon.toRad();
var a = e1.a, b = e1.b;
var sinPhi = Math.sin(p1.lat), cosPhi = Math.cos(p1.lat);
var sinLambda = Math.sin(p1.lon), cosLambda = Math.cos(p1.lon);
var H = p1.height;
var eSq = (a * a - b * b) / (a * a);
var nu = a / Math.sqrt(1 - eSq * sinPhi * sinPhi);
var x1 = (nu + H) * cosPhi * cosLambda;
var y1 = (nu + H) * cosPhi * sinLambda;
var z1 = ((1 - eSq) * nu + H) * sinPhi;
// -- apply helmert transform using appropriate params
var tx = t.tx, ty = t.ty, tz = t.tz;
var rx = t.rx / 3600 * Math.PI / 180; // normalise seconds to radians
var ry = t.ry / 3600 * Math.PI / 180;
var rz = t.rz / 3600 * Math.PI / 180;
var s1 = t.s / 1e6 + 1; // normalise ppm to (s+1)
// apply transform
var x2 = tx + x1 * s1 - y1 * rz + z1 * ry;
var y2 = ty + x1 * rz + y1 * s1 - z1 * rx;
var z2 = tz - x1 * ry + y1 * rx + z1 * s1;
// -- convert cartesian to polar coordinates (using ellipse 2)
a = e2.a, b = e2.b;
var precision = 4 / a; // results accurate to around 4 metres
eSq = (a * a - b * b) / (a * a);
var p = Math.sqrt(x2 * x2 + y2 * y2);
var phi = Math.atan2(z2, p * (1 - eSq)), phiP = 2 * Math.PI;
while (Math.abs(phi - phiP) > precision) {
nu = a / Math.sqrt(1 - eSq * Math.sin(phi) * Math.sin(phi));
phiP = phi;
phi = Math.atan2(z2 + eSq * nu * Math.sin(phi), p);
}
var lambda = Math.atan2(y2, x2);
H = p / Math.cos(phi) - nu;
return new LatLon(phi.toDeg(), lambda.toDeg(), H);
},
/*
* convert numeric grid reference (in metres) to standard-form grid ref
*/
gridrefNumToLet: function(e, n, digits) {
// get the 100km-grid indices
var e100k = Math.floor(e / 100000), n100k = Math.floor(n / 100000);
if (e100k < 0 || e100k > 6 || n100k < 0 || n100k > 12)
return '';
// translate those into numeric equivalents of the grid letters
var l1 = (19 - n100k) - (19 - n100k) % 5 + Math.floor((e100k + 10) / 5);
var l2 = (19 - n100k) * 5 % 25 + e100k % 5;
// compensate for skipped 'I' and build grid letter-pairs
if (l1 > 7)
l1++;
if (l2 > 7)
l2++;
var letPair = String.fromCharCode(l1 + 'A'.charCodeAt(0), l2 + 'A'.charCodeAt(0));
// strip 100km-grid indices from easting & northing, and reduce precision
e = Math.floor((e % 100000) / Math.pow(10, 5 - digits / 2));
n = Math.floor((n % 100000) / Math.pow(10, 5 - digits / 2));
var gridRef = letPair + e.padLZ(digits / 2) + n.padLZ(digits / 2);
return gridRef;
},
LatLongToOSGrid: function(p) {
var lat = p.lat.toRad(), lon = p.lon.toRad();
var a = 6377563.396, b = 6356256.910; // Airy 1830 major & minor semi-axes
var F0 = 0.9996012717; // NatGrid scale factor on central meridian
var lat0 = (49).toRad(), lon0 = (-2).toRad(); // NatGrid true origin
var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres
var e2 = 1 - (b * b) / (a * a); // eccentricity squared
var n = (a - b) / (a + b), n2 = n * n, n3 = n * n * n;
var cosLat = Math.cos(lat), sinLat = Math.sin(lat);
var nu = a * F0 / Math.sqrt(1 - e2 * sinLat * sinLat); // transverse radius of curvature
var rho = a * F0 * (1 - e2) / Math.pow(1 - e2 * sinLat * sinLat, 1.5); // meridional radius of curvature
var eta2 = nu / rho - 1;
var Ma = (1 + n + (5 / 4) * n2 + (5 / 4) * n3) * (lat - lat0);
var Mb = (3 * n + 3 * n * n + (21 / 8) * n3) * Math.sin(lat - lat0) * Math.cos(lat + lat0);
var Mc = ((15 / 8) * n2 + (15 / 8) * n3) * Math.sin(2 * (lat - lat0)) * Math.cos(2 * (lat + lat0));
var Md = (35 / 24) * n3 * Math.sin(3 * (lat - lat0)) * Math.cos(3 * (lat + lat0));
var M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc
var cos3lat = cosLat * cosLat * cosLat;
var cos5lat = cos3lat * cosLat * cosLat;
var tan2lat = Math.tan(lat) * Math.tan(lat);
var tan4lat = tan2lat * tan2lat;
var I = M + N0;
var II = (nu / 2) * sinLat * cosLat;
var III = (nu / 24) * sinLat * cos3lat * (5 - tan2lat + 9 * eta2);
var IIIA = (nu / 720) * sinLat * cos5lat * (61 - 58 * tan2lat + tan4lat);
var IV = nu * cosLat;
var V = (nu / 6) * cos3lat * (nu / rho - tan2lat);
var VI = (nu / 120) * cos5lat * (5 - 18 * tan2lat + tan4lat + 14 * eta2 - 58 * tan2lat * eta2);
var dLon = lon - lon0;
var dLon2 = dLon * dLon, dLon3 = dLon2 * dLon, dLon4 = dLon3 * dLon, dLon5 = dLon4 * dLon, dLon6 = dLon5 * dLon;
var N = I + II * dLon2 + III * dLon4 + IIIA * dLon6;
var E = E0 + IV * dLon + V * dLon3 + VI * dLon5;
E = Math.floor(E * 100) / 100;
N = Math.floor(N * 100) / 100;
//return this.gridrefNumToLet(E, N, 8);
return { east: E, north: N }
;
}
}
} ();
I think you are looking for something similar to this function, which returns a point a certain percentage along a given line. Unfortuntaely I'm not aware offhand of a javascript port of this function, but it's probably worth a look.
Meanwhile here's a quick concept for a hack that may give you enough detail for your needs:
Start with your polyline (for simplicity's sake let's assume you have just a single path, which is a series of LatLngs)
When you want to estimate where the person is, take their percentage along the path as determined by the time (for example 8am they are 50% along)
Now for each LatLng in your path, calculate it's fractional distance along the total length of the path by adding the distances between LatLngs (you can use the computeLength for the path, and computeDistanceBetween for each LatLng)
As soon as you get to a fraction >50% along (in this case), you know the person is inbetween this LatLng and the previous one. You can then calculate exactly how far along as well to place the point exactly if you wish, or just skip this step if it's a pretty short segment and put their marker at one of these LatLngs.
The above is the general concept, but of course you should optimize by precomputing the percentage distances for each LatLng just once for each Path and storing that in a separate object, and keep track of your last index in the path so you don't start from the beginning next time you calculate their distance along, etc.
Hope this helps.
I think you pretty much got the answer already, except for one little detail that I haven't seen anybody mention explicitly: you need to use the encoded polyline from the steps to get to the point where you will be interpolating between two points that are close enough so that the straight line between them is a good approximation to the shape of the route.
Let's see an example:
Driving directions from Madrid to Toledo:
http://maps.googleapis.com/maps/api/directions/json?origin=Toledo&destination=Madrid®ion=es&sensor=false
The median point (half way through the whole route) would be somewhere in the biggest step which is nearly 50 km long:
{
"distance" : {
"text" : "49.7 km",
"value" : 49697
},
"duration" : {
"text" : "26 mins",
"value" : 1570
},
"end_location" : {
"lat" : 40.26681000000001,
"lng" : -3.888580
},
"html_instructions" : "Continue onto \u003cb\u003eAP-41\u003c/b\u003e\u003cdiv style=\"font-size:0.9em\"\u003eToll road\u003c/div\u003e",
"polyline" : {
"points" : "kdtrFj`~VEkA[oLEy#Cs#KeAM_BOgBy#eGs#iFWgBS{AQ{AQcBMuAM}BKcBGiCM_EGaFKgEKeDOqC[yFWwCIi#Is#i#_EaAsFy#aEeAcEsAqEuAeE]w#k#qAeAcCm#sA}#cBgB{CaBgCiEyFuB}BsBuBaDwCa#]_CsBmD_Di#g#aAaAs#s#{#aAU[q#w#s#{#wE{Ge#s#_#o#a#q#o#kAmAaCaAqBeAcCw#kBy#yBw#_Cg#aB}AkEoBgFyDwIcBwDa#iAcBwCgAcBsAiBy#uAeCiDqBeCaB_BuA_BiDeC{#o#u#k#cD{B}#g#y#_#k#]cD_BkD{AwBu#cA]eAYsD_AuE_AqBY{Du#_BW_AQm#K_AOiAWuAa#qA_#mA_#aBi#MGOGMGy#[OI_Bw#uBkAkDsBaAg#oEiCg#YeEcC}As#m#WqCmAmEeBuAe#mDeAqCq#qCm#iB]eBY}BYeCYi#C{#IgBMwCMmAEmAC{A#mB?wBFsBBiBHeAJcBNgBNcBRcC\\qCd#sBb#eAXeBd#uBn#{Bp#uAd#}B~#gD|AmCrA{#j#}Az#kDvB{AbAqA|#}AnAaAx#aAv#}AtAaA`AwClD{HzImH~IiF|F{#~#o#v#qAhAsAhAqA`AyAbA{A~#m#Xw#h#gCnAiBz#uAt#wAh#}#XwCz#qBd#qCf#gBXkBTw#FaCTmDPsADwDJgCFoFXwDXoDb#gCd#wB`#gCh#_D~#qC~#gC~#wChAmDxAaC|#sCbAgEzAuGbBaB`#cFdAo#NoAXiC^cD^oDXmEToBJkABA?Q#_##yDBwBAoB?wBEm#A_CKO?_EUcD[eEe#uAQq#I]GqDs#e#Ii#K_#IOEgAWk#MsBi#mBg#WIc#MkEwA{Am#yB}#yDcB_CgAsAs#eB}#aBaAiD{ByCqBkA}#mA}#uAiAwCcCyAoAmEiE{#aAgAyA{#cAmAuAaBsBkAyAgBcCwAoBwAwByCyEyBmD{BsDgCaEuA{Co#eAuC_Fs#iA{#iAo#_A{A}BkGmHmAwAeBaBcBeBcHsGmCkCyCyCm#m#m#m#_A_AkDaDkCiCkDgD}#y#iE_FcC}CkBkCw#gAs#cAcC{D_BmCe#}#}AuCsAkCiCqFkAgCe#kAgAeCw#mBuAaDWg#g#iAEE{AqCq#kA_#k#oCwDuAeBoAqAUQ_#YMOm#k#e#g#_#]u#m#k#a#i#_#YOSOe#[k#_#w#c#a#Ok#WyAo#y#[eBm#}Ac#{Bk#kASwBS}AMyBO}BGuGJsAJmCRuDn#iCn#}C`AwBx#kB|#{BfAmBfAkCdBaCzA_BpA_BlAuAnAeCdCuD`EgBzBgClDyBrD{AtCy#bB_#b#Wl#c#`AWr#KVSd#MXIPGPSd#MZQb#MZ_#bAm#dBQd#[`A_#jAGRIVGPGVIVSt#k#xBe#jBKd#U`As#nDKb#Q`AgAtHADM~ACNK|#SpBQhBKnBKxACv#OhDO~EGdFAhA#|CC~B?rA#vB#hB#dD#vBAbCCvCEzBGpBEpAEpAKrBI~#Ej#Et#WxCa#vDYrBq#bEQfAUnAy#vD}BtJUx#K^wBfGwCdHqBxD_B`CsBbDwCnEgCrCuCzCyBpBiCzBmBvAaC|AuAv#eAj#OHuAp#}#^oBz#eExAgBb#uFpAaC`#mDb#iCRmADaDFy#B}E?aEQaJcAuB]uA[uBc#kDu#eD{#{Cs#iDu#wBe#eEo#{BQwDYiEMkBEaA?aA?aB?_B#aBDsBJwDT{Ed#o#JcALi#JcBVuBb#sA\\eAV{Ct#aA\\cBh#_Bh#mAb#sCpAwDhB_CpA}BvAg#\\mAr#aBjAqBzAgBxAeBzAoBlB_C~BgAhAUV[`#uCjD_BvBQVGDw#fAiAdBeAdBuC`Fe#|#wCbGU^]r#]r#oAvCeApCQZKXo#vBu#|B}#zCoAjEg#vBc#~AOt#k#~Bu#jD}#tDc#zAW`AWv#Ux#[bAw#xBuAhD{#jByCvFcClDwBvCkCrCgCdCsBzAgBnAkBjAkBbAmAj#gAf#mDjAsBl#sBf#uBb#oBXaBLiBNaADgABuABkBCgEUuCU}Ck#_Cg#kCu#yB{#{BaAqBaA}#i#kAq#OIe#[g#_#a#WmAaAeAy#iAeA}#_AmAsAu#w#{#gA_#e#o#cAk#_Ay#sAYm#_#m#_#u#]q#u#cBi#eA[y#Se#g#iAYs#_#oAMi#[aAa#uA_#wAS}#a#cB]wAWqAI]CKgAyDu#yCo#eCgAmDu#cCmAoDmBwEgAaCa#_AcByCqDwGiBkCw#iAgBaCkAoAiC{CkBiBuAsAoBcBeEaD}BaBs#c#gCyAKEoBgAuAk#eBy#oAe#uCcAgBo#mD_AkCk#kAUsASgAQeAIm#ImCW_E[_FWwCSkBMuAM[E{#IGAmBUmCc#}#QcAUsA_#cAWgBi#w#UwAk#a#MmAi#eAe#yBiAk#[SMKEw#g#k#_#w#e#aC_Bc#]yBgBeCmB}BmB}BsB_BoAw#o#s#g#oDiCuA{#_BcAgAq#uBsAaAc#{#_#y#_#sAm#yD}AeDgAsDiAeCeAaCy#iCgAiBcAeAc#c#OyE{A{Ag#y#YaBm#{Aq#gAm#i#][YMMYWaGwGi#y#{A{B_A{Aw#iAs#iA_A}AcAaBsAiBeBkBoAiAaBsA{AcAoAq#iB}#qBu#wBk#cBa#qAW}#I}CSwEBiDVcBR_BXiCr#gBp#wBbAkAp#qA|#y#l#wCjC{#~#gArAmCzDiAnBm#tAu#jBq#pBmAvDwAnFw#bCELq#tBw#pBgAdCS\\qCnF_#f#yBtC{AhBqAvAkBhB{ArAyAhAg#Ze#Z{BrAiBz#SHy#^OFs#X_AZ_Bd#WJaDr#}B\\eBPoBNq#F_##iC#gACu#Ai#Ey#IQC_B[}Bo#_#Ks#S"
},
"start_location" : {
"lat" : 39.92150,
"lng" : -3.927260
},
"travel_mode" : "DRIVING"
},
I'm afraid this polyline is too long (2856 characters) to display it directly in the Static Maps API, but that's not necessary, it'd just be a nice way to show the polyline right here. Anyway, you can use the Interactive Polyline Encoder Utility to paste this encoded polyline (after replacing \\ with \) to see it.
Now, let's imagine you need to find the point in this route that is exactly 20 km. from the start of this step. That is, the point between start_location and end_location that is 20,000 meters from start_location, along the route defined by the above polyline.
In your app, you'd use the Encoding Methods in the Geometry Library (which you need to load explicitly) to decode this polyline into the whole array of LatLng points. You'd then use the computeDistanceBetween between each two adjacent points to figure out which one is the first LatLng point (Y) in that polyline that is more than 20,000 from start_location. Then you take that point plus the previous one (X) and do the straight-line interpolation between X and Y. At this point, you can count on the straight line between these two points to be a reasonable approximation to the shape of the route.
Mind you, this is a fairly detailed calculation that may turn up too expensive. If you hit performance issues due to the big size of the polyline, you can simplify it by dropping part of the points. Doing this simplification smartly may be, again, expensive though, so I'd keep it simple ;)
I would say it's doable. :-) This is how I visualize it, but I haven't tested any of it.
First define a PolyLine based on the "guessed route" which the users are supposed to take. Store that in a local variable in your js. It will be handy to have lots of points, to make the estimated point better.
Then set up an interval (window.setInterval) to check for updates in users positions, say every 30 seconds. If the position is newer than the interval - display the known position and draw a solid line from the last known position, creating a line of known data. (setPath)
When no new data is present, do a simple velocity calculation using the latest few known points.
Using the velocity and the timeframe calculate an estimated travel distance.
Using the calculated distance, load your estimated route object and "walk" point by point in the "guessed route" until the pseudo walked distance is almost equal to your estimate. Then return the point where you have reached the right distance.
Draw a dotted line from the last known location to the guessed one.
Good luck!
PS.
A PolyLine is a line object consisting of many paths and waypoints
Calculate lengths between points using geometry spherical namespaces function "computeLength"
This site: http://www.gmap-pedometer.com/ may be of interest, as it lets the user draw routes, and adds mile or km markers along the route, so it must be doing a similar calculation to the one you require.