Related
I'm working on flutter project using google-maps-flutter plugin, and I want to check if the user location is inside the polygon that I created on the map. There is an easy way using JavaScript api (containsLocation() method) but for flutter I only found a third party plugin,google_map_polyutil, which is only for android and I get a security worming when I run my app. Is there another way to do so??
I found this answer and just modified some minor things to work with dart, I ran a test on a hardcoded polygon. The list _area is my polygon and _polygons is required for my mapcontroller.
final Set<Polygon> _polygons = {};
List<LatLng> _area = [
LatLng(-17.770992200, -63.207739700),
LatLng(-17.776386600, -63.213576200),
LatLng(-17.778348200, -63.213576200),
LatLng(-17.786848100, -63.214262900),
LatLng(-17.798289700, -63.211001300),
LatLng(-17.810547700, -63.200701600),
LatLng(-17.815450600, -63.185252100),
LatLng(-17.816267800, -63.170660900),
LatLng(-17.800741300, -63.153838100),
LatLng(-17.785867400, -63.150919800),
LatLng(-17.770501800, -63.152636400),
LatLng(-17.759712400, -63.160361200),
LatLng(-17.755952300, -63.169802600),
LatLng(-17.752519100, -63.186625400),
LatLng(-17.758404500, -63.195551800),
LatLng(-17.770992200, -63.206538100),
LatLng(-17.770996000, -63.207762500)];
The function ended like this:
bool _checkIfValidMarker(LatLng tap, List<LatLng> vertices) {
int intersectCount = 0;
for (int j = 0; j < vertices.length - 1; j++) {
if (rayCastIntersect(tap, vertices[j], vertices[j + 1])) {
intersectCount++;
}
}
return ((intersectCount % 2) == 1); // odd = inside, even = outside;
}
bool rayCastIntersect(LatLng tap, LatLng vertA, LatLng vertB) {
double aY = vertA.latitude;
double bY = vertB.latitude;
double aX = vertA.longitude;
double bX = vertB.longitude;
double pY = tap.latitude;
double pX = tap.longitude;
if ((aY > pY && bY > pY) || (aY < pY && bY < pY) || (aX < pX && bX < pX)) {
return false; // a and b can't both be above or below pt.y, and a or
// b must be east of pt.x
}
double m = (aY - bY) / (aX - bX); // Rise over run
double bee = (-aX) * m + aY; // y = mx + b
double x = (pY - bee) / m; // algebra is neat!
return x > pX;
}
Notice the polygons property and the onTap method. I was trying to check if the marker created in my map was inside my polygon:
GoogleMap(
initialCameraPosition: CameraPosition(
target: target, //LatLng(0, 0),
zoom: 16,
),
zoomGesturesEnabled: true,
markers: markers,
polygons: _polygons,
onMapCreated: (controller) =>
_mapController = controller,
onTap: (latLng) {
_getAddress(latLng);
},
)
Then i just used the following call in my _getAddress method:
_checkIfValidMarker(latLng, _area);
I hope it helps you to create what you need.
The easiest way to use it - https://pub.dev/packages/maps_toolkit
with isLocationOnPath method.
L. Chi's answer really help.
But due to I have pretty close points, rayCastIntersect might have wrong boolean return if aX is equal to bX
Therefore, I just add aX == bX condition check before calculate m then it works.
bool rayCastIntersect(LatLng tap, LatLng vertA, LatLng vertB) {
double aY = vertA.latitude;
double bY = vertB.latitude;
double aX = vertA.longitude;
double bX = vertB.longitude;
double pY = tap.latitude;
double pX = tap.longitude;
if ((aY > pY && bY > pY) || (aY < pY && bY < pY) || (aX < pX && bX < pX)) {
return false; // a and b can't both be above or below pt.y, and a or
// b must be east of pt.x
}
if (aX == bX) {
return true;
}
double m = (aY - bY) / (aX - bX); // Rise over run
double bee = (-aX) * m + aY; // y = mx + b
double x = (pY - bee) / m; // algebra is neat!
return x > pX;
}
The easiest way to use it - https://pub.dev/packages/maps_toolkit
with PolygonUtil.containsLocation - computes whether the given point lies inside the specified polygon.
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'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.
I'm trying to check if a point is in polygon.
At the moment I have try with this function
pointInPolygon:function (point,polygon){
var i;
var j=polygon.length-1;
var inPoly=false;
var lon = point.longitude;
var lat = point.latitude;
for (i=0; i<polygon.length; i++)
{
if (polygon[i][0]<lon && polygon[j][0]>=lon|| polygon[j][0]<lon && polygon[i][0]>=lon){
if (polygon[i][0]+(lon-polygon[i][0])/(polygon[j][0]-polygon[i][0])*(polygon[j][1]-polygon[i][1])<lat){
inPoly=!inPoly;
}
}
j=i;
}
return inPoly;
}
... this function is seems to work on simple polygon ( http://jsfiddle.net/zTmr7/3/ ) but it won't work for me...
here is sample data of a polygon:
polygon: Array[14]
Array[2]
0: "-120.190625"
1: "29.6614549946937"
Array[2]
0: "-116.87275390625"
1: "32.6320990313992"
Array[2]
0: "-116.60908203125"
1: "34.0363970332393"
Array[2]
0: "-120.89375"
1: "41.9203747676428"
Array[2]
0: "-114.74140625"
1: "45.784484644005"
Array[2]
0: "-115.971875"
1: "48.6489780115889"
Array[2]
0: "-132.758984375"
1: "59.9891712248332"
Array[2]
0: "-162.5099609375"
1: "68.919753529737"
Array[2]
0: "-168.6623046875"
1: "68.9828872543805"
Array[2]
0: "-168.4865234375"
1: "64.2551601036027"
Array[2]
0: "-179.874356794357"
1: "51.0915874974707"
Array[2]
0: "-179.999916362762"
1: "13.1823178795562"
Array[2]
0: "-143.8771484375"
1: "19.9962034117847"
Array[2]
0: "-120.190625"
1: "29.6614549946937"
Maybe you can help... thanks in advance
PS. solution must be especially for Bing maps or universal solution...
The Google maps API does not already provide a method for checking points in polygons. After researching a bit I stumbled across the Ray-casting algorithm which will determine if an X-Y coordinate is inside a plotted shape. This will translate to latitude and longitude. The following extends the google.maps.polygon.prototype to use this algorithm. Simply include this code at a point in the code after google.maps has loaded:
google.maps.Polygon.prototype.Contains = function(point) {
var crossings = 0, path = this.getPath();
// for each edge
for (var i=0; i < path.getLength(); i++) {
var a = path.getAt(i),
j = i + 1;
if (j >= path.getLength()) {
j = 0;
}
var b = path.getAt(j);
if (rayCrossesSegment(point, a, b)) {
crossings++;
}
}
// odd number of crossings?
return (crossings % 2 == 1);
function rayCrossesSegment(point, a, b) {
var px = point.lng(),
py = point.lat(),
ax = a.lng(),
ay = a.lat(),
bx = b.lng(),
by = b.lat();
if (ay > by) {
ax = b.lng();
ay = b.lat();
bx = a.lng();
by = a.lat();
}
// alter longitude to cater for 180 degree crossings
if (px < 0) { px += 360 };
if (ax < 0) { ax += 360 };
if (bx < 0) { bx += 360 };
if (py == ay || py == by) py += 0.00000001;
if ((py > by || py < ay) || (px > Math.max(ax, bx))) return false;
if (px < Math.min(ax, bx)) return true;
var red = (ax != bx) ? ((by - ay) / (bx - ax)) : Infinity;
var blue = (ax != px) ? ((py - ay) / (px - ax)) : Infinity;
return (blue >= red);
}
};
Here we have extended the functionality of google.maps.Polygon by defining a function with name ‘Contains’ which can be used to determine whether the latitude longitude provided in function parameter are within the polygon or not. Here we make use of Ray-casting algorithm and developed a function using the same. After doing this much of exercise now, we can check a point as follows:
var point = new google.maps.LatLng(52.05249047600099, -0.6097412109375); var polygon = new google.maps.Polygon({path:[INSERT_PATH_ARRAY_HERE]}); if (polygon.Contains(point)) { // point is inside polygon }
For complete code and demo please go to: http://counsellingbyabhi.blogspot.in/2013/01/google-map-check-whether-point-latlong.html
The first if statement looks good - you're checking to see if the longitude of the point lies within the longitude of the polygon segment.
The second if should be interpolating the intercept of the segment with the exact longitude of the point, and determining if that intercept is above or below the point. I don't think that is what it is doing, due to a simple typo.
if (polygon[i][1]+(lon-polygon[i][0])/(polygon[j][0]-polygon[i][0])*(polygon[j][1]-polygon[i][1])<lat){
^
You should also include a separate case when polygon[i][0]==polygon[j][0] so that you don't get a divide-by-zero error.
You can use my clone of the libkml variant which I have mirrored in github here: https://github.com/gumdal/libkml-pointinpolygon
With help of the author of this open source, a module is designed which will indicate whether the given point is inside the KML polygon or not. Make sure that you check the branch "libkml-git" and not the "master" branch of the git sources. The class you would be interested in is "pointinpolygon.cc". It is C++ source code which you can include inside your project and build it along with your project.
Edit - The solution for point in polygon problem is independent of what map it is overlayed on.
true|false = google.maps.geometry.poly.containsLocation(googlePoint, googlePoly);
I have a point (X,Y) and I want to create a square , Google maps LatLngBounds object so to make geocode requests bias only into this LatLngBound region.
How can I create such a LatLngBounds square with center the given point? I have to find the NE and SW point. But how can I find it given a distance d and a point (x,y)?
Thanks
You can also getBounds from a radius defined as a circle and leave the trig to google.
new google.maps.Circle({center: latLng, radius: radius}).getBounds();
well that's very complicated. for a rough box try this:
if (typeof(Number.prototype.toRad) === "undefined") {
Number.prototype.toRad = function() {
return this * Math.PI / 180;
}
}
if (typeof(Number.prototype.toDeg) === "undefined") {
Number.prototype.toDeg = function() {
return this * 180 / Math.PI;
}
}
var dest = function(lat,lng,brng, dist) {
this._radius = 6371;
dist = typeof(dist) == 'number' ? dist : typeof(dist) == 'string' && dist.trim() != '' ? +dist : NaN;
dist = dist / this._radius;
brng = brng.toRad();
var lat1 = lat.toRad(),
lon1 = lng.toRad();
var lat2 = Math.asin(Math.sin(lat1) * Math.cos(dist) + Math.cos(lat1) * Math.sin(dist) * Math.cos(brng));
var lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(dist) * Math.cos(lat1), Math.cos(dist) - Math.sin(lat1) * Math.sin(lat2));
lon2 = (lon2 + 3 * Math.PI) % (2 * Math.PI) - Math.PI;
return (lat2.toDeg() + ' ' + lon2.toDeg());
}
var northEastCorner = dest(centreLAT,centreLNG,45,10);
var southWestCorner = dest(centreLAT,centreLNG,225,10);
EDIT
The above was they way to do it way back in 2011 when I wrote it. These days the google maps api has come on a loooong way. The answer by #wprater is much neater and uses some of the newer api methods.
Wouldn't it work to simply add/subtract d/2 to your x/y locations?
Given x,y as the center point:
NW = x-(d/2),y-(d/2)
SE = x+(d/2),y+(d/2)
Don't trust me on this, though - I am terrible at math :)
This assumes d as a "diameter", rather than a radius. If "d" is the radius, don't bother with the divide-by-two part.