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.
Related
This question already has answers here:
How to deal with floating point number precision in JavaScript?
(47 answers)
Closed 8 years ago.
I have a large amount of numeric values y in javascript. I want to group them by rounding them down to the nearest multiple of x and convert the result to a string.
How do I get around the annoying floating point precision?
For example:
0.2 + 0.4 = 0.6000000000000001
Two things I have tried:
>>> y = 1.23456789
>>> x = 0.2
>>> parseInt(Math.round(Math.floor(y/x))) * x;
1.2000000000000002
and:
>>> y = 1.23456789
>>> x = 0.2
>>> y - (y % x)
1.2000000000000002
From this post: How to deal with floating point number precision in JavaScript?
You have a few options:
Use a special datatype for decimals, like decimal.js
Format your result to some fixed number of significant digits, like this:
(Math.floor(y/x) * x).toFixed(2)
Convert all your numbers to integers
You could do something like this:
> +(Math.floor(y/x)*x).toFixed(15);
1.2
Edit: It would be better to use big.js.
big.js
A small, fast, easy-to-use library for arbitrary-precision decimal arithmetic.
>> bigX = new Big(x)
>> bigY = new Big(y)
>> bigY.div(bigX).round().times(bigX).toNumber() // => 1.2
> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02
Quick solution:
var _cf = (function() {
function _shift(x) {
var parts = x.toString().split('.');
return (parts.length < 2) ? 1 : Math.pow(10, parts[1].length);
}
return function() {
return Array.prototype.reduce.call(arguments, function (prev, next) { return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift (next)); }, -Infinity);
};
})();
Math.a = function () {
var f = _cf.apply(null, arguments); if(f === undefined) return undefined;
function cb(x, y, i, o) { return x + f * y; }
return Array.prototype.reduce.call(arguments, cb, 0) / f;
};
Math.s = function (l,r) { var f = _cf(l,r); return (l * f - r * f) / f; };
Math.m = function () {
var f = _cf.apply(null, arguments);
function cb(x, y, i, o) { return (x*f) * (y*f) / (f * f); }
return Array.prototype.reduce.call(arguments, cb, 1);
};
Math.d = function (l,r) { var f = _cf(l,r); return (l * f) / (r * f); };
> Math.m(0.1, 0.2)
0.02
You can check the full explanation here.
Check out this link.. It helped me a lot.
http://www.w3schools.com/jsref/jsref_toprecision.asp
The toPrecision(no_of_digits_required) function returns a string so don't forget to use the parseFloat() function to convert to decimal point of required precision.
Tackling this task, I'd first find the number of decimal places in x, then round y accordingly. I'd use:
y.toFixed(x.toString().split(".")[1].length);
It should convert x to a string, split it over the decimal point, find the length of the right part, and then y.toFixed(length) should round y based on that length.
I have an Html5 canvas which i am drawing squares to.
The canvas itself is roughly the size of the window.
When i detect a click on a square i would like to translate the canvas so that the square is roughly in the center of the window. Any insights, hints, or straight-forward replies are welcome.
Here is what i tried so far:
If a square is at point (1000, 1000) I would simply translate the canvas (-1000, -1000). I know i need to add an offset so that it is centered in the window. However, the canvas always ends up off of the visible window (too far in the upper-left corner somewhere).
A more complex scenario:
Ultimately i would like to be able to center on a clicked object on a canvas that is transformed (rotated & skewed). I'm going for an isometric effect which seems to work really well. I'm wondering if this transformation affects the centering logic/math at all?
Transforming from screen to world and back
When working with non standard axis (or projections) such as isometrix it is always best to use a transformation matrix. It will cover every possible 2D projection with the same simple functions.
The coordinates of the iso world are called world coordinates. All you objects are stored as world coordinates. When you render them you project those coordinates to the screen coordinates using a transformation matrix.
The matrix, not a movie.
The matrix represents the direction and size in screen coordinates of the world
x and y axis and the screen location of the world origin (0,0)
For iso that is
x axis across 1 down 0.5
y axis across -1 down 0.5
z axis up 1 (-1 as up is the reverse of down) but this example does not use z
So the matrix as an array
const isoMat = [1,0.5,-1,0.5,0,0]; // ISO (pixel art) dimorphic projection
The first two are the x axis, the next two the y axis and the last two values are the screen coordinates of the origin.
Use the matrix to transform points
You apply a matrix to a point, this transforms the point from one coordinate system to another. You can also convert back via a inverse transform.
World to screen
You will need to convert from world coordinates to screen coordinates.
function worldToScreen(pos,retPos){
retPos.x = pos.x * isoMat[0] + pos.y * isoMat[2] + isoMat[4];
retPos.y = pos.x * isoMat[1] + pos.y * isoMat[3] + isoMat[5];
}
In the demo I ignore the origin as I set that at the center of the canvas at all times. Thus remove the origin from that function
function worldToScreen(pos,retPos){
retPos.x = pos.x * isoMat[0] + pos.y * isoMat[2];
retPos.y = pos.x * isoMat[1] + pos.y * isoMat[3];
}
Screen to world.
You will also need to convert from the screen coordinates to the world. For this you need to use the inverse transform. It's a bit like the inverse of multiply a * 2 = b is the inverse of b / 2 = a
There is a standard method for calculating the inverse matrix as follows
const invMatrix = []; // inverse matrix
// I call the next line cross, most call it the determinant which I
// think is stupid as it is effectively a cross product and is used
// like you would use a cross product. Anyways I digress
const cross = isoMat[0] * isoMat[3] - isoMat[1] * isoMat[2];
invMatrix[0] = isoMat[3] / cross;
invMatrix[1] = -isoMat[1] / cross;
invMatrix[2] = -isoMat[2] / cross;
invMatrix[3] = isoMat[0] / cross;
Then we have a function that converts from the screen x,y to the world position
function screenToWorld(pos,retPos){
const x = pos.x - isoMat[4];
const y = pos.y - isoMat[5];
retPos.x = x * invMatrix[0] + y * invMatrix[2];
retPos.y = x * invMatrix[1] + y * invMatrix[3];
}
So you get the mouse coords as screen pixels, use the above function to convert to world coords. Then you can use the world coords to find the object you are looking for.
To move a world object to the screen center you convert its coords to screen coords, add the position on the screen (the canvas center) and set the transform matrix origin to that location.
The demo
The demo creates a set of boxes in world coordinates. It sets the 2D context transform to the isoMat (isometric projection) via ctx.setTransform(
Every frame I convert the mouse screen coords to world coords then use that to check which box the mouse is over.
If the mouse button is down I then convert that box from world coords to screen and add the screen center. To smooth the step the new screen center is chased (smoothed)..
Well you should be able to work it out in the code, any problems ask in the comments.
const ctx = canvas.getContext("2d");
const moveSpeed = 0.4;
const boxMin = 20;
const boxMax = 50;
const boxCount = 100;
const boxArea = 2000;
// some canvas vals
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
const U = undefined;
// Helper function
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); };
const eachOf = (array, cb) => { var i = 0; const len = array.length; while (i < len && cb(array[i], i++, len) !== true ); };
const setOf = (count, cb) => {var a = [],i = 0; while (i < count) { a.push(cb(i ++)) } return a };
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min, max = min + (min = 0)) => Math.random() * (max - min) + min;
// mouse function and object
const mouse = {x : 0, y : 0, button : false, world : {x : 0, y : 0}}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
// boxes in world coordinates.
const boxes = [];
function draw(){
if(this.dead){
ctx.fillStyle = "rgba(0,0,0,0.5)";
ctx.fillRect(this.x,this.y,this.w,this.h);
}
ctx.strokeStyle = this.col;
ctx.globalAlpha = 1;
ctx.strokeRect(this.x,this.y,this.w,this.h);
// the rest is just overkill
if(this.col === "red"){
this.mr = 10;
}else{
this.mr = 1;
}
this.mc += (this.mr-this.m) * 0.45;
this.mc *= 0.05;
this.m += this.mc;
for(var i = 0; i < this.m; i ++){
const m = this.m * (i + 1);
ctx.globalAlpha = 1-(m / 100);
ctx.strokeRect(this.x-m,this.y-m,this.w,this.h);
}
}
// make random boxes.
function createBoxes(){
boxes.length = 0;
boxes.push(...setOf(boxCount,()=>{
return {
x : randI(cw- boxArea/ 2, cw + boxArea/2),
y : randI(ch- boxArea/ 2, ch + boxArea/2),
w : randI(boxMin,boxMax),
h : randI(boxMin,boxMax),
m : 5,
mc : 0,
mr : 5,
col : "black",
dead : false,
draw : draw,
isOver : isOver,
}
}));
}
// use mouse world coordinates to find box under mouse
function isOver(x,y){
return x > this.x && x < this.x + this.w && y > this.y && y < this.y + this.h;
}
var overBox;
function findBox(x,y){
if(overBox){
overBox.col = "black";
}
overBox = undefined;
eachOf(boxes,box=>{
if(box.isOver(x,y)){
overBox = box;
box.col = "red";
return true;
}
})
}
function drawBoxes(){
boxes.forEach(box=>box.draw());
}
// next 3 values control the movement of the origin
// rather than move instantly the currentPos chases the new pos.
const currentPos = {x :0, y : 0};
const newPos = {x :0, y : 0};
const chasePos = {x :0, y : 0};
// this function does the chasing
function updatePos(){
chasePos.x += (newPos.x - currentPos.x) * moveSpeed;
chasePos.y += (newPos.y - currentPos.y) * moveSpeed;
chasePos.x *= moveSpeed;
chasePos.y *= moveSpeed;
currentPos.x += chasePos.x;
currentPos.y += chasePos.y;
}
// ISO matrix and inverse matrix plus 2world and 2 screen
const isoMat = [1,0.5,-1,0.5,0,0];
const invMatrix = [];
const cross = isoMat[0] * isoMat[3] - isoMat[1] * isoMat[2];
invMatrix[0] = isoMat[3] / cross;
invMatrix[1] = -isoMat[1] / cross;
invMatrix[2] = -isoMat[2] / cross;
invMatrix[3] = isoMat[0] / cross;
function screenToWorld(pos,retPos){
const x = pos.x - isoMat[4];
const y = pos.y - isoMat[5];
retPos.x = x * invMatrix[0] + y * invMatrix[2];
retPos.y = x * invMatrix[1] + y * invMatrix[3];
}
function worldToScreen(pos,retPos){
retPos.x = pos.x * isoMat[0] + pos.y * isoMat[2];// + isoMat[4];
retPos.y = pos.x * isoMat[1] + pos.y * isoMat[3];// + isoMat[5];
}
// main update function
function update(timer){
// standard frame setup
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
if(w !== innerWidth || h !== innerHeight){
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
createBoxes();
}else{
ctx.clearRect(0,0,w,h);
}
ctx.fillStyle = "black";
ctx.font = "28px arial";
ctx.textAlign = "center";
ctx.fillText("Click on a box to center it.",cw,28);
// update position
updatePos();
isoMat[4] = currentPos.x;
isoMat[5] = currentPos.y;
// set the screen transform to the iso matrix
// all drawing can now be done in world coordinates.
ctx.setTransform(isoMat[0], isoMat[1], isoMat[2], isoMat[3], isoMat[4], isoMat[5]);
// convert the mouse to world coordinates
screenToWorld(mouse,mouse.world);
// find box under mouse
findBox(mouse.world.x, mouse.world.y);
// if mouse down and over a box
if(mouse.button && overBox){
mouse.button = false;
overBox.dead = true; // make it gray
// get the screen coordinates of the box
worldToScreen({
x:-(overBox.x + overBox.w/2),
y:-(overBox.y + overBox.h/2),
},newPos
);
// move it to the screen center
newPos.x += cw;
newPos.y += ch;
}
// forget what the following function does, think it does something like draw boxes, but I am guessing.. :P
drawBoxes();
requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas { position : absolute; top : 0px; left : 0px; }
<canvas id="canvas"></canvas>
I created a formula to form a grid on the google earth. I want to get the intersection point between the lat/long. Please tell me how we can get the intersection.I am using SharpKML lib for generating KML
for (int x = 90; x >= 0; x = x - 15)
{
Placemark placemark = new Placemark();
LineString line = new LineString();
CoordinateCollection co = new CoordinateCollection();
for (int i = 0; i <= 180; i = i + 15)
{
Vector cords = new Vector()
{
Latitude = x,
Longitude = i,
Altitude = 1000
};
co.Add(cords);
}
for (int i = -180; i <= 0; i = i + 15)
{
Vector cords = new Vector()
{
Latitude = x,
Longitude = i,
Altitude = 1000
};
co.Add(cords);
}
line.Coordinates = co;
placemark.Geometry = line;
document.AddFeature(placemark);
}
for (int x = -90; x <= 0; x = x + 15)
{
Placemark placemark = new Placemark();
LineString line = new LineString();
CoordinateCollection co = new CoordinateCollection();
for (int i = 0; i <= 180; i = i + 15)
{
Vector cords = new Vector()
{
Latitude = x,
Longitude = i,
Altitude = 1000
};
co.Add(cords);
}
for (int i = -180; i <= 0; i = i + 15)
{
Vector cords = new Vector()
{
Latitude = x,
Longitude = i,
Altitude = 1000
};
co.Add(cords);
}
line.Coordinates = co;
placemark.Geometry = line;
document.AddFeature(placemark);
}
for (int i = 0; i <= 180; i = i + 15)
{
Placemark placemark = new Placemark();
LineString line = new LineString();
CoordinateCollection co = new CoordinateCollection();
for (int x = 0; x <= 90; x = x + 15)
{
Vector cords = new Vector()
{
Latitude = x,
Longitude = i,
Altitude = 1000
};
co.Add(cords);
}
for (int x = -90; x <= 0; x = x + 15)
{
Vector cords = new Vector()
{
Latitude = x,
Longitude = i,
Altitude = 1000
};
co.Add(cords);
}
line.Coordinates = co;
placemark.Geometry = line;
document.AddFeature(placemark);
}
for (int i = -180; i <= 0; i = i + 15)
{
Placemark placemark = new Placemark();
LineString line = new LineString();
CoordinateCollection co = new CoordinateCollection();
for (int x = 0; x <= 90; x = x + 15)
{
Vector cords = new Vector()
{
Latitude = x,
Longitude = i,
Altitude = 1000
};
co.Add(cords);
}
for (int x = -90; x <= 0; x = x + 15)
{
Vector cords = new Vector()
{
Latitude = x,
Longitude = i,
Altitude = 1000
};
co.Add(cords);
}
line.Coordinates = co;
placemark.Geometry = line;
document.AddFeature(placemark);
}
Matthew is correct if the question is how you can find the intersection point of an arbitrary LineString object with your grid using C#. In C++ you can use GEOS http://trac.osgeo.org/geos/ in Java it would be JTS http://www.vividsolutions.com/jts/JTSHome.htm.
If, however, you are creating the grid yourself, and want an answer to the far simpler question of how do I find the intersection points between the horizontal and vertical lines of the grid I just created, the answer would be to use the same exact latitude, longitude values that you used for the LineStrings in a nested loop:
Document document = new Document();
for(y = -90; y < 0; y += 15){
for(x = -180; x < 0; x+= 15){
Point point = new Point();
point.Coordinate = new Vector(x, y);
Placemark placemark = new Placemark();
placemark.Geometry = point;
document.AddFeature(placemark);
}
}
.. repeat for the other 4 quadrants
// It's conventional for the root element to be Kml,
// but you could use document instead.
Kml root = new Kml();
root.Feature = document;
XmlFile kml = KmlFile.Create(root, false);
Here is some source code if you wanted to use DotSpatial, for instance, to find the intersection between the grids and a Shapefile. In this case, the shapefile has river lines and only produced one intersection point. Be advised that the topology intersection code is kind of slow, so you will want to use extent checking to speed things up. In your case you may want to build new Features by using KMLSharp to read a the linestring coordinates in a kml source file, rather than opening a shapefile, but the intersection code would be similar.
As a side note, I don't think the seemingly easy to use FeatureSet.Intersection method is smart enough to handle the case where line intersections produce point features as intersections. It only works for points or polygons where the output is likely to be the same feature type as the input.
using DotSpatial.Controls;
using DotSpatial.Data;
using DotSpatial.Topology;
using DotSpatial.Symbology;
private FeatureSet gridLines;
private void buttonAddGrid_Click(object sender, EventArgs e)
{
gridLines = new FeatureSet(FeatureType.Line);
for (int x = -180; x < 0; x += 15)
{
List<Coordinate> coords = new List<Coordinate>();
coords.Add(new Coordinate(x, -90));
coords.Add(new Coordinate(x, 90));
LineString ls = new LineString(coords);
gridLines.AddFeature(ls);
}
for (int y = -90; y < 0; y += 15)
{
List<Coordinate> coords = new List<Coordinate>();
coords.Add(new Coordinate(-180, y));
coords.Add(new Coordinate(180, y));
LineString ls = new LineString(coords);
gridLines.AddFeature(ls);
}
map1.Layers.Add(new MapLineLayer(gridLines));
}
private void buttonIntersect_Click(object sender, EventArgs e)
{
if (gridLines == null)
{
MessageBox.Show("First add the grid.");
}
IFeatureSet river = FeatureSet.Open(#"C:\Data\Rivers\River.shp");
MapLineLayer riverLayer = new MapLineLayer(river);
map1.Layers.Add(river);
List<DotSpatial.Topology.Point> allResultPoints = new List<DotSpatial.Topology.Point>();
foreach (Feature polygon in river.Features)
{
Geometry lineString = polygon.BasicGeometry as Geometry;
foreach (Feature lineFeature in gridLines.Features)
{
// Speed up calculation with extent testing.
if(!lineFeature.Envelope.Intersects(lineString.Envelope)){
continue;
}
IFeature intersectFeature = lineFeature.Intersection(lineString);
if (intersectFeature == null)
{
continue;
}
MultiPoint multi = intersectFeature.BasicGeometry as MultiPoint;
if (multi != null)
{
for(int i = 0; i < multi.NumGeometries; i++)
{
allResultPoints.Add(intersectFeature.GetBasicGeometryN(i) as DotSpatial.Topology.Point);
}
}
DotSpatial.Topology.Point single = intersectFeature.BasicGeometry as DotSpatial.Topology.Point;
{
allResultPoints.Add(single);
}
}
}
FeatureSet finalPoints = new FeatureSet(FeatureType.Point);
foreach(DotSpatial.Topology.Point pt in allResultPoints){
finalPoints.AddFeature(pt);
}
map1.Layers.Add(new MapPointLayer(finalPoints));
}
I think the DotSpatial library should meet your needs, I have used this library in the past but not made use of the intersections function:
http://dotspatial.codeplex.com/wikipage?title=DotSpatial.Data.FeatureSetExt.Intersection
If you try and do your own line intersection analysis, know that a simplistic Cartesian plane approach will introduce errors (which [I think] become more obvious as you approach the poles).
See here: http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html
And here: Intersection between two geographic lines
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 convex polygon P1 of N points. This polygon could be any shape or proportion (as long as it is still convex).
I need to compute another polygon P2 using the original polygons geometry, but "expanded" by a given number of units. What might the algorithm be for expanding a convex polygon?
To expand a convex polygon, draw a line parallel to each edge and the given number of units away. Then use the intersection points of the new lines as the vertices of the expanded polygon. The javascript/canvas at the end follows this functional breakdown:
Step 1: Figure out which side is "out"
The order of the vertices (points) matters. In a convex polygon, they can be listed in a clockwise (CW), or a counter-clockwise (CCW) order. In a CW polygon, turn one of the edges 90 degrees CCW to obtain an outward-facing normal. On a CCW polygon, turn it CW instead.
If the turn direction of the vertices is not known in advance, examine how the second edge turns from the first. In a convex polygon, the remaining edges will keep turning in the same direction:
Find the CW normal of the first edge. We don't know yet whether it's facing inward or outward.
Compute the dot product of the second edge with the normal we computed. If the second edge turns CW, the dot product will be positive. It will be negative otherwise.
Math:
// in vector terms:
v01 = p1 - p0 // first edge, as a vector
v12 = p2 - p1 // second edge, as a vector
n01 = (v01.y, -v01.x) // CW normal of first edge
d = v12 * n01 // dot product
// and in x,y terms:
v01 = (p1.x-p0.x, p1.y-p0.y) // first edge, as a vector
v12 = (p2.x-p1.x, p2.y-p1.y) // second edge, as a vector
n01 = (v01.y, -v01.x) // CW normal of first edge
d = v12.x * n01.x + v12.y * n01.y; // dot product: v12 * n01
if (d > 0) {
// the polygon is CW
} else {
// the polygon is CCW
}
// and what if d==0 ?
// -- that means the second edge continues in the same
// direction as a first. keep looking for an edge that
// actually turns either CW or CCW.
Code:
function vecDot(v1, v2) {
return v1.x * v2.x + v1.y * v2.y;
}
function vecRot90CW(v) {
return { x: v.y, y: -v.x };
}
function vecRot90CCW(v) {
return { x: -v.y, y: v.x };
}
function polyIsCw(p) {
return vecDot(
vecRot90CW({ x: p[1].x - p[0].x, y: p[1].y - p[0].y }),
{ x: p[2].x - p[1].x, y: p[2].y - p[1].y }) >= 0;
}
var rot = polyIsCw(p) ? vecRot90CCW : vecRot90CW;
Step 2: Find lines parallel to the polygon edges
Now that we know which side is out, we can compute lines parallel to each polygon edge, at exactly the required distance. Here's our strategy:
For each edge, compute its outward-facing normal
Normalize the normal, such that its length becomes one unit
Multiply the normal by the distance we want the expanded polygon to be from the original
Add the multiplied normal to both ends of the edge. That will give us two points on the parallel line. Those two points are enough to define the parallel line.
Code:
// given two vertices pt0 and pt1, a desired distance, and a function rot()
// that turns a vector 90 degrees outward:
function vecUnit(v) {
var len = Math.sqrt(v.x * v.x + v.y * v.y);
return { x: v.x / len, y: v.y / len };
}
function vecMul(v, s) {
return { x: v.x * s, y: v.y * s };
}
var v01 = { x: pt1.x - pt0.x, y: pt1.y - pt0.y }; // edge vector
var d01 = vecMul(vecUnit(rot(v01)), distance); // multiplied unit normal
var ptx0 = { x: pt0.x + d01.x, y: pt0.y + d01.y }; // two points on the
var ptx1 = { x: pt1.x + d01.x, y: pt1.y + d01.y }; // parallel line
Step 3: Compute the intersections of the parallel lines
--these will be the vertices of the expanded polygon.
Math:
A line going through two points P1, P2 can be described as:
P = P1 + t * (P2 - P1)
Two lines can be described as
P = P1 + t * (P2 - P1)
P = P3 + u * (P4 - P3)
And their intersection has to be on both lines:
P = P1 + t * (P2 - P1) = P3 + u * (P4 - P3)
This can be massaged to look like:
(P2 - P1) * t + (P3 - P4) * u = P3 - P1
Which in x,y terms is:
(P2.x - P1.x) * t + (P3.x - P4.x) * u = P3.x - P1.x
(P2.y - P1.y) * t + (P3.y - P4.y) * u = P3.y - P1.y
As the points P1, P2, P3 and P4 are known, so are the following values:
a1 = P2.x - P1.x a2 = P2.y - P1.y
b1 = P3.x - P4.x b2 = P3.y - P4.y
c1 = P3.x - P1.x c2 = P3.y - P1.y
This shortens our equations to:
a1*t + b1*u = c1
a2*t + b2*u = c2
Solving for t gets us:
t = (b1*c2 - b2*c1)/(a2*b1 - a1*b2)
Which lets us find the intersection at P = P1 + t * (P2 - P1).
Code:
function intersect(line1, line2) {
var a1 = line1[1].x - line1[0].x;
var b1 = line2[0].x - line2[1].x;
var c1 = line2[0].x - line1[0].x;
var a2 = line1[1].y - line1[0].y;
var b2 = line2[0].y - line2[1].y;
var c2 = line2[0].y - line1[0].y;
var t = (b1*c2 - b2*c1) / (a2*b1 - a1*b2);
return {
x: line1[0].x + t * (line1[1].x - line1[0].x),
y: line1[0].y + t * (line1[1].y - line1[0].y)
};
}
Step 4: Deal with special cases
There is a number of special cases that merit attention. Left as an exercise to the reader...
When there's a very sharp angle between two edges, the expanded vertex can be very far from the original one. You might want to consider clipping the expanded edge if it goes beyond some threshold. At the extreme case, the angle is zero, which suggests that the expanded vertex is at infinity, causing division by zero in the arithmetic. Watch out.
When the first two edges are on the same line, you can't tell if it's a CW or a CCW polygon by looking just at them. Look at more edges.
Non convex polygons are much more interesting... and are not tackled here.
Full sample code
Drop this in a canvas-capable browser. I used Chrome 6 on Windows. The triangle and its expanded version should animate.
canvas { border: 1px solid #ccc; }
$(function() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var context = canvas.getContext('2d');
// math for expanding a polygon
function vecUnit(v) {
var len = Math.sqrt(v.x * v.x + v.y * v.y);
return { x: v.x / len, y: v.y / len };
}
function vecMul(v, s) {
return { x: v.x * s, y: v.y * s };
}
function vecDot(v1, v2) {
return v1.x * v2.x + v1.y * v2.y;
}
function vecRot90CW(v) {
return { x: v.y, y: -v.x };
}
function vecRot90CCW(v) {
return { x: -v.y, y: v.x };
}
function intersect(line1, line2) {
var a1 = line1[1].x - line1[0].x;
var b1 = line2[0].x - line2[1].x;
var c1 = line2[0].x - line1[0].x;
var a2 = line1[1].y - line1[0].y;
var b2 = line2[0].y - line2[1].y;
var c2 = line2[0].y - line1[0].y;
var t = (b1*c2 - b2*c1) / (a2*b1 - a1*b2);
return {
x: line1[0].x + t * (line1[1].x - line1[0].x),
y: line1[0].y + t * (line1[1].y - line1[0].y)
};
}
function polyIsCw(p) {
return vecDot(
vecRot90CW({ x: p[1].x - p[0].x, y: p[1].y - p[0].y }),
{ x: p[2].x - p[1].x, y: p[2].y - p[1].y }) >= 0;
}
function expandPoly(p, distance) {
var expanded = [];
var rot = polyIsCw(p) ? vecRot90CCW : vecRot90CW;
for (var i = 0; i < p.length; ++i) {
// get this point (pt1), the point before it
// (pt0) and the point that follows it (pt2)
var pt0 = p[(i > 0) ? i - 1 : p.length - 1];
var pt1 = p[i];
var pt2 = p[(i < p.length - 1) ? i + 1 : 0];
// find the line vectors of the lines going
// into the current point
var v01 = { x: pt1.x - pt0.x, y: pt1.y - pt0.y };
var v12 = { x: pt2.x - pt1.x, y: pt2.y - pt1.y };
// find the normals of the two lines, multiplied
// to the distance that polygon should inflate
var d01 = vecMul(vecUnit(rot(v01)), distance);
var d12 = vecMul(vecUnit(rot(v12)), distance);
// use the normals to find two points on the
// lines parallel to the polygon lines
var ptx0 = { x: pt0.x + d01.x, y: pt0.y + d01.y };
var ptx10 = { x: pt1.x + d01.x, y: pt1.y + d01.y };
var ptx12 = { x: pt1.x + d12.x, y: pt1.y + d12.y };
var ptx2 = { x: pt2.x + d12.x, y: pt2.y + d12.y };
// find the intersection of the two lines, and
// add it to the expanded polygon
expanded.push(intersect([ptx0, ptx10], [ptx12, ptx2]));
}
return expanded;
}
// drawing and animating a sample polygon on a canvas
function drawPoly(p) {
context.beginPath();
context.moveTo(p[0].x, p[0].y);
for (var i = 0; i < p.length; ++i) {
context.lineTo(p[i].x, p[i].y);
}
context.closePath();
context.fill();
context.stroke();
}
function drawPolyWithMargin(p, margin) {
context.fillStyle = "rgb(255,255,255)";
context.strokeStyle = "rgb(200,150,150)";
drawPoly(expandPoly(p, margin));
context.fillStyle = "rgb(150,100,100)";
context.strokeStyle = "rgb(200,150,150)";
drawPoly(p);
}
var p = [{ x: 100, y: 100 }, { x: 200, y: 120 }, { x: 80, y: 200 }];
setInterval(function() {
for (var i in p) {
var pt = p[i];
if (pt.vx === undefined) {
pt.vx = 5 * (Math.random() - 0.5);
pt.vy = 5 * (Math.random() - 0.5);
}
pt.x += pt.vx;
pt.y += pt.vy;
if (pt.x < 0 || pt.x > 400) { pt.vx = -pt.vx; }
if (pt.y < 0 || pt.y > 400) { pt.vy = -pt.vy; }
}
context.clearRect(0, 0, 800, 400);
drawPolyWithMargin(p, 10);
}, 50);
}
});
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
</head>
<body>
<canvas id="canvas" width="400" height="400"></canvas>
</body>
</html>
sample code disclaimers:
the sample sacrifices some efficiency for the sake of clarity. In your code, you may want to compute each edge's expanded parallel just once, and not twice as in here
the canvas's y coordinate grows downward, which inverts the CW/CCW logic. Things keep on working though as we just need to turn the outward normals in a direction opposite to the polygon's -- and both get flipped.
For each line segment of the original, find the midpoint m and (unit length) outward normal u of the segment. The corresponding segment of the expanded polygon will then lie on the line through m+n*u (where you want to expand the original by n) with normal u. To find the vertices of the expanded polygon you then need to find the intersection of pairs of successive lines.
If the polygon is centered on the origin simply multiply each of the points by a common scaling factor.
If the polygon is not centered on the origin then first translate so the center is on the origin, scale, and then translate it back to where it was.
After your comment
It seems you want all points to be moved the same distance away from the origin.
You can do this for each point by getting the normalised vector to this point. multiplying this by your 'expand constant' and adding the resulting vector back onto the original point.
n.b. You will still have to translate-modify-translate if the center is not also the origin for this solution.
Let the points of the polygon be A1, B1, C1... Now you have lines from A1 to B1, then from B1 to C1... We want to compute points A2, B2, C2 of the polygon P2.
If you bisect angle, for example A1 B1 C1, you will have a line which goes in the direction you want. Now you can find a point B2 on it which is the appropriate distance from B1 on bisector line.
Repeat this for all points of the polygon P1.
Look at straight skeletons. As has been implied here there are a number of tricky issues with non convex polygons that you have been mecifully spared!